该文档翻译自 Android 5.0 文档中对 Material Design 的介绍,原文地址:http://developer.android.com/training/material/animations.html
Material Design 中的动画用于在用户执行操作时给予他们反馈,并使用户在与你的应用交互时提供良好的视觉连续性。Material 主题为按钮和 Activity 过渡提供了一些默认的动画,同时在Android 5.0(API level 21)及以上版本你可以自定义这些动画并创建新的:
- 触摸反馈 (Touch feedback)
- 圆形波纹 (Circular Reveal)
- Activity 过渡 (Activity transitions)
- 曲线运动 (Curved motion)
- 视图状态变化 (View state changes)
自定义触摸反馈
Material Design 中的触摸反馈在用户与UI元素交互时在其接触点上给予了一个转瞬即逝的视觉确认信息。默认的触摸反馈使用新的 RippleDrawable 类为按钮施加动画,该类可以使按钮在不同状态间切换时实现水波纹效果。
在大多数情况下,你若要将这些功能应用到你的视图 XML 文件中,需要像这样指定视图的背景:
- 有边界波纹需要设置背景为: ?android:attr/selectableItemBackground
- 超出视图界限的波纹设置背景为: ?android:attr/selectableItemBackgroundBorderless
注意:selectableItemBackgroundBorderless 是在 API level 21 加入的新属性。
或者,你可以使用 ripple 元素定义一个 XML 资源RippleDrawable。
你可以为 RippleDrawable 分配一种颜色。要改变默认的触摸反馈颜色,使用主题的 android:colorControlHighlight 属性。
更多信息,请参考 API 文档 RippleDrawable 类。
使用显露效果(Reveal Effect)
显露(Reveal)动画在你显示或隐藏一组 UI 元素时给用户提供了更好的视觉连续性。ViewAnimationUtils.createCircularReveal() 方法能够让你对一个剪切圆实施动画来显现和隐藏一个视图。
要显示之前隐藏的视图使用以下效果:
// 之前不可见的视图
View myView = findViewById(R.id.my_view);
// 获取裁剪圆的中心
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;
// 获得裁剪圆的最终半径
int finalRadius = myView.getWidth();
// 为该视图创建并启动动画绘制器
// (开始半径为 0 )
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
anim.start();
要隐藏一个之前可见的视图使用以下效果:
// 之前可见的视图
final View myView = findViewById(R.id.my_view);
// 获取裁剪圆的中心
int cx = (myView.getLeft() + myView.getRight()) / 2;
int cy = (myView.getTop() + myView.getBottom()) / 2;
// 获得裁剪圆的最终半径
int initialRadius = myView.getWidth();
// 创建动画(最终半径为0)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
// 当动画完成时使视图不可见
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
});
// 启动动画
anim.start();
自定义 Activity 过渡(transition)
Material Design 中的 Activity 过渡动画通过两个 Activity 中的共有元素的移动和变换为其在不同状态提供了可视化的连接。你可以为进入过渡,退出过渡和 Activity 之间共享元素的过渡进行定制来指定你自己创建的动画。
- 进入过渡决定了 Activity 中的视图如何进入场景。例如,在 explode 的进入过渡中,视图从屏幕外面进入屏幕并飞到屏幕中间。
- 退出过渡决定了Activity 中的视图如何退出场景。例如,在 explode 的退出过渡中,视图从中间向外退出场景。
- 共享元素过渡决定了在两个 Activitis 之间共享的视图在这些 Activities 转变时的变化。例如,如果两个 Activities 有一个相同的图片在不同的位置有着不同的大小,changeImageTransform这个共享元素过渡将在两个 Activities 之间平滑的变换缩放该图片。
Android 5.0(API level 21)支持这些进入和退出过渡:
- explode - 将视图从场景中心移入或移出。
- slid - 从场景的某一边移入或移出
- fade - 通过改变视图的透明度来添加或移除一个视图。
任何继承了 Visibility 类的过渡都可以作为进入过渡或退出过渡。更多信息,请查看 Transition 类。
Android 5.0(API level 21)还支持共享元素过渡:
- changeBounds - 动态改变目标视图的布局边界
- changeClipBounds - 动态改变目标视图的裁剪边界
- changeTransform - 动态改变目标视图的缩放比例和旋转角度
- changeImageTransform - 动态改变目标图片的尺寸和缩放比例
当你在你的应用中启用了 Activity 过渡,默认的交叉渐隐过渡会在进入和退出 Activities 时触发。
图例: 有一个共享元素的场景过渡 (一个共享元素过渡的视频演示)
指定自定义过渡
首先,当你定义了一个继承了 Material 主题的样式时,可以使用 android:windowContentTransitions 属性启用窗口内容过渡。你还可以在你的样式定义中指定进入,退出和共享元素过渡。
<style name="BaseAppTheme" parent="android:Theme.Material">
<!-- 启用窗口内容过渡 -->
<item name="android:windowContentTransitions">true</item>
<!-- 指定进入和退出过渡 -->
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/explode</item>
<!-- 指定共享元素过渡 -->
<item name="android:windowSharedElementEnterTransition">
@transition/change_image_transform</item>
<item name="android:windowSharedElementExitTransition">
@transition/change_image_transform</item>
</style>
该实例中的 change_image_transform过渡定义如下:
<!-- res/transition/change_image_transform.xml -->
<!-- (更多信息请查看下面的共享元素过渡) -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeImageTransform/>
</transitionSet>
changeImageTransform 元素对应了ChangeImageTransform 类。更多信息,查看 Transition API 文档。
要在你的代码中启用窗口内容过渡,调用 Window.requestFeature() 方法:
// 在你的 Activity 代码中(如果你没有在你的主题中启用过渡)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// 设置一个退出过渡
getWindow().setExitTransition(new Explode());
要在代码中指定过渡,传入一个 Transition 对象来调用这些方法:
- Window.setEnterTransition()
- Window.setExitTransition()
- Window.setSharedElementEnterTransition()
- Window.setSharedElementExitTransition()
setExitTransition() 和setSharedElementExitTransition() 方法为调用的 Activity 定义了退出过渡。
setEnterTransition()和 setSharedElementEnterTransition() 方法为调用的 Activity 定义了进入过渡。
要获得过渡的全部效果,你必须要在调用和被调用的 Activities 都启用窗口内容过渡。否则,你调用的 Activity 会启动退出过渡,但是之后你将看到窗口过渡(像是缩放或淡出)。
要尽可能快的开始进入过渡,在被调用的 Activity 上使用 Window.setAllowEnterTransitionOverlap() 方法。这会让你有更引人注目的进入过渡动画。
用过渡启动一个 Activity
如果你启用了过渡并为一个 Activity 设置了一个退出过渡,当你像下面这样启动另一个 Activity 时该过渡会被激活:
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
如果你为第二个 Activity 设置了一个进入过渡,该过渡也会在 Activity 启动时激活。要在你启动另一个 Activity 时禁用过渡,提供一个 null 选项的 bundle 参数。
在有一个共享元素时启动一个 Activity
要在有共享元素的两个 Activity 之间创建一个屏幕过渡动画:
- 在你的主题里启用窗口内容过渡
- 在你的样式里指定一个共享元素过渡
- 用一个 XML 资源定义你的的过渡
- 使用 android:transitionName 属性为两个布局中的共享元素指定一个公用名。
- 使用 ActivityOptions.makeSceneTransitionAnimation() 方法
// 得到一个能获取点击事件的元素
final View imgContainerView = findViewById(R.id.img_container);
// 获得该 Activity 过渡中的公有元素
final View androidRobotView = findViewById(R.id.image_small);
// 定义一个点击监听器
imgContainerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(this, Activity2.class);
// 创建过渡动画 - 两个 Activity 的布局中的图片都被
// 定义为 android:transitionName="robot"
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, androidRobotView, "robot");
// 启动新的 Activity
startActivity(intent, options.toBundle());
}
});
为了在你的代码中共享动态的视图(View),使用 View.setTransitionName() 方法在两个 Activity 中指定一个公用的元素名字。
要在你结束第二个 Activity 时反转场景过渡动画,调用 Activity.finishAfterTransition() 方法替代Activity.finish().
在有多个共享元素时启动一个 Activity
要在有一个以上共享元素的两个 Activities 之间创建一个场景过渡动画,使用 android:transitionName 属性在两个布局中(或在两个 Activities 中使用View.setTransitionName() 方法)定义共享元素,并创建一个像下面这样的 ActivityOptions 对象:
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
Pair.create(view1, "agreedName1"),
Pair.create(view2, "agreedName2"));
使用曲线运动(Curved Motion)
Material Design 中的动画依赖于曲线的时间插补器和空间运动模式。在 Android 5.0(API level 21)及以上,你可以为动画定义自定义的时间曲线和曲线运动模式。
PathInterpolator 类是一个基于贝塞尔曲线或一个Path 类的新插补器。该插补器指定一个在 1 X 1 直角坐标上的运动曲线, 该曲线有着(0, 0)和(1,1)两个固定锚点和使用构造器参数指定的控制点。你也可以定义一个 XML 资源的路径插补器:
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.4"
android:controlY1="0"
android:controlX2="1"
android:controlY2="1"/>
系统为 Material Design 规范中定义的三种基本曲线提供了 XML 资源:
- @interpolator/fast_out_linear_in.xml
- @interpolator/fast_out_slow_in.xml
- @interpolator/linear_out_slow_in.xml
你可以传入一个 PathInterpolator 对象到 Animator.setInterpolator() 方法中。
ObjectAnimator类有新的构造函数,能够让你一次使用两个或更多属性沿着一个路径动态的改变坐标。例如,下面的动画制作器使用了一个 Path 对象来动态的改变一个视图的X和Y属性:
ObjectAnimator mAnimator;
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
...
mAnimator.start();
对视图的状态改变施加动画
StateListAnimator类可以让你定义在视图(View)状态改变时运行的动画绘制器(Animator)。下面的示例展示了如何用XML资源定义一个 StateListAnimator:
<!-- 当按下时对视图的 translationZ 属性实施动画 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator android:propertyName="translationZ"
android:duration="@android:integer/config_shortAnimTime"
android:valueTo="2dp"
android:valueType="floatType"/>
<!-- 你可以在这里为 "x" 和 "y"
或其它属性使用另一个 ObjectAnimator 元素 -->
</set>
</item>
<item android:state_enabled="true"
android:state_pressed="false"
android:state_focused="true">
<set>
<objectAnimator android:propertyName="translationZ"
android:duration="100"
android:valueTo="0"
android:valueType="floatType"/>
</set>
</item>
</selector>
要往一个视图中添加自定义的视图状态动画,在一个如上面的例子中那样的 XML 资源中使用 selector 元素定义一个动画绘制器(Animator),然后将自定义的视图状态动画通过android:stateListAnimator 分配给你的视图。要在你的代码中将一个状态列表动画绘制器(Animator)分配给一个视图,使用AnimationInflater.loadStateListAnimator() 方法,并通过 View.setStateListAnimator()方法将该动画绘制器分配给你的视图。
当你的主题继承了 Material 主题后,按钮默认会有一个 Z 方向的动画。要在你的按钮中避免该行为,设置android:stateListAnimator 属性为@null 。
AnimatedStateListDrawable类可以让你创建 drawables, 该 drawables 会在与其关联的视图(View)状态发生改变时显示动画。Android 5.0中的一些系统组件默认使用这些动画。下面的例子展示了如何用一个XML资源定义一个AnimatedStateListDrawable :
<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 为每种状态提供不同的 Drawable -->
<item android:id="@+id/pressed" android:drawable="@drawable/drawableP"
android:state_pressed="true"/>
<item android:id="@+id/focused" android:drawable="@drawable/drawableF"
android:state_focused="true"/>
<item android:id="@id/default"
android:drawable="@drawable/drawableD"/>
<!-- 指定一个过渡 -->
<transition android:fromId="@+id/default" android:toId="@+id/pressed">
<animation-list>
<item android:duration="15" android:drawable="@drawable/dt1"/>
<item android:duration="15" android:drawable="@drawable/dt2"/>
...
</animation-list>
</transition>
...
</animated-selector>
对矢量图形(Vector Drawables)施加动画
Vector Drawables可缩放并且不会降低清晰度。 AnimatedVectorDrawable 类让你能对一个矢量图形的属性施加动画。
你通常要在3个 XML 文件中定义可动矢量图形(animated vector drawables):
- 一个在 res/drawable/ 中有着<vector> 元素的矢量图形。
- 一个在 res/drawable/ 中有着<animated-vector> 元素的可动矢量图形(animated vector drawables)。
- 在 res/anim/ 中的一个或更多有着 <objectAnimator> 元素的动画绘制器(Animators)。
可动矢量图形(animated vector drawables)可以对 <group> 元素和 <path> 元素的属性施加动画。<group> 元素定义一个路径集合或其子元素,<path> 元素定义了将要被绘制的路径。
当你定义了你想要施加动画的矢量图形,使用 android:name 属性分配一个唯一的名字到组(groups)和路径(paths)中,然后你就可以从动画绘制器(animator)的定义中引用它们。例如:
<!-- res/drawable/vectordrawable.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="64dp"
android:width="64dp"
android:viewportHeight="600"
android:viewportWidth="600">
<group
android:name="rotationGroup"
android:pivotX="300.0"
android:pivotY="300.0"
android:rotation="45.0" >
<path
android:name="v"
android:fillColor="#000000"
android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
</group>
</vector>
可动矢量图像的定义通过组和路径的名字从矢量图形中引用他们:
<!-- res/drawable/animvectordrawable.xml -->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/vectordrawable" >
<target
android:name="rotationGroup"
android:animation="@anim/rotation" />
<target
android:name="v"
android:animation="@anim/path_morph" />
</animated-vector>
动画的定义代表了 ObjectAnimator 或 AnimatorSet 对象。在该例子中的第一个动画绘制器将目标组旋转了360度:
<!-- res/anim/rotation.xml -->
<objectAnimator
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
该例子中的第二个动画绘制器将矢量图像的路径从一个形状变形为另一种形状。两个路径必须为变形进行兼容调整:他们必须要有同样数量的指令并且每个指令有相同数量的参数。
<!-- res/anim/path_morph.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:duration="3000"
android:propertyName="pathData"
android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
android:valueType="pathType" />
</set>
更多信息,请查看 API 文档 AnimatedVectorDrawable 。