某位哲人曾经曰过:在应用中使用一些符合系统设计规范的动画可以显著提升用户体验和自身逼格,我们在开发中或多或少会接触到一些动画方面的东西。Android
系统本身的一些交互体验也在发生着变化,当然是朝着越来越绚丽的方向发展了。为了支持各种交互视觉设计的不断更新,Android
对于开发者提供了越来越多的动画API
支持。从API 1
就存在的Drawable Animation
和View 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
定义一个开始状态和结束状态【关键帧,比如放缩,我们得设置fromXScale
和toXScale
】,随着View
个数的增加,这个情况会越来越复杂。
1. Transition 动画例子
为了先让大家有一个直观的感受,我们先来看一个Transition
的简单例子,本系列中所有的代码示例可以直接在这里下载-github。
这里我们有三个ImageView
,然后我们需要将他们的位置按顺时针旋转一下。如果我们以前实现这个需求应该怎么做呢?我们得写一个三个位置移动的动画,分别应用到这三个View
上面。而在Transition
中,这个动画非常简单,我们只需要执行如下操作:
准备工作
我们定义了一个Activity
,用来展示一个界面:
1.1 定义起始帧
在资源文件目录下新建一个layout
,定义开始时的视图树的状态,scene1.xml
这个布局的效果如下:
由于我们动画开始的默认状态就是scene1
,所以我们在Activity
的layout
中通过一个FrameLayout
作为父容器【rootview
】把scene1
加载进来。
为什么需要一个
rootView
,后面会有解释
然后我们定义一个结束时的布局 scene2.xml
,注意视图树中的View
的id
是一一对应的。
结束时的布局就是我们要的效果,如下:
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
动画的示意图,和我们例子中的步骤完全吻合:
这里我们将一个记录视图树状态的东西成为 场景(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
中的所有子View
都remove
掉,然后在rootview
中加载我们的end
场景。所以,对于end
场景,如通过代码new Scene(mSceneRoot, view)
创建的场景其实对于view
是有要求的:要么view
是mSceneRoot
直接子view
,这样在一开始就会被rootview remove
掉,或者view
是没有parentview
的,不然在addview
的时候会报错。
3. 过渡 Transition
3.1 过渡基础
Transition
针对start
场景和end
场景视图树中View
的一些属性,比如width,height,position,visibility
等的变化,定义了过渡的效果。系统内置了changebounds
、fade
、slide
等效果。
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);
我们可以看到只有一张图片应用了动画:
当然我们可以给视图树增加一个动画集合,我们使用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
的大小,效果如下:
是不是很赞??
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,同时,在场景切换时,会执行我们自己定义的动画,效果如下:
官方给的
Sample
有些问题,主要有两处:1. 没有复写getTransitionProperties
;2.captureValues
里面直接存储background
,由于View
在变化前后的background Drawable
引用并没有改变,Transition
会去判断我们transitionValues.values.put
前后的值,如果equals
,那么表示这个值没有变化,动画不会执行。
好了,这里是过渡动画的基础,如果看完这篇文章,相信你对于怎么使用过渡动画应该不会有问题了。下一篇我们将来看看过渡动画源码是如何实现的。