用过TapTap的APP发现在排行榜的列表页点击单项会有一个进入详情页的过场效果,觉得很不错, 小米的系统相册也有类似的过场效果,个人对这个效果很有兴趣,便决定自己也实现下这个效果。虽说做完Demo后了解到android 5.0以上的sdk有共享元素动画的方式去实现,但是,这里并不采用该方式。按照自己的思路来实现,记录一下实现的过程。Demo是基于kotlin写的。效果图如下:

目录

目录

效果分析

一些要点

1.Activity转场动画

2.跳转

3.View信息

4.根布局

5.坐标

代码

1.布局文件

2.页面代码



效果分析

从上面的GIF看到 在列表页点击一个列表项的图片,图片大小发生变化并且移动到详情页中的某一个位置,详情页返回时图片会回到列表页中的原位置。详情页中布局有一个不可见 INVISIBLE 状态的ImageView,当动画完成后,才将可见状态设置为可见。从思路上就是,复制一个ImageView添加到跳转页中,跳转页中有一个不可见的ImageView,复制的ImageView经过动画变化到达与目标ImageView的位置大小状态一致,结束。

这里实现用到的知识点有 Activity过场动画,属性动画,View的坐标以及View在Window中的坐标。以下是详细分析

1.点击列表项的图片时,传递当前图片View的信息到到详情页,使用startActivityForResult,在列表页的onStop生命周期方法(防止出现闪烁情况)中将当前项的图片隐藏,列表页根布局透明度设为0.记录当前项图片的位置,宽高信息。

2.详情页初始状态根布局的透明度为0,且有一个INVISIBLE 状态的ImageView,根据从详情页传递过来的View信息,构建一个复制的ImageView添加到根布局中,计算出复制ImageView和目标ImageView的坐标,宽高差异后,执行复制ImageView坐标,宽高动画,并同时进行详情页根布局的透明度变化,动画完成后,将目标ImageView设为可见,从根布局移除添加的复制ImageView。此时,图片从列表页过渡到列表页中的过程结束。

3.详情页返回列表页的过程也是类似,在此demo中是监听手机的返回键,将详情页的目标ImageView的信息传递给列表页,在列表页中复制一个ImageView添加到根布局中,在第1步的时候我们已经记录了被点击图片的位置,宽高信息,所以复制的ImageView需要回到被点击图片的状态中,动画和第2步一样(反过来),执行完后,在第1步被隐藏的图片设为可见,同样移除复制的ImageView。

一些要点

1.Activity转场动画

Activity默认的转场效果是从右往左,从gif的效果图上看是进场Activity直接从上覆盖到原Activity,所以这里可以用以下代码来实现,可以看成是透明度进场时长为0的动画效果,也可以定义动画xml文件来控制时长。

overridePendingTransition(0,0)

2.跳转

从列表页跳转到详情页需要传递列表页当前项图片的View信息,从详情页回到列表页也需要传递信息,所以跳转这里用startActivityForResult方法来实现

3.View信息

这里我们定义一个类来记录页面传的View信息,主要记录坐标,宽高信息,定义如下:

(由于在本Demo中图片是在资源包res里面的,一般情况下可以多加个url属性,通过一些图片缓存框架如Glide可以很快加载)

package com.example.zyb.tapdemo.Beanimport java.io.Serializable/*** 复制的View的信息类  宽高和坐标* Created by ZYB on 2018/8/3 0003.*/
class ViewInfo(var width:Int,var height:Int,var x:Int,var y:Int):Serializable{}

4.根布局

根布局采用的是FrameLayout帧布局,新添加的ImageView处于最上的图层,通过坐标方便的定位到ImageView在根布局所处的位置(只要是继承于ViewGroup都可以)

5.坐标

View的属性中有x,y,但是x,y坐标是相对于父布局的位置坐标,所以在列表中的图片ImageView的x,y坐标不是我们想要的,我们应该获取的是列表中ImageView相对于列表页根布局的坐标。在这里,我们使用的是屏幕Window坐标,屏幕坐标,是View相对于Window窗口的绝对坐标,通过屏幕坐标可以间接得出View在根布局中的坐标,使得复制的View的位置和原View一样。

如果存在状态栏的情况下,列表项ImageView相对于根布局的Y坐标=屏幕Y坐标-状态栏高度

不存在的情况下,列表项ImageView相对于根布局的Y坐标=屏幕Y坐标

列表项ImageView相对于根布局的X坐标=屏幕X坐标

这样一来 我们就得到了列表项相对于根布局的X,Y坐标,由此我们可以在详情页复制一个位置和列表项位置一样的View出来

   获取View的屏幕window坐标  View类的getLocationInWindow方法

//定义一个长度为2的数组
var item_loc = IntArray(2)
//调用View的getLocationInWindow方法
//item_loc[0]即为View相对于屏幕的X坐标,item_loc[1]即为View相对于屏幕的Y坐标,
View.getLocationInWindow(item_loc)

获取状态栏高度

/*** 获取状态栏高度*/
fun getStateBarHeight(context:Context):Int{var height = 0;var resid = context.resources.getIdentifier("status_bar_height", "dimen", "android");if (resid > 0){height = context.resources.getDimensionPixelOffset(resid)}return height;
}

代码

1.布局文件

列表页布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/frame_root"tools:context="com.example.zyb.tapdemo.ListInfoActivity"><android.support.v7.widget.RecyclerViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/rcv_tap"/>
</FrameLayout>

详情页布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/frame_root"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/ll_content"android:orientation="vertical"android:alpha="0.0"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="古陵逝烟.大宗师"android:textSize="18sp"android:textStyle="bold"android:layout_marginTop="50dp"/><ImageViewandroid:layout_width="300dp"android:layout_height="300dp"android:layout_gravity="center_horizontal"android:layout_marginTop="20dp"android:scaleType="centerCrop"android:src="@mipmap/timg"android:visibility="invisible"android:id="@+id/img_target"/><TextViewandroid:layout_width="250dp"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="30dp"android:textStyle="bold"android:textSize="18sp"android:text="冷灯看剑,剑上几番功名?炉香无须计苍生,纵一川烟逝,万丈云埋,孤阳还照古陵。"/></LinearLayout>
</FrameLayout>

item布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="10dp"android:paddingRight="10dp"android:paddingTop="10px"><ImageViewandroid:layout_width="80dp"android:layout_height="80dp"android:id="@+id/item_img"android:src="@mipmap/timg"android:scaleType="centerCrop"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="烟都大宗师"android:layout_gravity="center_vertical"android:paddingLeft="50dp"/></LinearLayout><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="#ff0000"android:layout_marginTop="10px"/>
</LinearLayout>

布局上都比较简单,没什么可以讲的

2.页面代码

ListInfoActivity.kt 

package com.example.zyb.tapdemoimport android.content.Intent
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.view.View
import android.widget.ImageView
import com.example.zyb.tapdemo.Adapter.TapAdapter
import com.example.zyb.tapdemo.Bean.ViewInfo
import kotlinx.android.synthetic.main.activity_main.*;class ListInfoActivity : AppCompatActivity() {lateinit var adapter: TapAdapter//记录列表当前被点击的View的信息lateinit var viewinfo: ViewInfo//记录列表当前被点击的View的坐标信息private var item_loc = IntArray(2)var itemX = 0;var itemY = 0;//记录列表当前点击的viewvar focus_view : ImageView ?= null//是否可以隐藏列表当前点击的Viewvar isCanHideView = falseoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)initRecycleView()}fun initRecycleView() {adapter = TapAdapter(this)rcv_tap.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)rcv_tap.adapter = adapteradapter.itemOnclickListener = object: TapAdapter.itemOnClickListener{override fun itemOnclick(v: View) {when(v.id){R.id.item_img ->{imgItemClick(v as ImageView)}}}}}fun imgItemClick(v:ImageView){focus_view = visCanHideView = true//获取当前view在屏幕的绝对坐标X,Yv.getLocationInWindow(item_loc)//减去状态栏高度即可得到当前view在Activity跟布局的绝对坐标YitemX = item_loc[0]itemY = item_loc[1] - getStateBarHeight(this)viewinfo = ViewInfo(v.measuredWidth, v.measuredHeight, itemX, itemY)var intent = Intent(this,DetailActivity::class.java)intent.putExtra("viewinfo",viewinfo);startActivityForResult(intent, REQUEST_GO)overridePendingTransition(0,0)}override fun onResume() {super.onResume()isCanHideView = false}/*** 在onstop隐藏View 防止隐藏或者更改透明度时发生闪烁*/override fun onStop() {super.onStop()if (focus_view != null && isCanHideView) {focus_view!!.visibility = View.INVISIBLErcv_tap.alpha = 0f}}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (requestCode == REQUEST_GO && resultCode == RESULT_BACK){var backViewInfo = data!!.getSerializableExtra("backViewInfo") as ViewInfoViewHelper(this,focus_view!!).setRootView(frame_root).setViewInfos(backViewInfo,viewinfo).addCopyView().setDuration(500L).startAnim()}}}

DetailActivity .kt

package com.example.zyb.tapdemoimport android.app.Activity
import android.content.Intent
import android.os.Bundle
import com.example.zyb.tapdemo.Bean.ViewInfo
import kotlinx.android.synthetic.main.activity_detail.*/*** Created by ZYB on 2018/8/3 0003.*/
class DetailActivity : Activity() {lateinit var receiver_viewinfo: ViewInfolateinit var target_viewinfo: ViewInfovar target_loc = IntArray(2)var targetX = 0var targetY = 0//是否第一次执行onWindowFocusChangedvar isFirstWindowFocusChanged = true;override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_detail)receiver_viewinfo = intent.getSerializableExtra("viewinfo") as ViewInfo}//在此处可以获得布局控件的宽高属性等override fun onWindowFocusChanged(hasFocus: Boolean) {super.onWindowFocusChanged(hasFocus)if (isFirstWindowFocusChanged) {isFirstWindowFocusChanged = falseimg_target.getLocationInWindow(target_loc)targetX = target_loc[0]targetY = target_loc[1] - getStateBarHeight(this)target_viewinfo = ViewInfo(img_target.measuredWidth, img_target.measuredHeight, targetX, targetY)ViewHelper(this,img_target).setRootView(frame_root).setViewInfos(receiver_viewinfo,target_viewinfo).addCopyView().setDuration(500L).startAnim()}}//监听返回键 事件override fun onBackPressed() {var intent = Intent()intent.putExtra("backViewInfo", ViewInfo(img_target.measuredWidth, img_target.measuredHeight, targetX, targetY))setResult(RESULT_BACK,intent)finish()overridePendingTransition(0,0)}}

Adapter.kt

package com.example.zyb.tapdemo.Adapterimport android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.example.zyb.tapdemo.R/**这里只是单纯显示布局而已,所以没有传入bean数据类,直接在getItemCount()设置列表的项数* Created by ZYB on 2018/8/3 0003.*/
class TapAdapter(val context:Context):RecyclerView.Adapter<TapAdapter.Holder>(),View.OnClickListener
{lateinit var itemOnclickListener: itemOnClickListeneroverride fun onBindViewHolder(holder: Holder?, position: Int) {holder!!.item_img!!.setOnClickListener(this)}override fun getItemCount(): Int {return 20;}override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder {return Holder(LayoutInflater.from(context).inflate(R.layout.item_tap,null))}override fun onClick(v: View?) {if (itemOnclickListener != null){itemOnclickListener.itemOnclick(v!!)}}inner class Holder(v:View):RecyclerView.ViewHolder(v){var item_img:ImageViewinit {item_img = v.findViewById(R.id.item_img) as ImageView}}//单个View的点击监听interface itemOnClickListener{fun itemOnclick(v:View);}
}

由于点击进入详情页以及从详情页返回的动画效果一致,所以这里定义了一个类来执行添加复制类以及动画效果

ViewHelper.kt

package com.example.zyb.tapdemoimport android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.widget.ImageView
import com.example.zyb.tapdemo.Bean.ViewInfo
import com.example.zyb.tapdemo.Listener.animationEndListener/*** 负责添加复制的View到根布局 并且开始动画* context 上下文* targetView 目标View* Created by ZYB on 2018/8/5 0005.*/
class ViewHelper(var context : Context,var targetView:View){//根Viewlateinit var rootView : ViewGroup//复制的ImageViewlateinit var copyView : ImageView//复制的VIew的信息类lateinit var fromViewInfo : ViewInfo//目标View的信息类lateinit var toViewInfo : ViewInfo//动画时长private  var duration = 0L;fun setRootView(rootView : ViewGroup):ViewHelper{this.rootView = rootViewreturn this}fun setViewInfos(fromViewInfo : ViewInfo, toViewInfo : ViewInfo) : ViewHelper{this.fromViewInfo = fromViewInfothis.toViewInfo = toViewInforeturn this}fun  setDuration(duration: Long) : ViewHelper{this.duration = duration;return this}//构建一个View添加到根布局fun addCopyView() : ViewHelper{copyView = ImageView(context)var layoutParam = ViewGroup.LayoutParams(toViewInfo.width, toViewInfo.height)copyView.scaleType = ImageView.ScaleType.CENTER_CROPcopyView.layoutParams = layoutParamcopyView.x = toViewInfo.x.toFloat()copyView.y = toViewInfo.y.toFloat()copyView.setImageResource(R.mipmap.timg)rootView.addView(copyView)return this}//执行根布局透明度动画,复制ImageView的x坐标动画,Y坐标动画,宽高动画fun startAnim() : ViewHelper{var alphaAnim = ObjectAnimator.ofFloat(rootView.getChildAt(0),"alpha",0f,1f)var xAnim = ObjectAnimator.ofFloat(copyView, "x", fromViewInfo.x.toFloat(), toViewInfo.x.toFloat())var yAnim = ObjectAnimator.ofFloat(copyView, "y", fromViewInfo.y.toFloat(), toViewInfo.y.toFloat())var widthAnim = ValueAnimator.ofInt(fromViewInfo.width, toViewInfo.width)var heightAnim = ValueAnimator.ofInt(fromViewInfo.height, toViewInfo.height)widthAnim.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {override fun onAnimationUpdate(animation: ValueAnimator?) {var param = copyView.layoutParamsparam.width = animation!!.animatedValue as IntcopyView.layoutParams = param}})heightAnim.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {override fun onAnimationUpdate(animation: ValueAnimator?) {var param = copyView.layoutParamsparam.height = animation!!.animatedValue as IntcopyView.layoutParams = param}})//多个动画同时播放var animset = AnimatorSet()animset.playTogether(xAnim, yAnim, widthAnim, heightAnim,alphaAnim)animset.duration = 500;animset.interpolator = AccelerateInterpolator()animset.addListener(object : animationEndListener(){override fun animatorEnd(animation: Animator?) {//动画执行完毕后,目标ImageView显示出来,移除复制的ImageViewtargetView.visibility = View.VISIBLErootView.removeView(copyView)}})animset.start()return this}}

总结

实现这样的过场效果需要了解View的屏幕window坐标,以及View的坐标知识,属性动画,掌握了思路后,亲手实现这样的效果还是挺有趣的。(PS:感觉写的好乱啊,以下会有Demo下载地址,可以参考)

下载地址 https://download.csdn.net/download/qq_33617079/10587284

Android 过场效果--列表页到详情页相关推荐

  1. Android仿京东、天猫商品详情页

    前言 前面在介绍控件TabLayout控件和CoordinatorLayout使用的时候说了下实现京东.天猫详情页面的效果,今天要说的是优化版,是我们线上实现的效果,首先看一下效果: 项目结构分析 首 ...

  2. html列表详情页,如何区分一个页面是列表页还是详情页

    解析页面是做爬虫的过程中的重要环节,而且如果站点多了,解析也会变得非常复杂,所以智能化解析就可能是一个不错的解决方案.如果我们能够容忍一定的错误率,那么我们可以利用智能化解析算法帮我们提取一些内容,简 ...

  3. 织梦 详情页 php,织梦DEDECMS列表页与详情页调用图集多张图片的方法

    DEDECMS列表页与详情页调用图集多张图片的方法,先找到include/common.inc.php文件,把下面代码贴进去: function Getimg($aid,$imgwith,$imghe ...

  4. 列表页详情页html源码,UI布局欣赏:文章列表与内容详情页设计

    UI布局欣赏:文章列表与内容详情页设计 3月 23, 2017 评论 Sponsor 信息内容几乎是每个新闻.博客.摄影.社区等类型媒体常用的功能,所以他们一般都会拥有信息的列表页和内容详情页面的设计 ...

  5. Scrapy翻页爬取示例——列表页、详情页

    Scrapy翻页爬取示例--列表页.详情页 引言: 本人最近在帮助同事们爬取一批英-泰双语数据,顺带复习了一下scrapy爬虫相关的知识.下面以简单的小项目为例,一起来开始吧! 示例一:爬取列表页 本 ...

  6. selenium和Python3.6实现招聘狗网站自动识别验证码登录、列表页、详情页爬取

    之所以选择selenium实现登录主要是为了处理验证码,招聘狗网站的验证码图片是拼接出来的,所以我的方法是通过webdriver截图来实现,然后通过打码兔平台获取验证码坐标实现自动自动登录.列表页和详 ...

  7. 实现商城的列表页与详情页

    这几天做项目做到了商城的列表页与详情页,需要将数据库的数据展示到前端来,我这里写了关于前端与后端的代码实现 1.创建商品模块 这里需要先创建一个商品的模块,用来对商品进行一系列的操作 在终端输入pyt ...

  8. 小程序列表页进入详情页

    小程序列表页进入详情页 从列表页进入详情页,通过使用本地存储来实现,当我点击的时候,拿到这个索引,并且获取整个对象,存入内存中,然后在详情页onload中get拿到这个数据,放到页面进行展示 1.1 ...

  9. android动画知乎,Android模仿知乎的回答详情页的动画效果

    废话不多说,咱们第一篇文章就是模仿"知乎"的回答详情页的动画效果,先上个原版的效果图,咱们就是要做出这个效果 在实现之前,我们先根据上面的动画效果,研究下需求,因为gif帧数有限, ...

最新文章

  1. 如何理解HTTP协议是无状态的
  2. eclipse启动出错的解决方案:org.osgi.framework.BundleExcep...
  3. matplotlib cmap
  4. ASP.NET服务器控件的生命周期分析
  5. 「镁客·请讲」安智汽车郭健:ADAS是一个链条式的系统,每个模块都必须做好...
  6. 根文件系统构建(BusyBox方式)
  7. 踩自行车来进行人力发电,真的能驱动旋转木马吗?
  8. 【HDU - 4417】Super Mario(查询区间小于K的数的个数,主席树)
  9. Chapter7-7_Deep Learning for Coreference Resolution
  10. WinForm小程序系列:注册表阅读器
  11. 坐标轨迹计算_【老杨讲坛】737NG无系留最大风速限制的插值计算
  12. map转换成JSON的方法
  13. Simple QQLogin 1.3(QQ2008 或更早版本)
  14. Java Wbe开发快速入门
  15. QComboBox下拉框给选项增加删除按钮
  16. Excel IF 函数多条件判断
  17. 2021年嵌入式面试题汇总(最新经典)
  18. Android仿美团选择城市
  19. kitti LIDAR点云生成鸟瞰图BEV
  20. Web应用安全十大主动安全措施

热门文章

  1. 如何使用Python脚本转换数据和命令行
  2. Bootstrap书籍分类
  3. xlwings出现的报错
  4. java照片切换播放音乐_怎么将照片制作成视频并添加音乐呢
  5. 我发现了个 Python 黑魔法,执行任意代码都会自动念上一段 『平安经』
  6. java计算机毕业设计ssm学生课堂考勤小程序947n4(附源码、数据库)
  7. language express
  8. [机缘参悟-46]:鬼谷子-第十谋篇-谋者,智慧之意也
  9. lenovo小新 win10系统装ubuntu经验总结
  10. CREO学习问题——不显示弱尺寸,无法标注线段长度