Android 过渡(Transition)动画解析之基础篇

《Android 过渡(Transition)动画解析之基础篇》 android_transition.png

某位哲人曾经曰过:在应用中使用一些符合系统设计规范的动画可以显著提升用户体验和自身逼格,我们在开发中或多或少会接触到一些动画方面的东西。Android系统本身的一些交互体验也在发生着变化,当然是朝着越来越绚丽的方向发展了。为了支持各种交互视觉设计的不断更新,Android对于开发者提供了越来越多的动画API支持。从API 1就存在的Drawable AnimationView Animation,以及API 11(Android 3.0)以后加入的Property Animation。而过渡动画Transition是在API 19(Android 4.4.2)中加入的。

那为什么要引入Transition动画呢?由于在Android引入了Metrial Desigon之后,动画的场面越来越大,比如以前我们制作一个动画可能涉及到的View就一个,或者就那么几个,如果我们一个动画中涉及到了当前Activity视图树中的各个View,那么情况就复杂了。比如我们要一次针对视图树中的10个View进行动画,这些View的效果都不同,可能有的是平移,有的是旋转,有的是淡入淡出,那么不管是使用之前哪种方式的动画,我们都需要为每个View定义一个开始状态和结束状态【关键帧,比如放缩,我们得设置fromXScaletoXScale 】,随着View个数的增加,这个情况会越来越复杂。

1. Transition 动画例子

为了先让大家有一个直观的感受,我们先来看一个Transition的简单例子,本系列中所有的代码示例可以直接在这里下载-github

《Android 过渡(Transition)动画解析之基础篇》 transition_demo1.gif

这里我们有三个ImageView,然后我们需要将他们的位置按顺时针旋转一下。如果我们以前实现这个需求应该怎么做呢?我们得写一个三个位置移动的动画,分别应用到这三个View上面。而在Transition中,这个动画非常简单,我们只需要执行如下操作:

准备工作

我们定义了一个Activity,用来展示一个界面:

<LinearLayout               
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
  >  

<Button
    android:layout_width="match_parent"
    android:layout_height="45dp"
    android:text="开始动画"
    android:id="@+id/begin"
    />
  <!--这个View用来做动画的父布局-->
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/rootView"
    >
    <include layout="@layout/scene1"></include>
</FrameLayout>
</LinearLayout>

1.1 定义起始帧

在资源文件目录下新建一个layout,定义开始时的视图树的状态,scene1.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="400dp"
>
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image1"
    android:layout_centerInParent="true"
    android:id="@+id/image1"
    />
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image3"
    android:id="@+id/image3"
    android:layout_below="@id/image1"
    android:layout_alignParentLeft="true"
    />
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image2"
    android:id="@+id/image2"
    android:layout_below="@id/image1"
    android:layout_alignParentRight="true"
    />
</RelativeLayout>

这个布局的效果如下:

《Android 过渡(Transition)动画解析之基础篇》 Paste_Image.png

由于我们动画开始的默认状态就是scene1,所以我们在Activitylayout中通过一个FrameLayout作为父容器【rootview】把scene1加载进来。

为什么需要一个rootView,后面会有解释

然后我们定义一个结束时的布局 scene2.xml,注意视图树中的Viewid是一一对应的。

<RelativeLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="400dp"
  >
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image3"
    android:layout_centerInParent="true"
    android:id="@+id/image3"
    />
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image2"
    android:id="@+id/image2"
    android:layout_below="@id/image3"
    android:layout_alignParentLeft="true"
    />
<ImageView
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:src="@drawable/image1"
    android:id="@+id/image1"
    android:layout_below="@id/image3"
    android:layout_alignParentRight="true"
    android:alpha="20"
    />
</RelativeLayout>

结束时的布局就是我们要的效果,如下:

《Android 过渡(Transition)动画解析之基础篇》 Paste_Image.png

1.2 定义过渡效果

Transition框架中,对于过渡效果的抽象就是Transition类,框架内置了一些常用的效果,比如幻灯片 Slide,淡入淡出 Fade,由于我们这里3个View涉及的都是位置的变化,我们可以直接使用Transition框架内置的动画效果ChangeBounds来进行两个帧之间的过渡。

1.3 开启动画

通过TransitionManager就可以开启动画:

    //加载Scene
Scene scene2 = Scene.getSceneForLayout(rootView, R.layout.scene2, this);
TransitionManager.go(scene2, new ChangeBounds());

通过这三个步骤,上面的动画效果就出来了,是不是很简单?

我来简单总结一下上面我们做了什么,首先,我们定义了一个视图树的两种状态(在以前的动画框架中称为关键帧),然后我们使用了一个内置的动画效果ChangeBounds它能够处理View本身大小、位置改变,然后我们通过TransitionManager让整个视图树进入任何你想要的状态即可。

注意,Transition中定义所谓的关键帧不再需要你去计算具体的View的坐标,你只需要告诉系统你需要过渡动画完毕之后是什么个状态就ok了。

下图是Transition动画的示意图,和我们例子中的步骤完全吻合:

《Android 过渡(Transition)动画解析之基础篇》 transitions_diagram.png

这里我们将一个记录视图树状态的东西成为 场景(Scene),在一个动画中我们一般会定义两个场景,开始和结束,有点类似于之前的关键帧。描述一个视图树变化效果的东西我们抽象成Transtion,例子中的ChangeBounds就是Transtion的一个子类。最后就是TransitionManager,负责启动一个过渡动画。下面我们展开来说一下Transition框架中的着三个核心类。

2. 场景 Scenes

如上所述,场景记录了一个视图树中所有View的属性值,我们上面的例子中是通过Scene.getSceneForLayout(rootView, R.layout.scene2, this);来记录一个场景的,其实我们也可以通过代码来创建一个场景。

Scene mScene = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));

当然,我们可以直接在代码中将一个ViewGroup定义成一个场景:

Scene mScene = new Scene(mSceneRoot, findViewById(R,id.scene));

我们注意到不管哪种方式,我们都需要为场景指定一个rootview,这个rootview将为作为视图树场景的根View

rootview在动画开始时,会将rootview中的所有子Viewremove掉,然后在rootview中加载我们的end场景。所以,对于end场景,如通过代码new Scene(mSceneRoot, view)创建的场景其实对于view是有要求的:要么viewmSceneRoot直接子view,这样在一开始就会被rootview remove掉,或者view是没有parentview的,不然在addview的时候会报错。

3. 过渡 Transition

3.1 过渡基础

Transition针对start场景和end场景视图树中View的一些属性,比如width,height,position,visibility等的变化,定义了过渡的效果。系统内置了changeboundsfadeslide等效果。

Transition不仅仅可以直接作用到整个视图树,如果我们不想把动画应用到视图树中的所有的View上,通过Transition.addTarget&removeTarget。我们将上面例子中的Transition改成:

Scene scene2 = Scene.getSceneForLayout(rootView,R.layout.scene2,BasicTransitions.this);
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.addTarget(R.id.image1);
TransitionManager.go(scene2,changeBounds);

我们可以看到只有一张图片应用了动画:

《Android 过渡(Transition)动画解析之基础篇》 transition_demo2.gif

当然我们可以给视图树增加一个动画集合,我们使用TransitionSet即可。

3.2 延迟动画

通过上面的例子,我们可以看到使用Transition动画,我们在定义一个end场景的时候需要通过一个layout来描述视图树的状态,这样多少有些麻烦,特别是我只想改变当前已经在展示的视图树,基于此,Transition框架提供了一个更加简单的机制,称为延迟动画。
它的使用灰常简单:

 ChangeBounds changeBounds = new ChangeBounds();
    changeBounds.setDuration(1000);
    //开启延迟动画,在这里会记录当前视图树的状态
    TransitionManager.beginDelayedTransition(rootView,changeBounds);
    //我们直接修改视图树中的View的属性
    ViewGroup.LayoutParams layoutParams = circulView1.getLayoutParams();
    layoutParams.height = 400;
    layoutParams.width = 400;
    circulView1.setLayoutParams(layoutParams);

    ViewGroup.LayoutParams layoutParams2 = circulView2.getLayoutParams();
    layoutParams2.height = 100;
    layoutParams2.width = 100;
    circulView2.setLayoutParams(layoutParams2);

在执行TransitionManager.beginDelayedTransition后,系统会保存一个当前视图树状态的场景,然后我们直接修改视图树,在下一次绘制时,系统会自动对比之前保存的视图树,然后执行一步动画。比如这里,我们修改了View的大小,效果如下:

《Android 过渡(Transition)动画解析之基础篇》 transition_demo3.gif

是不是很赞??

3.3 自定义Transition

处理系统内置的Transition,我们也可以自己定义过渡动画的效果,此时,我们就需要继承自Transition,然后自己去实现动画,我们可以看一下ChangeColor的一个实现:

public class ChangeColor extends Transition {
private static final String PROPNAME_BACKGROUND =
        "chuyun.com.transitions.basictransition:changecolor:background";

 // 开始的状态,这里会对视图树中所有的View调用,这里我们可以记录一下View的我们感兴趣的状态,比如这里:background
@Override
public void captureStartValues(TransitionValues transitionValues) {
    if (transitionValues.view.getBackground() instanceof ColorDrawable) {
        captureValues(transitionValues);
    }
}

// 结束也会对所有的View进行调用
@Override
public void captureEndValues(TransitionValues transitionValues) {
    if (transitionValues.view.getBackground() instanceof ColorDrawable) {
        captureValues(transitionValues);
    }
}

private void captureValues(TransitionValues transitionValues) {
    View view = transitionValues.view;
    transitionValues.values.put(PROPNAME_BACKGROUND, ((ColorDrawable) transitionValues.view.getBackground()).getColor());
}

//新建动画
@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
    if (null == startValues || null == endValues) {
        return null;
    }
    final View view = endValues.view;
    int startBackground = (Integer) startValues.values.get(PROPNAME_BACKGROUND);
    int endBackground = (Integer) endValues.values.get(PROPNAME_BACKGROUND);

    if (startBackground != endBackground) {
        ValueAnimator animator = ValueAnimator.ofObject(new ArgbEvaluator(),
                startBackground, endBackground);
        animator.setDuration(1000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Object value = animation.getAnimatedValue();
                if (null != value) {
                    view.setBackgroundColor((Integer) value);
                }
            }
        });
        return animator;
    }
    return null;
}

//返回我们定义的一些存储Key,注意,这里一定要复写
@Override
public String[] getTransitionProperties() {
    return new String[] {
            PROPNAME_BACKGROUND
    };
}

}

这样,我们就实现了一个对视图树中View的背景颜色敏感的Transition,同时,在场景切换时,会执行我们自己定义的动画,效果如下:

《Android 过渡(Transition)动画解析之基础篇》 transition_demo4.gif

官方给的Sample有些问题,主要有两处:1. 没有复写getTransitionProperties;2.captureValues里面直接存储background,由于View在变化前后的background Drawable引用并没有改变,Transition会去判断我们transitionValues.values.put前后的值,如果equals,那么表示这个值没有变化,动画不会执行。

好了,这里是过渡动画的基础,如果看完这篇文章,相信你对于怎么使用过渡动画应该不会有问题了。下一篇我们将来看看过渡动画源码是如何实现的。

    原文作者:楚云之南
    原文地址: https://www.jianshu.com/p/b72718bade45
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞