概述:
Material design中的动画可以为用户的操作提供反馈, 还可以在用户跟app交互的时候提供视觉的连续性. Material主题为按钮和activity变换提供了一些默认的动画, Android 5.0及更高版本中让我们可以自定义这些动画, 并创建新的:
l 触摸反馈
l 圆形显示
l Activity变换
l 曲线运动
l View状态更改
自定义触摸反馈:
Material design中的触摸反馈在用户与UI交互的时候提供了一个瞬间的视觉效果来确认接触的点. 默认的按键触摸反馈动画使用新的RippleDrawable类, 它在按钮的状态变化时候会产生一个涟漪效果. 在大多数情况下, 我们应该在view的XML文件中通过指定view的背景为下面这些来使用这个功能:
?android:attr/seleceableItemBackground用于一个右边界的波纹涟漪效果.
?android:attr/seleceableItemBackgroundBorderless来指定一个可以超出View范围的涟漪效果. 它将会被绘制在最近的背景不为空的parent上, 并以parent为边界.
提醒: selectableItemBackgroundBorderless是一个新的属性, 它在API level21中被引入. 我们还可以通过ripple标签在XML文件中定义一个RippleDrawable. 它还可以修改颜色, 使用主题中的android:colorControlHighlight属性来改变默认的触摸反馈的颜色. 更多的信息可以参考RippleDrawable类.
使用揭示效果(Reveal Effect):
Reveal动画在显示或隐藏一组UI元素的时候为用户提供了视觉上的连续性. ViewAnimationUtils.createCircularReveal()方法让我们可以通过动画来剪切圆形来揭示或隐藏一个view. 要用这种小锅揭示一个之前不可见的view:
// previously invisible view
View myView = findViewById(R.id.my_view);
// get thecenter for the clipping circle
int cx = myView.getWidth()/ 2;
int cy = myView.getHeight()/ 2;
// get the finalradius for the clipping circle
float finalRadius = (float)Math.hypot(cx, cy);
// create theanimator for this view (the start radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy,0, finalRadius);
// make the viewvisible and start the animation
myView.setVisibility(View.VISIBLE);
anim.start();
要使用这种效果隐藏一个之前可见的view:
// previously visible view
final View myView = findViewById(R.id.my_view);
// get the center for the clipping circle
int cx = myView.getWidth() / 2;
int cy = myView.getHeight() / 2;
// get the initial radius for the clipping circle
float initialRadius = (float) Math.hypot(cx, cy);
// create the animation (the final radius is zero)
Animator anim =
ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
// make the view invisible when the animation is done
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
myView.setVisibility(View.INVISIBLE);
}
});
// start the animation
anim.start();
自定义Activity切换动画:
Activity切换通过动作和常用元素的变化为不同的状态提供了视觉连续性. 我们可以为进入和离开的切换操作指定自定义动画, 并在activity之间共享切换的元素.
l 进入的切换动画决定了一个activity中的view如何进入场景. 比如, 在explode进入动画中, view从外面进入场景并飞向屏幕中间.
l 离开切换动画决定activity的view如何离开场景. 比如, 在explode离开动画中, view从中心离开场景.
l 共享元素动画决定了在两个activity之间共享的view在activity切换的时候如何变化. 栗如, 如果两个activity拥有一个相同的图片, 但是在不同的位置拥有不同的尺寸, 这时使用changeImageTransform可以在activity之间平滑的变化和缩放图片.
Android 5.0支持这些进入和离开切换动画:
l Explode – 从场景的中心移入或移出view.
l Slide – 从场景的边界移入或移出view.
l Fade – 通过改变一个view的透明度使其移入或移出场景.
任何继承自Visibility类的切换都可以作为移入或移出切换动画. 更多信息可以参考Transition类. Android5.0同样支持这些共享元素切换:
l changeBounds – 在目标view的layout边界执行动画.
l changeClipBounds – 在目标view的剪切边界执行动画.
l changeTransform – 通过对目标view的缩放和旋转来实现动画.
l changeImageTransform – 通过修改目标图像的尺寸和缩放执行动画.
当启用activity切换时, 默认的cross-fading切换被使用在正在进入和离开的activity中. 下图是使用一个共享元素的场景切换.
指定自定义动画切换(transitions):
首先, 当我们定义一个从material design继承的样式, 通过android:windowActivityTransitions属性来启用窗口内容切换. 还可以在样式中指定进入, 离开和共享元素切换:
<style name="BaseAppTheme" parent="android:Theme.Material">
<!-- enable window content transitions -->
<item name="android:android:windowActivityTransitions">true</item>
<!-- specify enter and exit transitions -->
<item name="android:windowEnterTransition">@transition/explode</item>
<item name="android:windowExitTransition">@transition/explode</item>
<!-- specify shared element transitions -->
<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 -->
<!-- (see also Shared Transitions below) -->
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeImageTransform/>
</transitionSet>
changeImageTransform元素对应ChangeImageTransform类. 更多信息可以参考Transition.要启用窗口内容变换, 则要使用Window.requestFeature()方法:
// inside your activity (if you did not enable transitions in your theme)
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
// set an exit transition
getWindow().setExitTransition(new Explode());
要在代码中指定transition, 使用Transition对象调用这些方法:
Window.setEnterTransition()
Window.setExitTransition()
Window.setSharedElementEnterTransition()
Window.setSharedElementExitTransition()
setExitTransition()和setSharedElementExitTransition()方法定义了离开切换. setEnterTransition()和setSharedElementEnterTransition()方法定义了进入切换. 要实现切换动画的完整效果,必须在调用和被调用的activity中都启用窗口内容切换动画. 另外, 调用的activity将会使用退出切换动画, 可以看到一个缩放或者淡入淡出的动画.
要尽快启用一个进入的切换动画, 需要在被调用的activity中调用Window.setAllowEnterTransitionOverlap()方法. 这会让其拥有更多的进入动画.
使用切换动画启动一个activity:
如果启用了切换动画并设置了一个退出动画, 那么当加载另一个activity的时候切换动画会被触发:
startActivity(intent,
ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
如果我们已经设置了一个进入切换动画给第二个activity, 那么在activity启动的时候这个动画也会被激活. 要禁用切换动画, 只需要传一个null作为bondle就可以了.
使用共享元素启动一个activity:
要在两个activity之间使用共享的控件:
1. 在主题中启用窗口内容切换动画.
2. 在样式中指定一个共享的控件.
3. 将切换动画定义为一个XML资源.
4. 使用android:transitionName属性为共享的控件分配一个普通的名字.
5. 使用ActivityOptions.makeSceneTransitionAnimation()方法.
// get the element that receives the click event
final View imgContainerView = findViewById(R.id.img_container);
// get the common element for the transition in this activity
final View androidRobotView = findViewById(R.id.image_small);
// define a click listener
imgContainerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(this, Activity2.class);
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionName="robot"
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, androidRobotView, "robot");
// start the new activity
startActivity(intent, options.toBundle());
}
});
对于在代码中创建的共享的动态view, 使用View.setTranslationName()方法可以为其指定一个名字. 如果想要在第二个activity中回播之前的动画, 可以调用Activity.finishAfterTransition()方法, 而不是Activity.finish().
通过多个共享控件启动一个activity:
如果想要在两个activity切换的时候共享不止一个控件, 可以定义在两个layout都通过android:transitionName属性(或者使用View.setTransitionName()方法)来定义共享控件, 并这样创建一个ActivityOptions对象:
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
Pair.create(view1, "agreedName1"),
Pair.create(view2, "agreedName2"));
实现曲线运动:
在Android 5.0及以上版本中, 我们可以为动画自定义曲线运动及其时间跨度. PathInterpolator类是一个新的基于”贝塞尔曲线”或者一个Path对象的Interpolator. 它在一个1x1的区域指定了一个运动曲线, 在XML文件中我们可以这样定义一个Interpolator :
<pathInterpolatorxmlns:android="http://schemas.android.com/apk/res/android"
android:controlX1="0.4"
android:controlY1="0"
android:controlX2="1"
android:controlY2="1"/>
在material design规格中系统提供了三种XML资源用于基本的曲线运动:
l @interpolator/fast_out_linear_in.xml
l @interpolator/fast_out_slow_in.xml
l @interpolator/linear_out_slow_in.xml
我们可以给Animator.setInterpolator()方法传递一个PathInterpolator对象作为参数. ObjectAnimator类拥有一个新的构造方法可以让我们一次使用两个或者更多的属性来指定动画的坐标. 栗如, 下面的代码使用了一个Path对象来指定动画的X和Y属性:
ObjectAnimator mAnimator;
mAnimator = ObjectAnimator.ofFloat(view, View.X, View.Y, path);
...
mAnimator.start();
View状态改变动画:
StateListAnimator类让我们可以定义view状态改变时的动画效果. 下面的代码演示了如何在XML资源文件中定义一个StateListAnimator:
<!-- animate the translationZ property of a view when pressed -->
<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"/>
<!-- you could have other objectAnimator elements
here for "x" and "y", or other properties -->
</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>
要连接动画到一个view, 需要定义一个使用selector元素的XML资源文件, 并使用android:stateListAnimator属性将其指定给一个view. 要在代码里指定一个statelist animator给view, 可以使用AnimationInflater.loadStateListAnimator()方法, 并在view中执行View.setStateListAnimator()方法.
当主题继承了material主题, 默认情况下按钮会有一个Z动画. 要避免这种行为可以设置android:stateListAnimator属性为@null.
AnimatedStateListDrawable类让我们可以创建drawable来展示关联的view状态改变时候的动画. 默认情况下Android5.0中有一些系统控件使用了这些动画. 下面的栗子展示了如何在XML资源中定义一个AnimatedStateListDrawable:
<!-- res/drawable/myanimstatedrawable.xml -->
<animated-selector
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- provide a different drawable for each state-->
<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"/>
<!-- specify a transition -->
<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>
矢量Drawable动画:
矢量Drawable是可以缩放而不损失分辨率的Drawable. AnimatedVectorDrawable类让我们可以为矢量Drawable的属性指定动画. 通常, 我们会以这三种形式定义矢量动画:
l 在res/drawable/中使用<vector>元素定义一个矢量Drawable.
l 在res/drawable/中使用<animated-vector>元素定义一个可以使用动画的矢量Drawable.
l 在res/anim/中使用<objectAnimator>元素定义一个或者多个动画对象.
矢量drawable的动画可以改编<group>和<path>属性. <group>元素定义了一组路径和子群组, <path>元素则定义了要被绘制的路径. 当我们定义了一个想要执行动画的矢量drawable时, 使用android:name属性来分配一个唯一的名称给group和path, 这样就可以从动画定义中关联它们了:
<!-- 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>
动画矢量drawable的定义通过名字来关联group和path:
<!-- 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,第一个动画旋转目标group 360度:
<!-- res/anim/rotation.xml -->
<objectAnimator
android:duration="6000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360" />
第二个动画将矢量drawable路径从一个形状变成了另一个. 两种路径都必须对变形兼容: 它们必须有相同数量的命令, 每个命令比需拥有相同数量的参数:
<!-- 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>
更多信息可以参考AnimatedVectorDrawable.
本文参考: https://developer.android.com/training/material/animations.html