Android Transition

1.ActivityOptions

在Android2.0之前要实现Activity之间的过渡动画,使用的是overridePendingTransition。google在Android5.0提出了MD的设计风格,并扩充了Activity的转场动画,利用ActivityOptions和ActivityOptionsCompat可以实现新风格的转场动画。ActivityOptionsCompat是ActivityOptions的兼容包,虽然是兼容包但内部有的动画风格在5.0以下的版本依然不支持,只不过坚持在低版本使用ActivityOptionsCompat的动画不会出现crash。

ActivityOptionsCompat的主要作用是生成startActivity( Intent, Bundle) 所需要的Bundle,再配合其他方法实现转场动画。

ActivityOptionsCompat是一个静态类,提供了几个方法:

①ActivityOptionsCompat.makeCustomAnimation(Context context, int enterResId, int exitResId)

用户自定义动画,指定进入和退出动画。

enterResId:Activity进入动画资源id

exitResId:Activity退出动画资源id

比如,Activity1启动Activity2:

ActivityOptionsCompat compat = ActivityOptionsCompat.makeCustomAnimation(this,R.anim.translate_in, R.anim.translate_none);

ActivityCompat.startActivity(this, new Intent(this, Activity2.class), compat.toBundle());

在Activity2退出的时候调用ActivityCompat.finishAfterTransition(this)进行退出动画。

②ActivityOptionsCompat.makeScaleUpAnimation(View source,int startX, int startY, int startWidth, int startHeight)

放大一个view,然后显示新的activity,效果就是不断的放大一个view,进而进行activity的过度。

source:放大的view

startX,startY:从哪里开始缩放,以source为原点

width,height:新的activity从多大开始放大,如果是0,0则表示从最小开始。

ActivityOptions options = ActivityOptions.makeScaleUpAnimation(

 view, 0, 0, //拉伸开始的坐标

 view.getMeasuredWidth(), view.getMeasuredHeight()); // 初始的宽高

startActivity(intent, options.toBundle());

c7ee527b589e4fc1a54880156346f509.gif

 ③ActivityOptionsCompat.makeThumbnailScaleUpAnimation(View source,Bitmap thumbnail, int startX, int startY)

放大一张图片,然后打开activity

source:参考原点

thumbnail:要放大的图片

startX,startY:从哪里开始放大,以source为坐标原点

Bitmap bitmap = view.getDrawingCache();

ActivityOptions options = ActivityOptions.makeThumbnailScaleUpAnimation(view, bitmap, 0, 0);

startActivity(intent, options.toBundle());

bcfa8b44c8014cdf96f7699210f56011.gif

 ④ActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)

两个activity中的某些view协同完成过渡动画,即共享元素过渡动画。

⑤ActivityOptionsCompat.makeSceneTransitionAnimation(Activity activity,Pair<View, String>… sharedElements)

多个共享元素过渡动画。。

Pair<View, String> imagePair = Pair.create(mImageView,"share1");

Pair<View, String> textPair = Pair.create(mTextView,"share2");

ActivityOptionsCompat compat = ActivityOptionsCompat.makeSceneTransitionAnimation(this, imagePair, textPair);

ActivityCompat.startActivity(this, new Intent(this, Activity2.class),compat.toBundle());

ActivityOptionsCompat提供了这5种过渡方式,使用时需要在theme指定:

<item name="android:windowContentTransitions">true 

</item>

除了第一个指定了动画外,其他的好像没有机会去指定动画,如果想换一种动画呢?android提供了几种预设的动画方式,它们分别是change_bounds、change_clip_bounds、change_transition、change_image_transition、change_scroll。

首先需要在res目录下新建一个目录transition,然后在transition目录里新建xml文件,如果是change_bounds,则代码这么写:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <changeBounds />

</transitionSet>

如果是change_clip_bounds,则代码这么写:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <changeClipBounds />

</transitionSet>

其他的类似,那怎么指定使用这些xml呢?还需要在styles.xml文件中配置theme:

<resources>

    <style name="AppTheme" parent="Theme.AppCompat.Light">

        <item name="android:windowContentTransitions">true</item>

       //指定进入和退出的动画可以重叠

        <item name="android:windowAllowEnterTransitionOverlap">true</item>

        <item name="android:windowAllowReturnTransitionOverlap">true</item>

        //指定普通进入和退出的transition,这里使用了android预设的两个transition

        <item name="android:windowEnterTransition">@android:transition/slide_bottom</item>

        <item name="android:windowExitTransition">@android:transition/slide_bottom</item>

        //指定使用sharedElement时的进入和退出动画,这里使用了自定义的transition

        <item name="android:windowSharedElementEnterTransition">@transition/change_bounds</item>

        <item name="android:windowSharedElementExitTransition">@transition/change_bounds</item>

    </style>

</resources>

除此之外,还可以指定transition动画的速度插值器和动画时长:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <changeBounds

        android:interpolator="@android:interpolator/ accelerate_decelerate"

        android:duration="500"/>

</transitionSet>

2.Transition

Transition的意思是过渡,这里指过渡动画。

Transition内部使用了属性动画来实现,所以它可以认为是属性动画的封装。Transition有两个核心概念为:场景(scenes)和变换(transitions),场景是UI当前状态,变换则定义了在不同场景之间动画变化的过程。所以Transition主要负责两个方面,一是保存开始和结束场景的两种状态,二是在两种状态之间创建动画。由于场景记录了内部所有View的开始和结束状态,所以Transition动画更具连贯性。而TransitionManager负责执行动画的任务。

使用Transition需要注意:

①使用Transition的activity需要启用transition ,有两种方法:

1)通过代码启用

在setContentView之前调用:

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

2)通过设置主题xml方式启用

<item name="android:windowContentTransitions">true</item>

②在Activity中设置进入、退出Transition动画。Material主题默认将exit的transition设置成null,而enter的transition设置成Fade ,如果reenter 或return transition没有明确设置,则将用exit 和enter的transition替代。

③在Fragment中使用Transition时,在其FragmentTransaction执行added、removed、attached、detached、shown、hidden时触发动画。并且在Fragment commit之前,共享元素需要通过调用addSharedElement(View, String) 方法来成为FragmentTransaction的一部分。

Android提供了三种transition类型:

进入动画:进入一个Activity的过渡动画,即Activity如何进入屏幕。

退出动画:退出一个Activity的过渡动画,即Activity如何退出屏幕。

共享元素动画:利用共享元素实现Activity的跳转动画。

进入动画和退出动画合称Content Transition(内容变换动画),所以Transition分为不带共享元素的Content Transition和带共享元素的Shared Element Transition。

因此Transition一共有三个部分:场景Scene、Content Transition和Shared Element Transition。

3.场景scenes

场景过渡动画就是实现View从一种状态变化到另外一种状态。

Scene代表一个场景,它内部保存了一个完整的视图结构,从根ViewGroup到所有子view,还有它们的所有状态信息,所以Scene可以看作是一个设置了不同属性特征的ViewGroup,它存储着一个根View下的各种View的属性。

一般利用getSceneForLayout()函数生成Scene场景。

Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)

sceneRoot:根ViewGroup,也就是scene发生改变和动画执行的位置。

layoutId:view的布局文件资源id,代表一个场景,也就是上边所说的根View。

举个例子:

private Scene scene1;

private Scene scene2;

private boolean isScene2;

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_scene);

    initScene();

}

private void initScene() {

    ViewGroup sceneRoot= (ViewGroup) findViewById(R.id.scene_root);     

    scene1=Scene.getSceneForLayout( sceneRoot,R.layout.scene_1,this);

    scene2=Scene.getSceneForLayout( sceneRoot,R.layout.scene_2,this);

    TransitionManager.go(scene1);

}

//scene1和scene2相互切换,播放动画 

public void change(View view){

    TransitionManager.go(isScene2?scene1:scene2, new ChangeBounds());

    isScene2 = !isScene2;

}

scene1:

979d2b0c0ee6496fb586ead818f27fe2.webp

 scene2:

e88c2c3dd6074c50b63ae1779777c390.webp

注意,两个scene布局中1和4,2和3除了图片位置大小不一样,其id是一样的,可以当成一个view,因为分析比较起始scene的不同创建动画是针对于同一个view的。

通过TransitionManager.go()触发动画,在进入Activity的时候,手动将start scene通过TransitionManager.go(scene1)设置为scene1。点击button通过TransitionManager.go(scene2,new ChangeBounds())切换到end scene状态:scene2。Transition 框架通过ChangeBounds类分析start scene和end scene的不同创建并播放动画。由于ChangeBounds类是分析比较两个scene中view的位置边界创建移动和缩放动画。发现从scene1->scene2,其实是1->4,2->3。于是就执行相应的动画,就会出现1移动到4,同时2移动到3的效果。

8f0670632f0b4ee1975f2e3c47c97348.gif

 TransitionManager.go(scene1)像这样不指定动画,则默认动画是AutoTransition类。它其实是一个动画集合,查看源码可知其实是动画集合中添加了Fade和ChangeBounds类。

private void init() {

    setOrdering(ORDERING_SEQUENTIAL);

    addTransition(new Fade(Fade.OUT)).

            addTransition(new ChangeBounds()).

            addTransition(new Fade(Fade.IN));

}

同样的效果也可以通过xml文件创建,只需要两步,第一步在res/transition创建一个xml文件:

<?xml version="1.0" encoding="utf-8"?>

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <changeBounds />

    <fade />

</transitionSet>

第二步在代码中调用:

Transition sets = TransitionInflater.from( this ).inflateTransition(R.transition.changebounds_and_fade);

最后补充一点,TransitionManager.go(scene2)其实是调用scene1的exit()以及scene2的enter()。而它们又分别会触发scene1.setExitAction()和scene2.setEnterAction()。可以在这两个方法中定制一些特别的效果。

TransitionManager.go()一直都是根据xml文件创造start scene和end scene,这样未免有些麻烦。而beginDelayedTransition()原理则是通过代码改变view的属性,然后通过ChangeBounds等类分析start scene和end Scene不同来创建动画。

@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_delayed);

    initView();

}

@Override

public void onClick(View v) {

    //start scene 是当前的scene

    TransitionManager.beginDelayedTransition( sceneRoot,TransitionInflater.from(this).inflateTransition(R.transition.explode_and_changebounds));

    //next scene 此时通过代码已改变了scene statue

  changeScene(v);

}

private void changeScene(View view) {

    changeSize(view);

    changeVisibility(cuteboy,cutegirl,hxy,lly);

    view.setVisibility(View.VISIBLE);

}

// view的宽高1.5倍和原尺寸大小切换 * 配合ChangeBounds实现缩放效果

private void changeSize(View view) {

    isImageBigger=!isImageBigger;

    ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

    if(isImageBigger){

        layoutParams.width=(int)(1.5*primarySize);

        layoutParams.height=(int)(1.5*primarySize);

    }else {

        layoutParams.width=primarySize;

        layoutParams.height=primarySize;

    }

    view.setLayoutParams(layoutParams);

}

// VISIBLE和INVISIBLE状态切换

private void changeVisibility(View ...views){

    for (View view:views){

        view.setVisibility(view.getVisibility() == View.VISIBLE?View.INVISIBLE:View.VISIBLE);

    }

}

当触发点击事件时候,此时记录下当前scene status,然后改变被点击view的尺寸,并改变其他view的visibility,再记录下改变后的scene status。而beginDelayedTransition()第二个参数传的是一个ChangeBounds和Explode动画集合,所以这个集合中改变尺寸的执行缩放动画,改变visibility的执行爆炸效果。

363f9b31f57849fbb5ec63484a70076b.gif

4.内容变换动画

内容变换动画决定了非共享元素在activity或fragment切换期间如何进入或者退出场景。可以使用代码或XML方式实现。

先解释下几个重要概念:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

 ①A.exitTransition(transition)

Transition框架会先遍历A界面确定要执行动画的view(非共享元素view),执行A.exitTransition()前A界面会获取界面的start scene(view 处于VISIBLE状态),然后将所有的要执行动画的view设置为INVISIBLE,并获取此时的end scene(view 处于INVISIBLE状态)。根据transition分析差异的不同创建执行动画。

②B.enterTransition()

Transition框架会先遍历B界面,确定要执行动画的view,设置为INVISIBLE。执行B.enterTransition()前获取此时的start scene(view 处于INVISIBLE状态),然后将所有的要执行动画的view设置为VISIBLE,并获取此时的end scene(view 处于VISIBLE状态)。根据transition分析差异的不同创建执行动画。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

可以看到,界面切换动画是建立在visibility的改变的基础上的,所以getWindow().setEnterTransition(transition);中的参数一般传的是Fade,Slide,Explode类的实例(因为这三个类是通过分析visibility不同创建动画的)。

设置Content Transition的函数:

①setEnterTransition() - ActivityA跳转到ActivityB时,ActivityB中的View进入场景的动画。

②setExitTransition() - ActivityA跳转到ActivityB时,ActivityA中的View退出场景的动画。

③setReturnTransition() -从ActivityB返回ActivityA时,ActivityB中的View退出场景的动画。

④setReenterTransition() - 从ActivityB返回Activity A时,ActivityA中的View进入场景的动画。

系统提供了几种动画效果(用于content Transition):

①explode(分解):从场景的中心移入或移出

②slide(滑动):从屏幕边缘移入或移出,可以设置方向

setSlideEdge(int slideEdge) 设置从哪个边出现或隐藏,取值为Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END。

③fade(淡出):调整透明度产生渐变效果

通过xml方式实现Content Transition:

①在res下新建目录transition,在该目录下新建explode.xml文件:

<?xml version="1.0" encoding="utf-8"?>

<transitionSet xmlns:android="http://schemas. android.com/apk/res/android">

    <explode

        android:duration="500"   

        android:interpolator="@android:interpolator /accelerate_decelerate"/>

</transitionSet>

②在Style中设置:

<item name="android:windowContentTransitions">true</item>

<item name="android:windowEnterTransition">@transition/explode</item>

③在Java文件中调用

ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(Main1Activity.this);

Intent intent = new Intent(Main1Activity.this, Main2Activity.class);

startActivity(intent,activityOptionsCompat.toBundle());

④退出时调用:

@Override

public void onBackPressed() {

    super.onBackPressed();

    ActivityCompat.finishAfterTransition(this);

}

代码方式实现Content Transition:

@Override

protected void onCreate(Bundle savedInstanceState) { 

    //在super.onCreate之前设置

    getWindow().requestFeature(Window. FEATURE_CONTENT_TRANSITIONS);

    //getWindow().setEnterTransition(new Explode());

    Transition transition = TransitionInflater.from(Main1Activity.this).inflateTransition(R.transition.explode);

    getWindow().setReturnTransition(transition);  

    super.onCreate(savedInstanceState);

退出时调用也要使用finishAfterTransition()方法。

设置相应的A离开/B进入/B离开/A重新进入动画。

//A 不设置默认为null

getWindow().setExitTransition(transition);

//B 不设置默认为Fade

getWindow().setEnterTransition(transition);

//B 不设置默认为EnterTransition

getWindow().setReturnTransition(transition);

//A 不设置默认为ExitTransition

getWindow().setReenterTransition(transition);

注意:你可能会发现,在界面切换的时候,A退出时,过了一小会,B就进入了(不给A完全展示ExitTransition)。如果你想等A完全退出后B再进入可以通过设置setAllowEnterTransitionOverlap(false)(默认是true),同样可以在xml中设置:

<item name="android:windowAllowEnterTransitionOverlap">false</item>

<item name="android:windowAllowReturnTransitionOverlap">false</item>

还会发现一个问题:Activity A的状态栏也跟着动画一起变化了。其实从原理上来解释,Activity的切换动画针对的是整个界面的view的visibility,而有没有什么方法能让Transition框架只关注某一个view或者不关注某个view呢。当然,transition.addTarget()和transition.excludeTarget()可以分别实现上述功能。而且也可以在xml设置该属性,那现在要做的是将statusBar排除掉,可以在slide.xml这样写:

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <slide android:duration="1000">

        <targets >

            <target android:excludeId="@android:id/statusBarBackground"/>  //表示除了状态栏

            <target android:targetId="@android:id/statusBarBackground"/>  //表示只针对状态栏

       </slide>

</transitionSet>

4.共享元素动画

ShareElement指两个Activity或Fragment之间切换时的共享元素,它可以实现两个Activity之间很自然的切换。共享元素动画需要在共享的元素上添加android:transitionName设置transitionName,它的值随意,但两个activity要保持一致。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5a2f6Iqz6Iqz,size_20,color_FFFFFF,t_70,g_se,x_16

界面切换中往往Content Transition和Shared Element Transition是同时存在的。

要实现这样顺滑的切换效果,需要用到以下几个核心函数:

①setSharedElementEnterTransition();

ActivityA 跳转到 ActivityB,ActivityB进入动画

②setSharedElementExitTransition();

ActivityA 跳转到 ActivityB,ActivityA退出动画

③setSharedElementReenterTransition();

从ActivityB 返回Activity A时,ActivityA进入场景的动画。

④setSharedElementReturnTransition();

从ActivityB 返回Activity A时,ActivityB退出场景的动画。

xml中对应的设置:

<item name="android:windowSharedElementEnterTransition"></item>

<item name="android:windowSharedElementExitTransition"></item>

<item name="android:windowSharedElementReenterTransition"></item>

<item name="android:windowSharedElementReturnTransition"></item>

setSharedElementEnterTransition()/setSharedElementReturnTransition()不设置的话默认是@android:transition/move动画。而setExitTransition()和setEnterTransition()默认为null和Fade.

系统默认提供的Transition动画只有以下几个:

①ChangeBounds:改变目标视图的布局边界,也就是改变view的宽高或者位置。

捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

②ChangeClipBounds:裁剪目标视图边界,view的裁剪区域边界。

捕获共享元素clip bounds,然后播放clip bounds变化动画,view的裁剪区域边界。

③ChangeTransform:改变目标的缩放比例和旋转角度,对view进行缩放,旋转操作。

捕获共享元素的缩放与旋转属性 ,然后播放缩放与旋转属性变化动画。

④ChangeImageTransform:改变目标图片的大小和缩放比例,也就是改变图片的ScaleType。

捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小、形状或者ImageView.ScaleType 属性平滑过度。

其实Shared Element Transition原理和Content Transition类似都是根据始末scene status的不同创建动画。不同的是Content Transition是通过改变view的visibility来改变scene状态,从而进一步创建动画;而Shared Element Transition是分析A B界面共享view的尺寸、位置、样式的不同创建动画化的。所以前者通常设置Fade等Transition后者通常设置ChangeBounds等Transition。

overlay效果开启方法:

getWindow().setSharedElementsUseOverlay(true);

1)xml实现

①在res/transition目录下新建changebounds.xml文件

<?xml version="1.0" encoding="utf-8"?>

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">

    <changeBounds

        android:duration="1000"

        android:interpolator="@android:interpolator /accelerate_decelerate" />

</transitionSet>

②在style中设置

<item name="android:windowContentTransitions">true</item>

<item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>

③在java代码中调用

Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(Main1Activity.this,gongxiang,"shareElement").toBundle();

Intent intent = new Intent(Main1Activity.this, Main2Activity.class);

startActivity(intent,bundle);

④在两个Activity的布局文件里里设置共享元素

<ImageView

    android:id="@+id/gongxiang"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:layout_marginTop="400px"

    android:src="@drawable/image1"

    android:transitionName="shareElement"/>

<ImageView

    android:id="@+id/toptop"

    android:layout_width="wrap_content"

    android:layout_height="wrap_content"

    android:src="@drawable/image1"

    android:scaleType="fitCenter"

    android:transitionName="shareElement"/>

Main1Activity和Main2Activity 共享imageView。

2)代码实现

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

ChangeBounds changeBounds = new ChangeBounds();

changeBounds.setDuration(1000);

getWindow().setSharedElementEnterTransition(changeBounds);

其实当进入到ActivityB的时候,应该调用setEnterSharedElementCallback回调方法,当使用简单的共享元素的时候,可以不写这句话,framework已经实现好了。这里写这个是因为有时(滑动页面之后)要返回非进入的共享元素,要去回调过去。当然callback也是可以选择实现方法的,一般没有什么效果的只要实现onMapSharedElements就可以了。再来说一下常用的实现方法:

onMapSharedElements 装载共享元素
onSharedElementStart 共享元素开始时候回调,一般是进入的时候使用
onSharedElementEnd 共享元素结束的时候回调,一般是退出的时候使用

每次进入和退出都会回调SharedElementCallback,所以一般来说在onSharedElementStart和onSharedElementEnd里面要去判断是返回还是进入操作。当进入时,执行onSharedElementStart,返回时调用onSharedElementEnd。如何判断是返回还是进入呢,可以在finishAfterTransition方法中进行判断,当执行返回操作时,一般是back键时,判断是返回操作,然后去执行onSharedElementEnd里面的方法,否则去执行onSharedElementStart里面的方法。同时,要去返回一些共享元素的信息也可以在这里回调回去。

当返回到activityA的时候,要去获取activityB返回的信息,可以在onActivityReenter(int requestCode, Intent data)方法里面获取,比如获取到返回的position信息,然后去把RecyclerView滑动到响应的位置。同样的,activityA是要设置setExitSharedElementCallback的。这样才能响应到返回的共享元素。

5.自定义Transition

共享元素Transition动画其实就是拿着第一页某个view的信息去第二页的某个view上做的动画,这样在视觉上就会产生一个渐变的错觉。也就是说,整个ShareElement动画过程中,做动画的都只有Activity B里的ShareElement,Activity A里的ShareElement唯一的作用就是提供位置大小等参数,然后这些参数在setSharedElementState()函数里被设置到Activity B里对应的View上。

为了实现我们需要的动画效果,有时候需要自定义Transition。 自定义Transition需要继承Transition类,并有两个抽象方法必须重写,除了这两个必须要重写的方法,还要重写一个createAnimator方法来自定义动画。

public class MyTransition extends Transition {

    @Override

    public void captureStartValues( TransitionValues transitionValues) {

    }

    @Override

    public void captureEndValues( TransitionValues transitionValues) {

    }

    @Override

    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {

    }

}

captureStartValues方法用来收集动画的开始信息了,captureEndValues方法用来收集动画结束的信息。然后就要通过createAnimator方法来创建一个Animator供系统调用了。

这三个方法都有一个参数TransitionValues,这个类其实很简单,只有两个成员变量:view和values。 view指要从哪个view上收集信息,values用来存放收集到的信息的。比如: 在captureStartValues里,transitionValues.view指的就是在开始动画的界面上的那个view,在captureEndValues指的就是在目标界面上的那个view。

现在来完成一个进入消息内容的动画效果,先来看看效果:

74e8538059964794aa48eb14a96ddeab.gif

 仔细观察效果, 可以找到两处动画:

①单行内容从它在列表中的位置移动到界面的最上面。

②消息的内容由单行逐渐展开。

这两个动画是顺序执行的。

需要收集的信息有view在界面的位置和view的高度信息。先来定义一下需要收集的信息:

public class MyTransition extends Transition {

    private static final String TOP = "top";

    private static final String HEIGHT = "height";

    // ...

}

然后开始收集动画开始需要的信息:

public class MyTransition extends Transition {

    private static final String TOP = "top";

    private static final String HEIGHT = "height";

    @Override

    public void captureStartValues( TransitionValues transitionValues) {

        View view = transitionValues.view;

        Rect rect = new Rect();

        view.getHitRect(rect);

        transitionValues.values.put(TOP, rect.top);

        transitionValues.values.put(HEIGHT, view.getHeight());

        Log.d("test", "start:" + rect.top + ";" + view.getHeight());

    }

}

首先通过transitionValues.view拿到要收集信息的目标view,然后通过getHitRect拿到它在ListView中的上下左右信息,最后通过transitionValues.values.put(TOP, rect.top)保存它距离父布局上面的距离,通过transitionValues.values.put(HEIGHT, view.getHeight())保存动画初始的高度。

接下来收集动画结束的信息:

@Override

public void captureEndValues( TransitionValues transitionValues) {

    transitionValues.values.put(TOP, 0);

    transitionValues.values.put(HEIGHT, transitionValues.view.getHeight());

    Log.d("test", "end:" + 0 + ";" + transitionValues.view.getHeight());

}

动画结束后,view距离上面的距离应该是0,不过需要注意的是captureStartValues方法里的transitionValues.view是页面跳转开始那个界面上的view,而captureEndValues方法里的transitionValues.view是跳转目标上的view。所以这两个方法里获取到的view的高度肯定是不一样的。

在完成信息收集之后,就可以写动画效果了:

@Override

public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues) {

    if (startValues == null || endValues == null) {

        return null;

    }

    final View endView = endValues.view;

    final int startTop = (int) startValues.values.get(TOP);

    final int startHeight = (int) startValues.values.get(HEIGHT);

    final int endTop = (int) endValues.values.get(TOP);

    final int endHeight = (int) endValues.values.get(HEIGHT);

    ViewCompat.setTranslationY(endView, startTop);

    endView.getLayoutParams().height = startHeight;

    endView.requestLayout();

      ValueAnimator positionAnimator = ValueAnimator.ofInt(startTop, endTop);

      if (mPositionDuration > 0) {

           positionAnimator.setDuration( mPositionDuration);

        }

      positionAnimator.setInterpolator( mPositionInterpolator);

       positionAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

          @Override

          public void onAnimationUpdate( ValueAnimator valueAnimator) {

              int current = (int) valueAnimator.getAnimatedValue();

              ViewCompat.setTranslationY(endView, current);

          }

      });

      ValueAnimator sizeAnimator = ValueAnimator.ofInt(startHeight, endHeight);

      if (mSizeDuration > 0) {

            sizeAnimator.setDuration(mSizeDuration);

      }

      sizeAnimator.setInterpolator( mSizeInterpolator);

      sizeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

          @Override

          public void onAnimationUpdate( ValueAnimator valueAnimator) {

              int current = (int) valueAnimator.getAnimatedValue();

              endView.getLayoutParams().height = current;

              endView.requestLayout();

          }

      });

      AnimatorSet set = new AnimatorSet();

      set.play(sizeAnimator).after( positionAnimator);

      return set;

  }

}

这一系列的动画其实是在跳转后的界面上完成的,所以这里的动画也是在目标view上完成。上面两个方法中收集到的信息, 需要在这里用到,通过以下代码来获取收集到的信息:

final int startTop = (int) startValues.values.get(TOP);

final int startHeight = (int) startValues.values.get(HEIGHT);

final int endTop = (int) endValues.values.get(TOP);

final int endHeight = (int) endValues.values.get(HEIGHT);

startValues和endValues都是createAnimator的参数。

接着:

ViewCompat.setTranslationY(endView, startTop);

endView.getLayoutParams().height = startHeight;

endView.requestLayout();

因为我们的动画顺序是先移动,后展开,首先把view的高度设置为前一个界面上view的高度是为了防止在移动的过程中view的高度是他自身的高度的。

接着创建了两个动画,这两个动画很好理解, 一个位移的,一个是展开的。给了动画一个时长和插值器,这两个信息是公开给调用者去设置的。

最后创建一个AnimatorSet,在这个动画集合中,先完成sizeAnimator然后开始positionAnimator,最后返回该动画集合.。自定义Transition完毕。

那么在一次 Transition 变化中,createAnimator会执行多少次呢?Transition 本来就是将前后 scene 做差异动画的,所以,createAnimator 的调用次数其实就和前后 scene 有关。

注意,我遇到的坑:

①在captureStartValues和captureEndValues方法中transitionValues.values.put(key,value );里的key必须是相同的,如果两个方法的key都不相同,则不会执行createAnimator方法,导致自定义的Transition动画无效。

②Activity A和ActivityB必须在同一个task里,否则利用finishAfterTransition返回时,返回动画无效。

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/124169157