Android 动画总结(8) - Activity 转场动画

Android 动画总结(1) - 概述
Android 动画总结(2) - 帧动画
Android 动画总结(3) - 补间动画
Android 动画总结(4) - 插值器
Android 动画总结(5) - 属性动画
Android 动画总结(6) - 估值器
Android 动画总结(7) - ViewGroup 子元素间的动画
Android 动画总结(9) - 过渡动画


对于 Activity,在 startActivity 或 finish 后调用

overridePendingTransition(R.anim.activity_in, R.anim.activity_out)

对于 Fragment:

supportFragmentManager.beginTransaction().setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)

ActivityOptions

从 Android 5.0 之后,可以用 ActivityOptions 来实现,ActivityOptionsCompat 是 support v4 的兼容实现,可以支持到 4.1(SDK 16),它有几个 make 开头的方法

  1. makeCustomAnimation(Context context, int enterResId, int exitResId)
  2. makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight)
  3. makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY)
  4. makeClipRevealAnimation(View source, int startX, int startY, int width, int height)
  5. makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)
  6. makeSceneTransitionAnimation(Activity activity, Pair... sharedElements)

makeCustomAnimation

custom.onClick {
    val compat = ActivityOptionsCompat.makeCustomAnimation(ctx, R.anim.activity_in, R.anim.activity_out)
    start(it!!, compat)
}

private fun start(view: View, compat: ActivityOptionsCompat) {
    val intent = Intent(ctx, OptionAfterActivity::class.java)
    intent.putExtra("from", (view as Button).text)
    // SDK 16 以下会忽略 compat.toBundle()
    ActivityCompat.startActivity(ctx, intent, compat.toBundle())
}

最普通的,效果和过去的 overridePendingTransition 一样。

OptionAfterActivity 的布局就只有一个 TextView




    


class OptionAfterActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_option_after)
        // 接收前一个方法传过来的名字显示出来
        tv.text = intent.extras["from"].toString()
    }

    override fun onBackPressed() {
        ActivityCompat.finishAfterTransition(this)
    }
}

发现 ActivityCompat.finishAfterTransition(this) 并没什么用。看源码

public static void finishAfterTransition(Activity activity) {
    if (Build.VERSION.SDK_INT >= 21) {
        activity.finishAfterTransition();
    } else {
        activity.finish();
    }
}

好吧,ActivityCompat.startActivitySDK >= 16 就生效,这退出的必须 >= 21 才行。再继续往下走

public void finishAfterTransition() {
    if (!mActivityTransitionState.startExitBackTransition(this)) {
        finish();
    }
}
public boolean startExitBackTransition(final Activity activity) {
    if (mEnteringNames == null || mCalledExitCoordinator != null) {
        return false;
    } else {
        if (!mHasExited) {
            // 能判断对这里才真正执行页面返回的动画
        }
        return true;
    }
}

而这个 mEnteringNames 的定义是:

The shared elements that the calling Activity has said that they transferred to this

很可惜,此时的 mEnteringNames 是 null,直接返回 false 调用 finish 了。下面会讲到什么是 shared elements

makeScaleUpAnimation

参照 Activity 上的某个 View,新 Activity 从指定大小放大到最大显示。

动画速度太快了,也没找到可以控制时间的地方,查了许多资料,包括看源码注释,其实还不是很明白这个 View 到底有没有放大。

使用的一个场景是可能点击一个小 View,然后第二个页面某个位置显示的放大版的,这样看着好像是点击放大到另一个页面似的。

scaleUp.onClick {
    val compat = ActivityOptionsCompat.makeScaleUpAnimation(image, image.width / 2, image.height / 2, 0, 0)
    start(it!!, compat)
}

看下它的 5 个参数:

  1. View source - 参照物
  2. int startX - 相对于 source,新 Activity 开始的位置
  3. int startY - 同 startX,只不过这是 Y 轴方向上的
  4. int startWidth - 第二个 Activity 在做放大动画前一开始的初始宽度
  5. int startHeight - 这当然就是初始高度了

makeThumbnailScaleUpAnimation

和 makeScaleUpAnimation 的区别是,不再是放大页面上的一个 View,而是指定一张图,在转场时,放大这张图片。不过也许是太快了,根本看不见,也不知理解是否正确。

thumbnailScaleUp.onClick {
    val compat = ActivityOptionsCompat.makeThumbnailScaleUpAnimation(text, BitmapFactory.decodeResource(resources, R.drawable.timg), 0, 0)
    start(it!!, compat)
}

参数:

  • View source - 图片放大的参照物
  • Bitmap thumbnail - 要放大的图片
  • int startX - 相对于参照物 source,这张图片开始放大的 X 轴位置
  • int startY - 图片开始放大相对于 source 的 Y 轴位置

makeClipRevealAnimation

说是一个点圆形渐变到全部显示,参数含义和 makeScaleUpAnimation 的一样。也是太快,没看出什么特别的。

clipReveal.onClick {
    val compat = ActivityOptionsCompat.makeClipRevealAnimation(text, text.getWidth() / 2, text.getHeight() / 2, 0, 0)
    start(it!!, compat)
}

makeSceneTransitionAnimation 单个 View

Scene 就是场景,两个 Activity 中的某些 View 协同完成过渡动画。

在两个 Activity 的布局文件中,要协同做动画的 View 要有一个属性 android:transitionName 并将值设为一样的。

第一个 Activity 的 xml


    ...
    
    ...

要跳转的新 Activity 的 xml


    
    

两个要协同的 ImageView 都有一句 android:transitionName="image_name"

sceneTransitionSingleView.onClick {
    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, image1, "image_name")
    start(it!!, compat)
}

参数:

  1. Activity activity - 当前所在 Activity
  2. View sharedElement - 要协同过渡的 View,就是共享元素
  3. String sharedElementName - 就是 xml 里定义的 transitionName

上面说到 ActivityCompat.finishAfterTransition(this) 没生效是在一处判断 shared elements 为 null 就返回执行普通的 finish 了,现在这里有 shared elements 了,反现返回原来页面确实也有动画效果了。


这种协同过渡用同类型甚至内容都差不多的 View 来做看着效果好,但就算让两个完全不一样的 View 做协同过渡,也是可以的,乳第一个 Activity 的一个 Button,点击就跳转到新 Activity,就让这个 Button 和新 Activity 里的一个 TextView 做过渡,也是可以,效果还好,就是返回时有个突变。


sceneTransitionDifferent.onClick {
    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, sceneTransitionDifferent, "transition")
    start(it!!, compat)
}

makeSceneTransitionAnimation 多个 View

修改 xml 再给其它 View 也加上 android:transitionName。


    ...
    
        
    
    ...


    
    
        
    

sceneTransitionMultiView.onClick {
    val textPair = android.support.v4.util.Pair(text, "text_name")
    val imagePair = android.support.v4.util.Pair(image1, "image_name")

    val compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this@OptionActivity, textPair, imagePair)
    start(it!!, compat)
}
activity_option1.gif

有共享元素时的动画效果

使用共享元素时 Activity 的效果

有三种:

  • explode - 爆裂,从场景中间移动视图进入或者退出屏幕
  • slide - 滑动,视图从场景的一个边缘进入或者退出屏幕
    • android:slideEdge 属性控制滑动方向,取值可以是 LEFT, TOP, RIGHT, BOTTOM, START, END
  • fade - 淡入淡出,从场景添加或者移除一个视图时改变他的透明

可以指定 target,只在某个 View 或排除某个 View 上做动画。

主题中可以配置

  • android:windowEnterTransition - 当 A start B 时,B 页面进入场景的 transition
  • android:windowExitTransition - 当 A start B 时,A 页面退出场景的 transition
  • android:windowReturnTransition - 当 B 返回 A 时,B 页面退出场景的 transition
  • android:windowReenterTransition - 当 B 返回 A 时,A 页面进入场景的 transition

如果不在主题配置,在 Activity 的代码设置,如 getWindow().setEnterTransition(new Explode());,那么

  • setEnterTransition - B 中设置
  • setExitTransition() - A 中设置
  • setReturnTransition() - B 中设置
  • setReenterTransition() - A 中设置

res/transiton 目录创建两个文件,可以定义其时间和插值器

transition_slide.xml



transition_set.xml



    
    

然后配置主题

@transition/transition_set
@transition/transition_slide

如果不配置主题,也可以在 B 页面(不是跳转前的 A)代码设置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_set)
    val slide = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_slide)
    window.enterTransition = set
    window.returnTransition = slide
}

也可以不用 xml,全部代码配置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    val set = TransitionSet()
    set.ordering = TransitionSet.ORDERING_TOGETHER

    val explode = Explode()
    explode.duration = 2000
    explode.interpolator = AccelerateInterpolator()

    val fade = Fade()
    fade.duration = 2000
    fade.interpolator = AccelerateInterpolator()

    set.addTransition(explode)
    set.addTransition(fade)

    window.enterTransition = set

    val slide = Slide()
    slide.duration = 2000
    slide.slideEdge = Gravity.RIGHT
    window.returnTransition = slide
}

共享元素间的效果

  • changeBounds - 改变目标视图的布局边界
    • android:resizeClip
  • changeClipBounds - 裁剪目标视图边界
  • changeTransform - 改变目标视图的缩放比例和旋转角度
    • android:reparent 是否追踪父容器的变化

    • android:reparentWithOverlay 默认 true。

      When the parent change doesn't use an overlay, it affects the transforms of the child

  • changeImageTransform - 改变目标图片的大小和缩放比例
  • ChangeScroll

res/transition 目录下创建 transition_elements_enter.xml



创建 transition_elements_return_set.xml



    
    

配置主题

@transition/transition_elements_enter
@transition/transition_elements_return_set

也可以在 Activity B 中代码设置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {   
    // val set = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_return_set)
    // val enter = TransitionInflater.from(ctx).inflateTransition(R.transition.transition_elements_enter)
    val set = TransitionSet()
    set.ordering = TransitionSet.ORDERING_TOGETHER
    
    val changeTransform = ChangeTransform()
    changeTransform.duration = 2000
    
    val changeClipBounds = ChangeClipBounds()
    changeClipBounds.duration = 2000
    
    set.addTransition(changeTransform)
    set.addTransition(changeClipBounds)
    //
    val enter = ChangeBounds()
    enter.duration = 2000
    enter.resizeClip = true
        
    window.sharedElementEnterTransition = enter
    window.sharedElementReturnTransition = set
}
activity_option2.gif

Overlap

To start an enter transition as soon as possible, use the Window.setAllowEnterTransitionOverlap() method on the called activity. This lets you have more dramatic enter transitions.

更鲜活的动画效果?没看出什么来。在 Activity B 中设置

window.allowEnterTransitionOverlap = true
window.allowReturnTransitionOverlap = true

或者配置主题

true
true

关于主题

看网上有些文章说必须在主题里设置 true 或者代码里在 setContentView() 之前 getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);,可我没设置什么问题都没有,都生效了啊。然后去看官方文档 https://developer.android.com/training/transitions/start-activity.html,又出现一个新的配置 true

First, enable window content transitions with the android:windowActivityTransitions attribute when you define a style that inherits from the material theme.

这意思是说用 material theme 时才需要设置这个属性吗?而我 Demo 用的主题是 Theme.AppCompat.Light.DarkActionBar。不太清楚到底是不是这个原因。

你可能感兴趣的:(Android 动画总结(8) - Activity 转场动画)