什么是过渡动画 过渡动画是Android 5.0 以后提供同的一套动画框架,那么什么是过渡动画呢?借助 官网 的解释
使用 过渡
为 布局变化
添加 动画效果
这个解释其实非常的准确,想一个使用场景,Activity/Fragment 切换到其他界面的时候,是否也是一样,布局发生了变化,只需要添加对应的效果,我们也可以做出,界面切换时候炫酷动画效果。当然界面切换只是使用场景之一。
小误区
这里需要说明一下,网上有很多文章提到一个概念:场景动画。在 官网 使用动画启动 Activity 中,也有提到 场景过渡
所谓的场景动画,无外乎也是 布局变化
。不要被太多的概念性名词,混淆了学习的脚步。
过渡动画有哪些种类 放上全家福给大家看一下
每一种大致的介绍也放在下面
方法 说明 TransitionSet 组合效果 AutoTransition 默认过渡动画,Fade out 渐隐, move 位移 和 resize 大小缩放,fade in 渐显 ,按顺序 ChangeBounds 检测view的位置边界创建移动和缩放动画 ChangeClipBounds 检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果 ChangeImageTransform 检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画 一般时候,我们都是和 ChangeBounds 一起使用,能够做出很漂亮的动画 ChangeTransform 检测view的scale和rotation创建缩放和旋转动画 ChangeScroll 改变滑动位置 Explode 分解效果 Fade android:fadingMode
淡入淡出 有 fade_in,fade_out ,fade_in_outSlide android:slideEdge
从哪边滑动出,有 left, top, right, bottom, start, end 模式
如何使用过渡动画 看一下官网 如何说的
为起始布局和结束布局创建一个 Scene 对象。然而,起始布局的场景通常是根据当前布局自动确定的。 创建一个 Transition 对象以定义所需的动画类型。 调用 TransitionManager.go(),然后系统会运行动画以交换布局。 并且还贴心的加上了一个图
下面以一个简单的示例作为例子
第一步 创建Scene
首先是创建两个不同的 场景 scene_changebounds_1 和 scene_changebounds_2 布局如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" > <View android:id ="@+id/tv_one" android:layout_width ="200dp" android:layout_height ="200dp" android:layout_marginTop ="200dp" android:background ="#FF6200EE" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toTopOf ="parent" /> <View android:id ="@+id/tv_two" android:layout_width ="200dp" android:layout_height ="200dp" android:layout_marginTop ="20dp" android:background ="#FFBB86FC" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/tv_one" /> </androidx.constraintlayout.widget.ConstraintLayout >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" android:layout_width ="match_parent" android:layout_height ="match_parent" > <View android:id ="@+id/tv_two" android:layout_width ="100dp" android:layout_height ="100dp" android:layout_marginTop ="200dp" android:background ="#FFBB86FC" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toTopOf ="parent" /> <View android:id ="@+id/tv_one" android:layout_width ="100dp" android:layout_height ="100dp" android:layout_marginTop ="20dp" android:background ="#FF6200EE" app:layout_constraintLeft_toLeftOf ="parent" app:layout_constraintRight_toRightOf ="parent" app:layout_constraintTop_toBottomOf ="@+id/tv_two" /> </androidx.constraintlayout.widget.ConstraintLayout >
将布局添加到 父类的容器当中 父类的容器布局如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ="http://schemas.android.com/apk/res/android" android:layout_width ="match_parent" android:layout_height ="match_parent" android:orientation ="vertical" > <FrameLayout android:id ="@+id/scene_parent" android:layout_width ="match_parent" android:layout_height ="0dp" android:layout_weight ="1" /> <com.google.android.material.button.MaterialButton android:id ="@+id/btn_switch" android:layout_width ="match_parent" android:layout_height ="wrap_content" android:layout_marginStart ="3dp" android:layout_marginEnd ="3dp" android:background ="#FF6200EE" android:text ="转换" android:textAllCaps ="false" android:textColor ="#ffffff" android:textSize ="15sp" /> </LinearLayout >
然后就是真的创建 Scene
1 2 3 4 5 6 7 private fun createSceneChangeBounds1 () : Scene { return Scene.getSceneForLayout(scene_parent, R.layout.scene_changebounds_1, this ) } private fun createSceneChangeBounds2 () : Scene { return Scene.getSceneForLayout(scene_parent, R.layout.scene_changebounds_2, this ) }
最后使用 TransitionManager.go
方法 切换场景
1 TransitionManager.go(scene2, ChangeBounds())
看一下最终的效果
其他效果就不一一展示。
TransitionManager 分析 接下来我们分析一下 TransitionManager 所有的方法 一张图告诉你
图片截取自 TransitionManager
使用场景动画做转场动画 在上面分析 我们知道,在5.0 极其以上的版本, 过渡动画 其实也可以放在 Activity/Fragment 转场时候。
在做这个之前 我们需要搞懂一个概念,界面的界面其实是可以分成4个动画
对应的 方法
Window.setEnterTransition() Window.setExitTransition() Window.setReenterTransition() Window.setReturnTransition() 下面就开始我们的动画效果
检查系统版本 为了兼容,5.0之前的使用方式会放在以后进行说明, 在官网 中,按照他的不走一步一步来呗
1 2 3 4 5 6 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { } else { }
启用窗口内容过渡 两种配置方式
style 样式中配置
1 2 3 4 <style name ="BaseAppTheme" parent ="android:Theme.Material" > <item name ="android:windowActivityTransitions" > true</item > </style >
代码中配置
1 2 3 4 5 6 7 override fun onCreate (savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS) setContentView(R.layout.activity_first) }
指定自定义过渡 同样的,指定自定义过渡 也有两种方式
xml 方式 在 res/transition
下 创建过渡动画的资源
1 2 3 4 5 <?xml version="1.0" encoding="utf-8"?> <slide xmlns:android ="http://schemas.android.com/apk/res/android" android:duration ="500" android:slideEdge ="left" android:interpolator ="@android:interpolator/accelerate_decelerate" />
然后使用 inflateTransition
方法 加载 资源
1 2 3 4 5 val transition = TransitionInflater.from(this ) .inflateTransition(R.transition.transition_slide) window.exitTransition = transition val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this ) ActivityCompat.startActivity(this , intent, compat.toBundle())
除了自定义 系统也为我们提供了一些资源,在 android.R.transition
下有一些常用的过渡效果
1 2 val transition = TransitionInflater.from(this ).inflateTransition(android.R.transition.slide)
代码方式 和xml 不同的是获取资源的部分替换了
1 2 3 4 5 6 7 val intent = Intent(this , SecondAct::class .java ) window .exitTransition = Slide().apply { duration = 1000 slideEdge = Gravity.LEFT } val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this ) ActivityCompat.startActivity(this , intent, compat.toBundle())
添加返回的动画 在SecondAct 中 设置 enterTransition
的动画
1 2 3 4 window.enterTransition = Slide().apply { duration = 1000 slideEdge = Gravity.RIGHT }
并且使用
1 ActivityCompat.finishAfterTransition(this )
替换 finish()
方法
最终实现效果如下
🔥 特别注意 如果没有指定returnTransition
和reenterTransition
,返回 FirstActivity 时会分别执行 反转
的进入和退出转换
ActivityOptionsCompat 浅析 先看一下他所有的方法
方法 说明 makeCustomAnimation 自定义动画,传入 res/anim
下的资源id makeScaleUpAnimation 某个固定的坐标以某个大小扩大至全屏 makeThumbnailScaleUpAnimation 将一个设置的缩略图 缩放到正在启动的新的Activity makeClipRevealAnimation 新的Activity从屏幕的一小块原始区域显示到其最终的完整表示 makeSceneTransitionAnimation 共享元素动画
makeCustomAnimation 1 2 3 public static ActivityOptions makeCustomAnimation (Context context, int enterResId, int exitResId)
自定义动画,将动画资源传入,从而实现动画效果
资源文件 放在 res/anim
下
enterResId 进场动画 (B Activity 出现的动画)传0 表示没有动画 exitResId 退场动画 (A Activity 消失的动画)传0 表示没有动画 先看一下效果
简单的示例
创建 enter_in 和 enter_out 动画
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="100%" android:toXDelta ="0" android:duration ="1000" > </translate >
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="0" android:toXDelta ="-100%" android:duration ="1000" > </translate >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 val intent = Intent(this , SecondAct::class .java ) //参数1 enterResId 进场动画 (B Activity 出现的动画) //参数2 exitResId 退场动画 (A Activity 消失的动画) val compat = ActivityOptions.makeCustomAnimation( this , R.anim.enter_in, R.anim.enter_out ) startActivity(intent, compat.toBundle()) val compat = ActivityOptionsCompat.makeCustomAnimation( this , R.anim.enter_in, R.anim.enter_out ) ActivityCompat.startActivity(this , intent, compat.toBundle()) val compat = ActivityOptionsCompat.makeCustomAnimation( this , R.anim.enter_in, R.anim.enter_out ) startActivity(intent, compat.toBundle())
ActivityOptions 和 ActivityOptionsCompat 区别
ActivityOptionsCompat && ActivityOptions
ActivityOptions
是帮助我们处理 转场动画的,
ActivityOptionsCompat
则是向后兼容的方式访问ActivityOptions
中的特性
小提示
看一下 传入的方法 enterResId exitResId 对比之下能够知道,这个方法只能设置 A—->B 的动画,不支持 B 返回 A 的动画
makeScaleUpAnimation 某个固定的坐标以某个大小扩大至全屏
1 2 public static ActivityOptions makeScaleUpAnimation (View source, int startX, int startY, int width, int height)
参数1 source 参照物 指定从哪个View的坐标开始放大 参数2 startX 指定以View的X坐标为放大中心的X坐标 参数3 startY 指定以View的Y坐标为放大中心的Y坐标 参数4 width 指定放大前新Activity是多宽 0 表示 从无到有 参数5 height 指定放大前新Activity是多高 0 表示 从无到有 下面的代码是 以 图片中心点为起点,开始进行过度动画,当返回FirstAct 的时候,依然有动画效果
1 2 3 4 5 6 7 8 9 val intent = Intent(this , SecondAct::class .java ) val compat = ActivityOptions.makeScaleUpAnimation( image, image.width / 2 , image.height / 2 , 0 , 0 ) ActivityCompat.startActivity(this , intent, compat.toBundle())
makeThumbnailScaleUpAnimation 将一个设置的缩略图 缩放到正在启动的新的Activity
1 2 public static ActivityOptionsCompat makeThumbnailScaleUpAnimation (@NonNull View source, @NonNull Bitmap thumbnail, int startX, int startY)
参数1 source 传入View 用来确定第二个界面的坐标,以View 左上角作为中心点 参数1 thumbnail 缩略图 参数1 startX 相对于参数1 的中心点坐标 x 轴的距离 参数1 startY 相对于参数1 的中心点坐标 y 轴的距离 1 2 3 4 5 6 7 8 val intent = Intent(this , SecondAct::class .java ) val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation( image, BitmapFactory.decodeResource(resources, R.drawable.nm), 0 , 0 ) ActivityCompat.startActivity(this , intent, compat.toBundle())
效果需要拿出8倍镜才能看到。就不放gif了
makeClipRevealAnimation 官网的解释是 新的Activity从屏幕的一小块原始区域显示到其最终的完整表示
1 2 public static ActivityOptionsCompat makeClipRevealAnimation (@NonNull View source, int startX, int startY, int width, int height)
source View:新活动从中进行动画处理的视图。这定义了startX和startY的坐标空间。 startX int: 新活动相对于source的x起始位置。 startY int: 活动的y起始位置,相对于source。 width int: 新活动的初始宽度。 height int: 新活动的初始高度。 这个比较迷惑,不知道那种场景适合用他
共享元素动画 上面他的几个方法,平常开发中用的不多。但是下面的几个方法,共享元素的,实际开发中,用的还是比较多的。
单个共享元素
1 2 public static ActivityOptionsCompat makeSceneTransitionAnimation (@NonNull Activity activity, @NonNull View sharedElement, @NonNull String sharedElementName)
多个共享元素
1 2 public static ActivityOptionsCompat makeSceneTransitionAnimation (@NonNull Activity activity, Pair<View, String>... sharedElements)
单个共享元素核心 在于 sharedElementName
在 两个不同界面 对需要共享的view 设置他的 sharedElementName
属性,当 sharedElementName
相同时候,触发共享元素动画
1 2 3 4 5 6 7 val intent = Intent(this , SecondAct::class .java ) val compat = ActivityOptionsCompat.makeSceneTransitionAnimation( this , image, "image_1_test" ) ActivityCompat.startActivity(this , intent, compat.toBundle())
而对于多个共享元素,就需要创建 pair 即可 ,同样的需要在 第二个界面配置相同的 sharedElementName
属性
1 2 3 4 5 6 val intent = Intent(this , SecondAct::class .java ) val pair1 = androidx.core.util.Pair.create(image as View , "image_1_test" )val pair2 = androidx.core.util.Pair.create(btn_intent as View, "btn_intent" ) val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this , pair1,pair2) ActivityCompat.startActivity(this , intent, compat.toBundle())
5.0 以前设置转场动画 针对于 5.0 以前的 设备 可以使用 overridePendingTransition 实现 转场动画
1 2 3 4 startActivity(intent) overridePendingTransition(R.anim.enter_in, R.anim.enter_out)
1 2 3 4 finish() overridePendingTransition(R.anim.exit_in, R.anim.exit_out)
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="100%" android:toXDelta ="0" android:duration ="3000" > </translate >
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="0" android:toXDelta ="-100%" android:duration ="3000" > </translate >
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="-100%" android:toXDelta ="0" android:duration ="3000" > </translate >
1 2 3 4 5 6 <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android ="http://schemas.android.com/apk/res/android" android:fromXDelta ="0" android:toXDelta ="100%" android:duration ="3000" > </translate >
需要在 startActivity
或者 finish
之后立马执行