Android 过场效果--列表页到详情页
用过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 过场效果--列表页到详情页相关推荐
- Android仿京东、天猫商品详情页
前言 前面在介绍控件TabLayout控件和CoordinatorLayout使用的时候说了下实现京东.天猫详情页面的效果,今天要说的是优化版,是我们线上实现的效果,首先看一下效果: 项目结构分析 首 ...
- html列表详情页,如何区分一个页面是列表页还是详情页
解析页面是做爬虫的过程中的重要环节,而且如果站点多了,解析也会变得非常复杂,所以智能化解析就可能是一个不错的解决方案.如果我们能够容忍一定的错误率,那么我们可以利用智能化解析算法帮我们提取一些内容,简 ...
- 织梦 详情页 php,织梦DEDECMS列表页与详情页调用图集多张图片的方法
DEDECMS列表页与详情页调用图集多张图片的方法,先找到include/common.inc.php文件,把下面代码贴进去: function Getimg($aid,$imgwith,$imghe ...
- 列表页详情页html源码,UI布局欣赏:文章列表与内容详情页设计
UI布局欣赏:文章列表与内容详情页设计 3月 23, 2017 评论 Sponsor 信息内容几乎是每个新闻.博客.摄影.社区等类型媒体常用的功能,所以他们一般都会拥有信息的列表页和内容详情页面的设计 ...
- Scrapy翻页爬取示例——列表页、详情页
Scrapy翻页爬取示例--列表页.详情页 引言: 本人最近在帮助同事们爬取一批英-泰双语数据,顺带复习了一下scrapy爬虫相关的知识.下面以简单的小项目为例,一起来开始吧! 示例一:爬取列表页 本 ...
- selenium和Python3.6实现招聘狗网站自动识别验证码登录、列表页、详情页爬取
之所以选择selenium实现登录主要是为了处理验证码,招聘狗网站的验证码图片是拼接出来的,所以我的方法是通过webdriver截图来实现,然后通过打码兔平台获取验证码坐标实现自动自动登录.列表页和详 ...
- 实现商城的列表页与详情页
这几天做项目做到了商城的列表页与详情页,需要将数据库的数据展示到前端来,我这里写了关于前端与后端的代码实现 1.创建商品模块 这里需要先创建一个商品的模块,用来对商品进行一系列的操作 在终端输入pyt ...
- 小程序列表页进入详情页
小程序列表页进入详情页 从列表页进入详情页,通过使用本地存储来实现,当我点击的时候,拿到这个索引,并且获取整个对象,存入内存中,然后在详情页onload中get拿到这个数据,放到页面进行展示 1.1 ...
- android动画知乎,Android模仿知乎的回答详情页的动画效果
废话不多说,咱们第一篇文章就是模仿"知乎"的回答详情页的动画效果,先上个原版的效果图,咱们就是要做出这个效果 在实现之前,我们先根据上面的动画效果,研究下需求,因为gif帧数有限, ...
最新文章
- 如何理解HTTP协议是无状态的
- eclipse启动出错的解决方案:org.osgi.framework.BundleExcep...
- matplotlib cmap
- ASP.NET服务器控件的生命周期分析
- 「镁客·请讲」安智汽车郭健:ADAS是一个链条式的系统,每个模块都必须做好...
- 根文件系统构建(BusyBox方式)
- 踩自行车来进行人力发电,真的能驱动旋转木马吗?
- 【HDU - 4417】Super Mario(查询区间小于K的数的个数,主席树)
- Chapter7-7_Deep Learning for Coreference Resolution
- WinForm小程序系列:注册表阅读器
- 坐标轨迹计算_【老杨讲坛】737NG无系留最大风速限制的插值计算
- map转换成JSON的方法
- Simple QQLogin 1.3(QQ2008 或更早版本)
- Java Wbe开发快速入门
- QComboBox下拉框给选项增加删除按钮
- Excel IF 函数多条件判断
- 2021年嵌入式面试题汇总(最新经典)
- Android仿美团选择城市
- kitti LIDAR点云生成鸟瞰图BEV
- Web应用安全十大主动安全措施