不知道大家有没有发现,Android版的掘金有下面这个小小动画:点击作者头像跳转到作者的详情页,而作者头像会从当前界面通过动画过渡到详情页界面。
**
知识贫乏限制了我的视野,真心想不到这怎么实现的?
最近在写动画方面文章时候,从网上找到了答案:「Activity过渡动画中的共享元素过渡」。
本文的初衷,是和大家一起扫盲,如果对你有用,「欢迎点赞」,让更多的小伙伴多学点知识。小小的动画,隐藏着巨大的知识点;怪不得面试造火箭,工作拧螺丝,这是知识储备,虽然可能一辈子也用不上。
【「系列好文推荐」】
一、Activity切换过渡动画
Activity
过渡动画包含「进入过渡」和「退出过渡」、「共享元素过渡」三个动画,它们同样仅支持Android 5.0+
版本。
一)、共享元素过渡动画
共享元素过渡指的两个Activity
共享的视图如何在两个Activity
之间进行过渡。例如上面的Gif
图,共享视图就是ImageView
。
共享元素也分一个元素和多个元素。
定义共享元素过渡效果「步骤」如下:
- 在两个
Activity
定义两个相同类型的View; - 给两个
View
设置相同的transitionName
属性; - 通过
ActivityOptions.makeSceneTransitionAnimation()
函数生成Bundle
对象; startActivity()
函数传递bundle
对象。
「栗子讲解,清晰易懂:」
- 分别在
activity_first.xml
和activity_second.xml
布局文件定义ImageView
组件,并将transitionName
属性设为activityTransform
。
1 | 复制代码<!--activity_first.xml文件内容--> |
「预览图」activityTransform
属性也可以通过代码设置。
1 | 复制代码if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
- 在
FirstActivity
中给ImageView
设置点击事件,跳转到第二个Activity。
1 | 复制代码ivImage.setOnClickListener { |
代码中,先判断当前Android
版本是否大于等于5.0,大于或等于Android 5.0
的话就设置共享元素动画,小于5.0 就正常启动第二个Activity
。
通过ActivityOptions.makeSceneTransitionAnimation()
创建启动Activity
过渡的一些参数,makeSceneTransitionAnimation()
函数第一个参数为Activity
对象;第二个参数为共享元素组件,这里设置为id
是ivImage
的ImageView
视图;第三个参数为transitionName
属性的值,这里是activityTransform
。在调用AcivityOptions
对象toBundle
函数,包装成Bundle
对象。
「效果图:」
「多个共享元素过渡」
多个共享元素过渡也很简单,只需要调用makeSceneTransitionAnimation()
函数的另外一个重载函数即可。
- 在前面XML布局的基础上,给
TextView
增加transitionName
属性:textTransform
。
1 | 复制代码<!--activity_first.xml文件内容--> |
- 构建多个
Pair
对象,并传递给makeSceneTransitionAnimation()
函数,启动Activity
。
1 | 复制代码ivImage.setOnClickListener { |
这里主要是通过将共享视图和transitionName
属性的值包装到Pair
对象,其他操作和一个共享元素的操作步骤并无区别。
「效果图:」
「深坑提醒」
有时从RecyclerView
界面进入到详情页,由于详情页加载延迟,可能出现没有效果。例如ImageView
从网络加载图片,可能A界面到B界面没效果,B回到A界面有效果。
解决步骤:
- 在
setContentView
后添加下面代码,延迟加载过渡动画。
1 | 复制代码if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
- 在共享元素视图加载完毕,或者图片加载完毕后调用下面代码,开始加载过渡动画。
1 | 复制代码if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
例如我是在Glide加载完再调用:
1 | 复制代码 Glide.with(mContext) |
大家也可以考虑下面代码:
1 | 复制代码shareElement.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { |
二)、进入过渡与退出过渡动画
与共享元素相反的,就是Activity进入与退出过渡动画,两个Activity之间在没有共享的视图情况下进行动画切换。下面先看三种动画效果图:「爆炸式效果」和「淡入淡出式效果」、「滑动式效果」。
- 「爆炸式」:将视图移入场景中心或从中移出;
- 「滑动式」:将视图从场景的其中一个边缘移入或移出;
- 「爆炸式」:通过更改视图的不透明度,在场景中添加视图或从中移除视图;
第一个界面采用Fade
淡入淡出效果,第二个界面采用了Explode
爆炸效果。
前后两个界面都采用了Slide
滑入滑出效果。
利用Android现有的过渡框架,实现起来是很简单的,步骤如下:
- 在
Activity
的onCreate()
方法中调用setContentView()
前设置启用窗口过渡属性;
1 | 复制代码window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) |
- 创建过渡效果对象
Slide
、Explode
、Fade
;
1 | 复制代码val slide=Slide() |
如果是Slide
效果,可以设置slideEdge
属性来指定滑动方向,默认是Gravity.BOTTOM
。
- 将过渡效果设置给window相关属性,设置;
1 | 复制代码//退出当前界面的过渡动画 |
- 调用第二个
Activity
界面,使用过渡效果。
1 | 复制代码 startActivity( |
那么Activity
的OnCreate()
方法看起来是这样子的。
1 | 复制代码 override fun onCreate(savedInstanceState: Bundle?) { |
上面代码中调用 了excludeTarget()
方法将状态栏和导航栏排除在过渡动画效果之外。否则会跟着一起起动画效果,不是很美观。
正常情况,退出与进入过渡动画会有一小段交叉的过程,而window.allowEnterTransitionOverlap=false
就是禁止交叉,只有退出过渡动画结束后才会再显示进入过渡动画。
如果第二个Activity
在finish
掉后,回到第一个Activity
界面也想有过渡效果,就不要手动调用finish()
,可以调用finishAfterTransition ()
方法。
三)、兼容Android 5.0前
如果Android 5.0前也想要有切换动画怎么办?
- 在
res/anim
文件夹下创建想要的效果:
1 | 复制代码<alpha |
- 在启动
Activity
后调用overridePendingTransition()
方法。
1 | 复制代码val intent = Intent(this, TestActivity2::class.java) |
overridePendingTransition()
方法第一个参数为下一个界面进入动画,第二个参数为当前界面退出动画。
到这里,Activity
的切换使用过渡动画基本就结束了。有朋友可能会问,只有Activity切换才能应用过渡效果么?
二、布局变化过渡动画
在上一节要理解一个概念:场景。布局的显示与隐藏可以理解分别为一个场景,过渡动画就是解决场景切换带来的生硬视觉感受。Activity切换过渡动画指在两个Activity之间,而布局变化过渡动画,是指同个Activity之间View的变化过渡动画。
一)、手动创建Scene
手动创建场景的话,需要我们自己创建起始和结束场景,利用现有的过渡效果来达到两个场景的切换。默认情况下,当前界面就是起始场景。
- 创建起始场景和结束场景的
xml
布局。起始场景和结束场景需要有相同根元素,例如下面代码id
为flConatent
的FrameLayout
布局。
1 | 复制代码<?xml version="1.0" encoding="utf-8"?> |
初始视图,第一个场景,布局layout_first_scene.xml
:
1 | 复制代码<?xml version="1.0" encoding="utf-8"?> |
第二个场景,布局layout_second_scene.xml
:
1 | 复制代码<?xml version="1.0" encoding="utf-8"?> |
- 创建起始场景和结束场景。
1 | 复制代码val firstScene = Scene.getSceneForLayout(flContent, R.layout.layout_first_scene, this) |
默认情况下,过渡动画应用整个场景,如果场景某个View
不参加,可以通过过渡效果对象
的removeTarget()
方法进行移除。
1 | 复制代码Slide(Gravity.TOP).removeTarget(tvNoJoin) |
- 点击时,进行场景过渡。
1 | 复制代码tvText.setOnClickListener { |
TransitionManager.go()
第一个参数表示结束场景,第二个参数表示当前场景退出时过渡效果,当前场景就是初始场景。
「效果图:」
二)、系统自动创建Scene
这种情况,我们调用TransitionManager.beginDelayedTransition(sceneRoot)
函数时,系统会自动记录当前sceneRoot
节点下所有要进行动画的视图作为起始节点,下一帧中再次记录sceneRoot
子节点下所有 起始场景进行动画状态的视图作为结束场景。这种一般用来改变视图的属性,然后进行动画过渡,如View
的宽高。
「栗子」:
定义只有一个正方形的View
,通过改变正方形的宽高为原来的2倍,来看看动画效果。
activity_text.xml
布局文件,定义id
为sceneRoot
的根节点,也是场景的根节点。
1 | 复制代码<?xml version="1.0" encoding="utf-8"?> |
- 在
TestActivity
的OnCreate
方法中调用下面代码,将正方形的宽高设置200dp。
1 | 复制代码vSquare.setOnClickListener { |
「效果图:」
三、过渡动画效果
上面的动画效果,都是采用系统内置的,那具体有哪些动画效果,或支持自定义么?
过渡效果类都继承自Transition
类,Transition
类持有场景切动画的相关信息,子类的主要作用是捕获属性值(例如起始值和结束值)和如何演奏动画。从这里也可以看出,过渡动画也是属性动画的一个扩展与应用。
一)、内置过渡动画
系统支持将任何扩展Visibility
类的过渡作为进入或退出过渡,内置继承自Visibility
的类有Explode
、Slide
、Fade
;支持共享元素过渡的有:
changeScroll
为目标视图滑动添加动画效果changeBounds
为目标视图布局边界的变化添加动画效果changeClipBounds
为目标视图裁剪边界的变化添加动画效果changeTransform
为目标视图缩放和旋转方面的变化添加动画效果changeImageTransform
为目标图片尺寸和缩放方面的变化添加动画效果
代码示例:
1 | 复制代码if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
TransitionSet
对象是动画的合集,可以将多个过渡效果组织起来。
也可以通过XML布局来实现,在res/transition
文件夹创建``:
1 | 复制代码<?xml version="1.0" encoding="utf-8"?> |
transitionSet
和fade...
等等的一些属性和 Android矢量图动画:每人送一辆掘金牌小黄车文章 讲到的一些属性大同小异,这里不再复述。
代码调用:
1 | 复制代码if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
「效果图:」
当现有的过渡效果不满足日常需求时,可以通过继承Transition
,定制自己的动画特效。
二)、自定义过渡动画
子类继承Transition
类,并重写其三个方法。
1 | 复制代码class MyTransition : Transition() { |
captureStartValues()
与captureEndValues()
方法是必须实现的,捕获动画的起始值和结束值,而createAnimator()
方法,是用来创建自定义的动画。
参数TransitionValues
可以理解是用来存储View的一些属性值,参数sceneRoot
为根视图。
自定义过渡效果感兴趣可以参考:Android自定义Transition动画
好啦,过渡动画就讲到这里~
参考文章:
「 【码字不易,点个赞,日后好查看】」
本文使用 mdnice 排版**
本文转载自: 掘金