Android动画之Transition和TransitionManager使用

1 Transition概述

Transition 在前一篇已经简单讲解了它的使用Android动画之场景变换Transition动画的使用,这次接着讲解Transition的其他用法和TransitionManager。

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

2 Scene 场景

Scene 场景 场景过渡动画就是实现View从一种状态变化到另外一种状态,Scene就代表一个场景,它内部保存一个完整地视图结构,从根ViewGroup到所有子view,还有它们的所有状态信息。所以Scene最终就一个设置了不同属性特征的ViewGroup。

Scene构造函数
Scene(ViewGroup sceneRoot)
sceneRoot:根ViewGroup,用于承载Scene view
Scene(ViewGroup sceneRoot, View layout)
参数说明:
sceneRoot:根ViewGroup
layout:view布局

Scene一般利用getSceneForLayout()函数生成Scene场景
Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)
参数说明
sceneRoot:根ViewGroup,内部包含多个场景viewGroup。
layoutId:view的布局文件资源id,代表一个场景。
context:上下文
代码示例:

Activity layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main9Activity">

<FrameLayout
    android:id="@+id/viewcontainer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
</FrameLayout>

</android.support.constraint.ConstraintLayout>

scene1 layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <ImageView
android:id="@+id/imageview1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_home_game_nor"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="10dp"/>

    <ImageView
android:id="@+id/imageview2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_home_game_nor2"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="10dp"
        app:layout_constraintRight_toRightOf="parent"/>

</android.support.constraint.ConstraintLayout>

scene2 layout 只是图片调换了位置

生成Scene方法一:

Scene scene1 = Scene.getSceneForLayout(mContainer, R.layout.scene1, Main9Activity.this);
Scene scene2 = Scene.getSceneForLayout(mContainer, R.layout.scene2, Main9Activity.this);

除了利用getSceneForLayout外,根据Scene的构造函数,我们还可以传入View直接构造Scene。
生成Scene方法二:

View view1 = LayoutInflater.from(Main9Activity.this).inflate(R.layout.scene1, null);
View view2 = LayoutInflater.from(Main9Activity.this).inflate(R.layout.scene2, null);

Scene scene1 = new Scene(mContainer, view1);
Scene scene2 = new Scene(mContainer, view1);

3 TransitionManager

TransitionManager在场景变换时控制transitions的执行。通过TransitionManager可以添加场景和Transition变换,但为场景变化设置一个默认transitions是没有必要的,因为默认会使用AutoTransition。

TransitionManager当场景变换时开启动画的方式:
beginDelayedTransition(ViewGroup sceneRoot, Transition transition)
beginDelayedTransition(ViewGroup sceneRoot)
场景变幻时传入场景的view根sceneRoot,和transition动画。如果不指定Transition,默认为AutoTransition。

go(Scene scene, Transition transition)
go(Scene scene)
go的方式需要传入scene,scene由Scene利用view生成。如果不指定Transition,则默认为AutoTransition。

Transiton系统效果

继承Transiton类的效果

  • ChangeBounds:检测view的位置边界创建移动和缩放动画
  • ChangeTransform:检测view的scale和rotation创建缩放和旋转动画
  • ChangeClipBounds:检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果
  • ChangeImageTransform:检测ImageView的尺寸,位置以及ScaleType,并创建相应动画。
  • Fade,Slide,Explode:根据view的visibility的状态执行渐入渐出,滑动,分解动画。

go 两个场景切换

public class Main9Activity extends AppCompatActivity {
    private FrameLayout mContainer;
    boolean togger = true;
    Scene scene1 ;
    Scene scene2 ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main9);
        mContainer = findViewById(R.id.viewcontainer);
        mContainer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (togger){
                    TransitionManager.go(scene1,new ChangeBounds());
                }else{
                    TransitionManager.go(scene2,new ChangeBounds());
                }
                togger = !togger;
            }
        });
        init();
    }

    private void init() {
         scene1 = Scene.getSceneForLayout(mContainer, R.layout.scene1, Main9Activity.this);
         scene2 = Scene.getSceneForLayout(mContainer, R.layout.scene2, Main9Activity.this);
   }

如果不指定Transition为ChangeBounds。

mContainer.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (togger){
           // TransitionManager.go(scene1,new ChangeBounds());
            TransitionManager.go(scene1);
        }else{
            TransitionManager.go(scene2);
        }
        togger = !togger;
    }
});

效果图虽然没有指定Transition但是也有动画效果,其实如果不指定Transition,系统默认的Transition为AutoTransition。

AutoTransition关键源码:

private void init() {
    setOrdering(ORDERING_SEQUENTIAL);
    addTransition(new Fade(Fade.OUT)).
            addTransition(new ChangeBounds()).
            addTransition(new Fade(Fade.IN));
}

AutoTransition是个组合变换。

slide效果
TransitionManager.go(scene1,new Slide());

Explode效果
TransitionManager.go(scene1,new Explode());

上面的例子都是对于xml layout 文件做变换,下面我们对已存在sceneRoot中的view做动画

Activity的layout:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main9Activity">

<FrameLayout
    android:id="@+id/viewcontainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent">
    <FrameLayout
    android:id="@+id/viewcontainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

 <ImageView
            android:layout_gravity="center"
            android:id="@+id/imageview1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/image_home_game_nor"
            android:scaleType="matrix"
            android:layout_marginTop="10dp"/>

        <ImageView
            android:layout_gravity="center"
            android:id="@+id/imageview2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/image_home_game_nor2"
            android:scaleType="matrix"
            android:layout_marginTop="10dp"/>
</FrameLayout>
      
</FrameLayout>

</android.support.constraint.ConstraintLayout>

然后取imageview1生成Scene,

imageView = mContainer.findViewById(R.id.imageview1);

imageView = mContainer.findViewById(R.id.imageview1);
        imageView2 = mContainer.findViewById(R.id.imageview2);
        scene1 = new Scene(mContainer, imageView);
        scene2 = new Scene(mContainer, imageView2);

Container.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (togger){
             Transition transition = new Fade();
                    transition.setDuration(1000);
                    TransitionManager.go(scene1,transition);
        }else{
            Transition transition = new Fade();
                    transition.setDuration(1000);
                    TransitionManager.go(scene2,transition);
        }
        togger = !togger;
    }
});

运行出错:
Process: com.ldx.canvasdrawdemo, PID: 18825
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.

注意:
利用Scene构造函数生成Scene时,需要sceneRoot,sceneRoot在动画开始时,会将sceneRoot中的所有子View都remove掉,然后在sceneRoot中加载结束场景。通过代码new Scene(mSceneRoot, view)生成Scene,view必须是sceneRoot的直接子view,或者view是没有parentview的,不然在addview的时候会报错。

修改如下:
让图片作为sceneRoot的直接子view。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Main9Activity">

<FrameLayout
    android:id="@+id/viewcontainer"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent">
        <ImageView
            android:layout_gravity="center"
            android:id="@+id/imageview1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/image_home_game_nor"
            android:scaleType="matrix"
            android:layout_marginTop="10dp"/>

        <ImageView
            android:layout_gravity="center"
            android:id="@+id/imageview2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/image_home_game_nor2"
            android:scaleType="matrix"
            android:layout_marginTop="10dp"/>

</FrameLayout>

</android.support.constraint.ConstraintLayout>
  mContainer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
             
                if (togger){
                  Transition transition = new Fade();
                    transition.setDuration(1000);
                    TransitionManager.go(scene1,transition);

                }else{
                   Transition transition = new Fade();
                    transition.setDuration(1000);
                    TransitionManager.go(scene2,transition);
                }
                togger = !togger;
            }
        });

只对布局中的某些view做动画

scene1和scene2 xml文件类似,只列举scene1 layout 文件

<android.support.constraint.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">

    <ImageView
        android:id="@+id/imageview1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_home_game_nor"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="10dp"/>

    <ImageView
        android:id="@+id/imageview2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/image_home_game_nor2"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="10dp"
        app:layout_constraintLeft_toLeftOf="parent"
       />

</android.support.constraint.ConstraintLayout>

可以利用Transition的addTarget(),removeTarget(),只对某些view做动画,或者不对某些view做动画。
如果调用了addTarget则只对调用了这个函数的View做动画,其他View直接完成最终状态,如果调用了removeTarget则是对没有调用这个函数的其他view做动画。如果同时调用了两个函数,则调用removeTarget会从调用了addTarget中的view查找,然后剔除。

mContainer.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (togger){
            Transition transition = new ChangeBounds();
            transition.addTarget(R.id.imageview1);
            TransitionManager.go(scene1,transition);
        }else{
            Transition transition = new ChangeBounds();
            transition.addTarget(R.id.imageview1);
            TransitionManager.go(scene2,transition);
           }
        togger = !togger;
    }
});

只对其中一个ImageView做动画:


可以看到效果图中,有一个ImageView没有动画效果。

beginDelayedTransition(ViewGroup sceneRoot, Transition transition)

TransitionManager.go函数需要生成对应的Scene,beginDelayedTransiton则不需要,只需要填入sceneRoot和Transition就可以实现Transition动画。

这个函数里面没有Scene,那它何时执行动画呢,很简单当view的某些属性信息改变时,就会执行动画,上面介绍go函数时使用了ChangeTransform,ChangeBounds,ChangeClipBounds
,ChangeImageTransform,但感觉他们效果差不多,其实这些属性主要用于beginDelayedTransition这个函数,当对应的属性改变时,会自动触发Transition动画。

执行TransitionManager.beginDelayedTransition后,系统会保存一个当前视图树状态的场景,修改view的属性信息,在下一次绘制时,系统会自动对比之前保存的视图树,然后执行一步动画
重要提醒:如果想让beginDelayedTransition有效果,必须每次改变视图属性之后,重新调用beginDelayedTransition,或者改变之前调用beginDelayedTransition,这样才能够保存当前view的状态,否则存储的属性没有改变,不会有动画效果。

ChangeBounds尺寸改变

mContainer.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Transition transition = new ChangeBounds();
        transition.setDuration(1000);
        TransitionManager.beginDelayedTransition(mContainer,transition);
        if (togger){
                      FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) imageView.getLayoutParams();
            layoutParams1.height =100;
            layoutParams1.width =100;
            imageView.setLayoutParams(layoutParams1);
        }else{
            FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) imageView.getLayoutParams();
            layoutParams2.height = 700;
            layoutParams2.width = 700;
            imageView.setLayoutParams(layoutParams2);
       }
        togger = !togger;
    }
});

Transition 缩放效果。
ChangeImageTransform

<ImageView
    android:layout_gravity="center"
    android:id="@+id/imageview1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/image_home_game_nor"
    android:scaleType="matrix"
    android:layout_marginTop="10dp"/>

ImageView的scaleType 设置为matrix

mContainer.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Transition transition = new ChangeImageTransform();
        transition.setDuration(1000);
        TransitionManager.beginDelayedTransition(mContainer,transition);
        if (togger){
            imageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
        }else{
            imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        }
        togger = !togger;
    }
});

效果图:

利用matrix缩放旋转ImageView,然后设置Transition,没有发现动画效果,有点疑惑还在看。

Animation动画概述和执行原理
Android动画之补间动画TweenAnimation
Android动画之逐帧动画FrameAnimation
Android动画之插值器简介和系统默认插值器
Android动画之插值器Interpolator自定义
Android动画之视图动画的缺点和属性动画的引入
Android动画之ValueAnimator用法和自定义估值器
Android动画之ObjectAnimator实现补间动画和ObjectAnimator自定义属性
Android动画之ObjectAnimator中ofXX函数全解析-自定义Property,TypeConverter,TypeEvaluator
Android动画之AnimatorSet联合动画用法
Android动画之LayoutTransition布局动画
Android动画之共享元素动画
Android动画之ViewPropertyAnimator(专用于view的属性动画)
Android动画之Activity切换动画overridePendingTransition实现和Theme Xml方式实现
Android动画之ActivityOptionsCompat概述
Android动画之场景变换Transition动画的使用
Android动画之Transition和TransitionManager使用
Android动画之圆形揭露动画Circular Reveal
Android 动画之 LayoutAnimation 动画
Android动画之视图动画的缺点和属性动画的引入

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容