Android Transition(Android过渡动画)

参考链接:

       在Android 4.4 Transition 就已经引入了,但在Android 5.0(API 21)之后,Transition 被更多的应用起来。相对于View Animation或Property Animator,Transition动画更加具有特殊性,Transition可以看作对Property Animator的高度封装。不同于Animator,Transition动画具有视觉连续性的场景切换。

       为了对Transition有一个大概的了解,我们通过:Scene Transition(场景过渡动画)、Activity过渡动画、Shared Element Transition(共享元素过渡动画)这三个方面来做一个简单的了解。

一、Scene Transition(场景过渡动画)

       场景过渡动画是指以动画的形式实现View两个场景的切换(从一个场景切换到另一个场景)。而且在切换过程中通过Transition来设置不同的过渡动画效果。

       场景过渡动画中有两个特别关键概念:(场景),Transition(过渡)。

  • Scene:Scene代表一个场景。Scene保存了一个视图层级结构,包括它所有的views以及views的状态,通常由getSceneForLayout (ViewGroup sceneRoot,int layoutId,Context context)获取Scene实例。Transition框架可以实现在starting scene和ending scene之间执行动画。而且大多数情况下,我们不需要创建starting scene,因为starting scene通常由当前UI状态决定,我们只需要创建ending scene。
  • Transition:Transiton则是用来设置过渡动画效果用的。而且系统给提供了一些非常使用的Transtion动画效果,如下表所示:
系统Transition 解释
ChangeBounds 检测View的位置边界创建移动和缩放动画(关注布局边界的变化)
ChangeTransform 检测View的scale和rotation创建缩放和旋转动画(关注scale和ratation的变化)
ChangeClipBounds 检测View的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域setClipBound(Rect rect) 中的rect(关注的是setClipBounds(Rect rect)rect的变化)
ChangeImageTransform 检测ImageView的ScaleType,并创建相应动画(关注的是ImageView的scaleType)
Fade 根据View的visibility状态的的不同创建淡入淡动画,调整的是透明度(关注的是View的visibility的状态)
Slide 根据View的visibility状态的的不同创建滑动动画(关注的是View的visibility的状态)
Explode 根据View的visibility状态的的不同创建分解动画(关注的是View的visibility的状态)
AutoTransition 默认动画,ChangeBounds、Fade动画的集合

       要想实现一个场景过渡动画,至少需要一个transition实例和一个ending scene实例。并通过TransitionManager执行过渡动画。TransitionManager执行动画有两种方式:TransitionManager.go()、beginDelayedTransition()。下面简单来介绍下这两种开启场景动画的方式。

1.1、TransitionManager.go()开启场景动画

用说TransitionManager.go()实现场景动画之前,先上效果图


TransitionManager.go()实现场景动画

       TransitionManager.go()需要两个参数:第一个参数代表要过渡到的场景(end scene)、第二个参数过渡动画(transition 实例)。

       TransitionManager.go()启动动画的时候,场景一般通过布局文件给出。场景实例的获取则通过Scene.getSceneForLayout()来获取,需要三个参数:第一个参数代表场景所在的ViewGroup、第二个参数代表场景布局文件、第三个参数布局文件转换View所需要的Content。

TransitionManager.go()的场景是通过布局文件指定。

1.2、beginDelayedTransition()开启场景动画

用beginDelayedTransition()实现场景动画的效果图


beginDelayedTransition实现场景动画

      通过TransitionManager.beginDelayedTransition()也可以开启场景动画。在执行TransitionManager.beginDelayedTransition()之后,系统会保存一个当前视图树状态的场景,之后当我们改变了View的属性之后(比如重新设置了View位置、缩放、clipe等等)。在下一次绘制时,系统会自动对比之前保存的视图树,然后执行相应动画。

二、Activity过渡动画

2.1、API 21之前Activity过渡动画使用

      API21之前Activity过渡动画通过两种方式来实现:style主题里面统一设置、或者使用代码overridePendingTransition函数单独设置。(当然了代码设置的优先级要高于style主题里面统一设置)

  • style文件主题里面统一定义,全局为所有Activity设置过渡动画效果。
<item name="android:windowAnimationStyle">@style/Animation.Activity.Customer</item>
    <style name="Animation.Activity.Customer" parent="@android:style/Animation.Activity">
        <!-- 进入一个新的Activity的时候,A->B B进入动画 -->
        <item name="android:activityOpenEnterAnimation">@anim/right_in</item>
        <!-- 进入一个新的Activity的时候,A->B A退出动画 -->
        <item name="android:activityOpenExitAnimation">@anim/left_out</item>
        <!-- 退出一个Activity的时候,B返回到A A进入动画 -->
        <item name="android:activityCloseEnterAnimation">@anim/left_in</item>
        <!-- 退出一个Activity的时候,B返回到A B退出动画 -->
        <item name="android:activityCloseExitAnimation">@anim/right_out</item>
    </style>
  • 代码overridePendingTransition(enterAnim, exitAnim)函数设置。
    关键是理解两个参数所代表的是谁的动画。假设有这么一个实例A启动B,那么第一个参数就是B进入时候的动画、第二个参数就是A退出时候的动画。

关于overridePendingTransition函数,有一个需要注意的地方就是:它必需紧挨着startActivity()或者finish()或者onBackPressed()函数调用,否则不一定有效果。

2.2、API 21 之后Activity过渡动画使用

Activity过渡动画效果图


Activity过渡动画效果图

      But 现在你又可以获取一个新的技能了。在API 21之后google又推出了一种比之前效果更加赞的过渡动画。 通过ActivityOptions + Transition来实现Activity过渡动画。

      在讲使用ActivityOptions + Transition来实现Activity过渡动画之前先来了看下ActivityOptions里面几个函数代表啥意思。

/**
 * 和overridePendingTransition类似,设置跳转时候的进入动画和退出动画
 */
public static ActivityOptions makeCustomAnimation(Context context, int enterResId, int exitResId);

/**
 * 通过把要进入的Activity通过放大的效果过渡进去
 * 举一个简单的例子来理解source=view,startX=view.getWidth(),startY=view.getHeight(),startWidth=0,startHeight=0
 * 表明新的Activity从view的中心从无到有慢慢放大的过程
 */
public static ActivityOptions makeScaleUpAnimation(View source, int startX, int startY, int width, int height);

/**
 * 通过放大一个图片过渡到新的Activity
 */
public static ActivityOptions makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY);

/**
 * 场景动画,体现在两个Activity中的某些view协同去完成过渡动画效果,等下在例子中能更好的看到效果
 */
public static ActivityOptions makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName);

/**
 * 场景动画,同上是对多个View同时起作用
 */
public static ActivityOptions makeSceneTransitionAnimation(Activity activity, android.util.Pair<View, String>... sharedElements);

       上面给出了四类方式的使用,个人觉得makeCustomAnimation、makeScaleUpAnimation、makeThumbnailScaleUpAnimation这三种产生的效果还是走的API 21之前的效果,而且这三种效果好像和Transition动画没啥太多的联系。我们用的最多的还是makeSceneTransitionAnimation()函数,makeSceneTransitionAnimation效果才是和Transition动画效果密切相关的。所以我们重点来看makeSceneTransitionAnimation的使用。

       Transitionz过渡动画的使用也是有前提的:

  • API 21以上。当然你也可以不使用ActivityOptions,而是使用兼容类ActivityOptionsCompat来替换ActivityOptions。(兼容类给到我们的作用是保证程序在低版本运行不会挂掉,但是不能保证低版本也能起到响应的效果的)
  • 必须允许Activity可以使用Transition,要么在style里面设置(<item name="android:windowContentTransitions">true</item>),要么直接通过代码设置(getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);)。

       对于Transition Activity过渡动画的使用,我们简单的分为三个步骤:告诉系统以Transition的方式启动Activity、定义过渡动画、设置过渡动画。

2.2.1 告诉系统以Transition的方式启动Activity

       启动Activity的方式和之前不一样了,要告诉Activity以Transition的方式启动。先要得到ActivityOptions,而且调用的startActivity()还和之前不一样了,是调用两个参数的startActivity(),第二个参数是ActivityOptions.toBundler。(当然为了兼容之前的版本你也可以使用兼容类ActivityOptionsCompat, ActivityCompat)。如下述代码所示。

API 21 之后的代码

ActivityOptions compat = ActivityOptions.makeSceneTransitionAnimation(mActivity);
startActivity(new Intent(mContext, MakeSceneTransitionctivity.class), compat.toBundle());

使用ActivityOptionsCompat兼容API 21之前的代码

ActivityOptionsCompat compat = ActivityOptionsCompat.makeSceneTransitionAnimation(mActivity);
ActivityCompat.startActivity(mContext, new Intent(mContext, MakeSceneTransitionActivity.class), compat.toBundle());
2.2.2、定义过渡动画

系统给我们提供了三种Transition过渡动画,可以拿来直接使用。

系统默认动画 解释
分解(explode) 从场景中心移入或移出视图
滑动(slide) 从场景边缘移入或移出视图
淡入淡出(fade) 通过调整透明度在场景中增添或移除视图

       我们有两种方式来定义过渡动画:资源文件的方式、代码的方式。

  • 资源文件的方式定义过渡动画

       在 res/ 目录下创建transition 资源文件夹后,就可以在该文件夹下对每一种动画进行定义。

       一般我们可以写成如下的形式:

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000">
    <targets>
        <target android:excludeId="@android:id/statusBarBackground"/>
        <target android:excludeId="@android:id/navigationBarBackground"/>
    </targets>
</slide>

该xml定义了一个slide过渡动画,除了状态栏、导航栏以外其他所有的View进行滑入滑出动画效果。

       其中 <slide/> 是动画效果的名称,当然你也可以换系统其他两种过渡动画(explode、fade),或者高档一点直接自己自定义一个过渡动画。每一种动画效果,都有额外的属性。比如滑动 slide动画,可以使用 android:slideEdge="top" 设置滑动的方向;淡入淡出fade动画,可以使用 android:fadingMode="fade_in" 设置具体是淡入(fade_in)还是淡出(fade_out)等等。

       <targets/> 标签让我们可以更加灵活的控制动画。targets标签里面可以定义需要转场(或者不需要转场)的目标 id ,这个 id 可以是系统自带的,也可以是我们自己视图中的 view 的 id,每一个 id 需要单独在 <target/> 标签中定义,android:targetId 表示目标 id 需要进行过渡转换的 view,而 android:excludeId 表示我们不需要该 id 的 view 进行过渡转场。

       上面只是定义了一种过渡动画,如果我们想要在同一个过渡状态中实现两种或多种动画效果怎么办?也简单,将根标签替换为 <transitionSet/>,然后定义每一种动画效果,最后记得在根标签中使用 android:transitionOrdering 注明这几种动画的演示顺序:sequential 表示顺序执行、 together 表示同时执行。比如像下面的代码:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <slide android:slideEdge="bottom">
        <targets>
            <target android:targetId="@id/image_shared" />
        </targets>
    </slide>

    <fade>
        <targets>
            <target android:excludeId="@android:id/statusBarBackground" />
            <target android:excludeId="@android:id/navigationBarBackground" />
            <target android:excludeId="@id/image_shared" />
        </targets>
    </fade>
</transitionSet>

该xml定义了两个过渡动画slide、fade。顺序执行。slide 动画针对 id 为 image_shared 的 view 进行下面滑入,fade 动画将除了状态栏、导航栏和 id 为image_shared 以外的 view,进行淡入淡出。

  • 代码的方式定义过渡动画
           可以用资源文件来定义的对象,那咱们就一定可以用代码的方式来实现,我们用代码来实现上述多种动画效果对应的资源文件:
//      //资源文件指定过渡动画
//      getWindow().setEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.transition_target));
//代码制定过渡动画
TransitionSet transitionSet = new TransitionSet();
//slide动画
Slide slide = new Slide(Gravity.BOTTOM);
slide.addTarget(R.id.image_shared);
transitionSet.addTransition(slide);
//fade动画
Fade fade = new Fade();
fade.excludeTarget(android.R.id.statusBarBackground, true);
fade.excludeTarget(android.R.id.navigationBarBackground, true);
fade.excludeTarget(R.id.image_shared, true);
transitionSet.addTransition(fade);
transitionSet.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
getWindow().setEnterTransition(transitionSet);

代码和资源文件几个对应函数

代码对应 xml对应 解释
addTarget() android:targetId 指定目标View,让目标View参与动画
excludeTarget() android:excludeId 是否过滤指定View(是否参与动画)
excludeChildren() 是否过滤指定ViewGroup的子View(是否参与动画)

       Transition动画已经定义出来了,还不够,有的时候还得监听动画的启动过程。这个时候就是TransitionListener登场的时候了。TransitionListener的使用就太容易了,聪明的你一看TransitionListener里面各个函数的名字就能知道怎么使用了。

2.2.3、设置过渡动画

       告诉Activity以Transition的方式启动了,也定义好了过渡动画了。接下来就是去设置过渡动画了。Transition过渡动画的设置可以在style文件中统一设置也可以在代码中设置(代码中设置的优先级比style主题文件优先级高)。

代码指定 style主题指定 解释
getWindow().setEnterTransition() android:windowEnterTransition A启动B,B中的View进入场景的transition(代码所在位置B)
getWindow().setExitTransition() android:windowExitTransition A启动B,A中的View退出场景的transition(代码所在位置A)
getWindow().setReturnTransition() android:windowReturnTransition B返回A,B中的View退出场景的transition(代码所在位置B)
getWindow().setReenterTransition() android:windowReenterTransition B返回A,A中的View重新进入场景的transition(代码所在位置A)

       Activity过渡动画使用的时候有一个设置可以提高展示效果,可以通过在主题中设置windowAllowEnterTransitionOverlap、windowAllowReturnTransitionOverlap让动画过渡的更加自然。其中windowAllowEnterTransitionOverlap表示进入动画是否可以覆盖别的动画、windowAllowReturnTransitionOverlap表示返回动画是否可以覆盖别的动画。

三、Shared Element Transition(共享元素过渡动画)

简单共享元素效果图


共享元素简单实例

更新共享元素动画效果图


共享元素-图片浏览实例

3.1、共享元素基本概念

       你可能有发现前面讲的Activity过渡动画的实例中,ActivityOptions类里面makeSceneTransitionAnimation()函数后面的参数我们都没有传递进去,其实后面的参数是在使用共享元素的时候才会使用到的,接下来的实例这些个参数就会排上用场了。

       当你想要从一个Activity A转换到Activity B,而且他们共享一个元素(比如是一个view),在这种场景下,最好的用户体验可能就是将共享的元素直接变换到最终的地方和大小,这会使用户专注于应用而且有一种连贯性的表达。

       共享元素的连接点是所有共享元素View的transition name。它可以在layout文件里面设置(android:transitionName)、也可以代码设置(View.setTransitionName(ImageConstants.IMAGE_SOURCE[mCurrentPosition]);)。通过transtion name来判断哪两个元素是共享关系。

代码指定 style主题指定 解释
getWindow().setSharedElementEnterTransition() android:windowSharedElementEnterTransition A启动B,B中的View共享元素的transition(代码所在位置B)
getWindow().setSharedElementExitTransition() android:windowSharedElementExitTransition A启动B,A中的View共享元素transition(代码所在位置A)
getWindow().setSharedElementReturnTransition() android:windowSharedElementReturnTransition B返回A,B中的View共享元素的transition(代码所在位置B)
getWindow().setSharedElementReenterTransition() android:windowSharedElementReenterTransition B返回A,A中的View重新进入共享元素的transition(代码所在位置A)

       有了前面Activity过渡动的理解,共享元素动画在理解上就简单的多了。同Activity过渡动画一样,共享元素的动画也可以通过代码或者主题文件来设置(Fragment里面共享元素动画的设置可以类比Activity里面共享元素动画的设置),如下所示。

代码指定 style主题指定 解释
getWindow().setSharedElementEnterTransition() android:windowSharedElementEnterTransition A启动B,B中的View共享元素的transition(代码所在位置B)
getWindow().setSharedElementExitTransition() android:windowSharedElementExitTransition A启动B,A中的View共享元素transition(代码所在位置A)
getWindow().setSharedElementReturnTransition() android:windowSharedElementReturnTransition B返回A,B中的View共享元素的transition(代码所在位置B)
getWindow().setSharedElementReenterTransition() android:windowSharedElementReenterTransition B返回A,A中的View重新进入共享元素的transition(代码所在位置A)

       同样和Activity过渡动画一样的也可以给Transiton设置回调监听,比如监听Transition开始和结束等等。

3.1、更新共享元素对应关系

       有这种情况,比如我们第一个界面是一个列表(RecyclerView)每个item都是一个图片,点击进入另一个页面详情页面,详情页面呢有是ViewPager的形式。可以左右滑动。咱们有的时候就想,就算详情界面滑动到了其他照片,在返回到第一个页面的时候也想要有共享元素动画的效果。这个时候就得更新下共享元素的对应关系了。

       怎么更新呢,关键是看SharedElementCallback类的onMapSharedElements()函数,这个函数是用来装载共享元素的。比如有这么个情况,还是上面的例子A界面跳转到B界面。那么A界面在B返回的时候要更新下、B界面在返回之前要更新下。所以给A界面设置setExitSharedElementCallback(SharedElementCallback);、给B界面设置setEnterSharedElementCallback(SharedElementCallback)。其他更多的细节可以参考下实例代码中的实现。

setExitSharedElementCallback(SharedElementCallback)的SharedElementCallback里面的onMapSharedElements()函数在Activity exit和reenter时都会触发
setEnterSharedElementCallback(SharedElementCallback)的SharedElementCallback里面的onMapSharedElements()函数在Activity enter和return时都会触发。

3.2、延时共享元素动画

       有的时候又有这种情况,比如要展示一个网络图片,在网络图片获取到之前,这个共享元素的动画效果没啥作用。怎么的也得等我图片获取完成之后在开始共享元素的动画效果吧,这个时候延时元素动画就派上大用场了。

       postponeEnterTransition()函数用于延时动画,startPostponedEnterTransition()函数用于开始延时的共享动画。那咱们就可以这么干了,在Activity进入的时候先调用postponeEnterTransition()延时动画,在网络图片获取完成之后在调用startPostponedEnterTransition()开始动画。具体可以参考下实例代码中的实现。

       由于自己的能力有限上面也只是简单的介绍了下Transition基本的使用,好让自己入个门,Transition里面的东西远不止这些,等待大家去发掘。最后给出本文中所有涉及到的实例下载地址:Android Transiton 实例代码下载地址