Android 的动画分类:
- View视图动画(补间动画 / 逐帧动画)
-
属性动画
0x01 View视图动画
1.1 逐帧动画
frame-by-frame animation
帧动画是顺序播放一组预先定义好的图片,类似电影播放。
不同于补间动画,系统提供了另外一个类 AnimationDrawable 来使用帧动画。
实现方式 2 种:XML定义 和 代码动态创建
1.1.1 实现方式1: XML定义
创建动画的 XML 文件路径:res/drawable/frame_animation.xml
根标签:animation-list
属性名称 | 说明 |
---|---|
oneshot | 是否仅播放一次 |
duration | 每一帧的显示时长 |
XML 示例代码如下:
// res/drawable/frame_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@drawable/img_0"
android:duration="50" />
<item
android:drawable="@drawable/img_1"
android:duration="50" />
<item
android:drawable="@drawable/img_2"
android:duration="50" />
</animation-list >
动画加载 Java 示例代码如下:
ImageView imageView = new ImageView(this);
imageView.setImageResource(R.drawable.frame_animation);
AnimationDrawable animationDrawable = (AnimationDrawable)imageView.getDrawable();
// 播放开始
animationDrawable.start();
// 播放结束
animationDrawable.stop();
1.1.2 实现方式2: 代码动态创建
Java 示例代码如下:
AnimationDrawable frameAnimation = new AnimationDrawable();
// 参数1:图片;参数2:显示时长;
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_0), 50);
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_1), 50);
frameAnimation.addFrame(getResources().getDrawable(R.drawable.img_2), 50);
// 是否重复
frameAnimation.setOneShot(false);
// 播放开始
frameAnimation.start();
// 播放结束
frameAnimation.stop();
1.1.3 优缺点
缺点:使用大量图片,易导致OOM
建议:直接使用 gif,大小可以明显改善
1.2 补间动画
View的作用对象是 View。
它支持4种动画效果,分别是 平移动画、缩放动画、旋转动画 和 透明度动画。
而 帧动画 的表现形式和上面4种变换效果不太一样,单独介绍。
View 动画的 4 种变换效果对应着 Animation 的 4 个子类:TranslateAnimation、ScaleAniamtion、RotateAnimation 和 AlphaAnimation。
名称 | XML标签 | Animation子类 | 效果 |
---|---|---|---|
平移动画 | <translate> | TranslateAnimation | 移动 View |
缩放动画 | <scale> | ScaleAniamtion | 放大/缩小 View |
旋转动画 | <rotate> | RotateAnimation | 旋转 View |
透明度动画 | <alpha> | AlphaAnimation | 改变 View 的透明度 |
组合动画 | <set> | AnimationSet | 多个动画的结合 |
实现方式 2 种:XML定义 和 代码动态创建
对于 View 动画来说,建议采用 XML 定义动画,可读性更好。
View 动画既可以是单个动画,也可以由一系列动画组成。
<set>标签表示动画集合,对应 AnimationSet 类,它可以包含若干个动画,并且它的内部也是可以嵌套其他动画集合的。
1.2.1 实现方式1: XML定义
创建动画的 XML 文件路径:res/anim/view_animation.xml
。
XML 示例代码如下:
// res/anim/view_animation.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000" // 动画时长
android:fillAfter="true" // 动画结束后,是否留在结束位置,优先级高于fillBefore属性,默认是false
android:fillBefore="false" //动画结束后,是否留在开始位置,默认是true。
android:fillEnabled="true" //是否应用fillBefore的值,对fillAfter无影响,默认是true
android:repeatCount="0" //infinite无限重复
android:repeatMode="restart" // restart正序重播/reverse反转重播
android:interpolator="@android:anim/overshoot_interpolator"
android:shareInterpolator="true" // 组合动画属性,组合动画是否和集合(<set></set>)共享一个插值器,去过不指定,子动画需要单独设定
android:startOffset="100" //组合动画默认是全部动画同时开始,如果不同动画不同开始需要使用该属性延迟开始时间
/>
<translate
android:fromXDelta="0" // 水平x方向的起始值
android:fromYDelta="0" // 竖直y方向的起始值
android:toXDelta="500" // 水平x方向的结束值
android:toYDelta="500" // 竖直y方向的结束值
/>
<scale
android:fromXScale="0.0" // 起始缩放x的倍数
android:fromYScale="0.0" // 起始缩放y的倍数
android:toXScale="1.0" // 结束缩放x的倍数
android:toYScale="2.0" // 结束缩放y的倍数
android:pivotX="50%" // 缩放中心点的x坐标
android:pivotY="50%" // 缩放中心点的y坐标
/>
<rotate
android:fromDegrees="0" // 开始的角度
android:toDegrees="180" // 结束的角度
android:pivotX="20%" // 旋转中心点 x 坐标
android:pivotY="20%" // 旋转中心点 y 坐标
/>
<alpha
android:fromAlpha="1.0" // 开始透明度 0.0-1.0
android:toAlpha="0.0" //结束透明度 0.0-1.0
/>
</set>
动画加载 Java 示例代码如下:
Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
view.startAnimation(translateAnimation);
pivotX / pivotY 的取值有三个类型
- 数字, eg:android:pivotX="50", 该View左上角在x方向上平移50px的点,对应Java代码中设置参数 Animation.ABSOLUTE。
- 百分比, eg:android:pivotX="50%", 该View左上角在x方向上平移自身宽度50%的点,对应Java代码中设置参数 Animation.RELATIVE_TO_SELF。
- 百分比p (parent), eg:android:pivotX="50%p"该View左上角在x方向上平移父布局宽度50%的点,对应Java代码中设置参数 Animation.RELATIVE_TO_PARENT。
1.2.2 实现方式2: 代码动态创建
Java 示例代码如下:
// 组合动画设置
// 步骤1:创建组合动画shareInterpolator对象(设置为true)
AnimationSet setAnimation = new AnimationSet(true);
// 步骤2:设置组合动画的属性
// 特别说明以下情况
// 因为在下面的旋转动画设置了无限循环(RepeatCount = INFINITE)
// 所以动画不会结束,而是无限循环
// 所以组合动画的下面两行设置是无效的
setAnimation.setRepeatMode(Animation.RESTART);
setAnimation.setRepeatCount(1);// 设置了循环一次,但无效
// 步骤3:逐个创建子动画(方式同单个动画创建方式,此处不作过多描述)
// 子动画1:旋转动画
Animation rotate = new RotateAnimation(0,360,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotate.setDuration(1000);
rotate.setRepeatMode(Animation.RESTART);
rotate.setRepeatCount(Animation.INFINITE);
// 子动画2:平移动画
Animation translate = new TranslateAnimation(TranslateAnimation.RELATIVE_TO_PARENT,-0.5f,
TranslateAnimation.RELATIVE_TO_PARENT,0.5f,
TranslateAnimation.RELATIVE_TO_SELF,0
,TranslateAnimation.RELATIVE_TO_SELF,0);
translate.setDuration(10000);
// 子动画3:透明度动画
Animation alpha = new AlphaAnimation(1,0);
alpha.setDuration(3000);
alpha.setStartOffset(7000); // 通过延迟播放达到顺序效果
// 子动画4:缩放动画
Animation scale = new ScaleAnimation(1,0.5f,1,0.5f,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scale1.setDuration(1000);
scale1.setStartOffset(4000); // 通过延迟播放达到顺序效果
// 步骤4:将创建的子动画添加到组合动画里
setAnimation.addAnimation(alpha);
setAnimation.addAnimation(rotate);
setAnimation.addAnimation(translate);
setAnimation.addAnimation(scale);
// 步骤5:播放动画
mButton.startAnimation(setAnimation);
1.2.3 优缺点
缺点:
- 作用对象局限;根据包分类可知 android.view.animation,只针对View进行动画操作。
- 只改变视觉效果,没有改变属性;例如点击只在原始位置有效(经API27测试,当属性动画View完全不可见时,点击位置和范围为原始位置)。
- 动画效果单一,只有四种。
1.2.4 自定义 View 动画
自定义 View 动画是一件既简单又复杂的事情。
简单,是因为派生一种新动画只需要继承Animation这个抽象类,然后重写它的 initialize 和 applyTransformation方法,在 initialize 方法中做一些初始化工作,在 applyTransformation 中进行相应的矩阵变换即可,很多时候需要采用 Camera 来简化矩阵变换的过程。
复杂,是因为自定义 View 动画的过程主要是矩阵变换的过程,而矩阵变换是数学上的概念。
1.2.5 View 动画的特殊使用场景
比如在 ViewGroup 中可以控制子元素的出场效果,在 Activity 中可以实现不同 Activity 之间的切换效果。
-
LayoutAnimation
LayoutAnimation 作用于 ViewGroup,为 ViewGroup 指定一个动画,这样当它的子元素出场时都会具有这种动画效果。这种效果常常被用在ListView。
步骤如下:- 定义LayoutAnimation
- 定义子元素具体的入场动画
- 为 ViewGroup 指定
android:layoutAnimation
属性
// 1 /res/anim/layout_animation
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/layout_animation_item"
android:animationOrder="normal"
android:delay="0.5" />
// 2 /res/anim/layout_animation_item
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
<translate
android:fromXDelta="500"
android:toXDelta="0" />
</set>
// 3 在布局文件的ViewGroup中使用 layoutAnimation属性
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/layout_animation" />
除了在 XML 中指定 LayoutAnimation 外,还可以通过 LayoutAnimationController 来实现。
Animation animation = AnimationUtils.loadAnimation(this, R.anim.layout_animation_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);
- Activity 的切换效果
Activity 有默认的切换效果,但是这个效果我们可以通过 overridePendingTransition(int enterAnim, int exitAnim) 这个方法进行自定义,这个方法必须在 startActivity(intent) 或者 finish() 之后被调用才能生效,之前无效。
Fragment 也可以添加动画,由于 Fragment 是在 API 11 中新引入的类,因此为了兼容性我们需要使用support-v4
这个兼容包,在这个情况下我们可以通过 FragmentTransaction 中的 setCustomAnimation(int enter, int exit) 方法来添加切换动画,这个动画需要是 View 动画,之所以不能采用属性动画是因为属性动画也是API 11 新引入的。
0x02 属性动画
属性动画是 API 11 新加入的特性,和 View 动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画,甚至还可以没有对象。
属性动画中有 ViewPropertyAnimator、 ValueAnimator、ObjectAnimator 和 AnimatorSet 等概念,通过它们可以实现绚丽的动画。
ObjectAnimator extends ValueAnimator extends Animator
位于 android.animation
。
ViewPropertyAnimator
位于 android.view
,其本质内部使用 ValueAnimator。
ViewPropertyAnimator < ObjectAnimator < ValueAnimator,使用难度越来越高,但越来越灵活。
三种使用方式举例:
// ViewPropertyAnimator
view.animate().alphaBy(0.8f)
// ObjectAnimator
Object.ofFloat(view, "alpha", 1.0f, 0.0f, 1.0f).start();
// ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1.0f, 0.0f, 1.0f);
value.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float animatedValue = (float) animation.getAnimatedValue();
view.setAlpha(animatedValue);
}
});
valueAnimator.start();
ValueAnimator 本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们对象的属性值,这样就相当于我们的对象做了动画。
2.1 ViewPropertyAnimator
view.animate().alphaBy(0.8f);
为了满足面向对象编程思想,View 中引入了ViewPropertyAnimator,可以链式调用。
注意 ViewPropertyAnimator 没有 setRepeatCount() 和 setRepeatMode(),不能重复
View 中的方法 | 功能 | 对应 ViewPropertyAnimator 的方法 |
---|---|---|
setTranslationX() | 设置x轴偏移 | translationX() / translationXBy() |
setTranslationY() | 设置y轴偏移 | translationY() / translationYBy() |
setTranslationZ() | 设置z轴偏移 | translationZ() / translationZBy() |
setX() | 设置x轴绝对位置 | x() / xBy() |
setY() | 设置y轴绝对位置 | y() / yBy() |
setZ() | 设置z轴绝对位置 | z() / zBy() |
setRotation() | 设置平面旋转 | rotation() / rotationBy() |
setRotationX() | 设置沿x轴旋转 | rotationX() / rotationXBy() |
setRotationY() | 设置沿y轴旋转 | rotationY() / rotationYBy() |
setScaleX() | 设置横向放缩 | scaleX() / scaleXBy() |
setScaleY() | 设置纵向放缩 | scaleY() / scaleYBy() |
setAlpha() | 设置透明度 | alpha() / alphaBy() |
注意
- View#setX()没有动画渐变效果,直接将该View放到设置位置;而 ViewPropertyAnimator#x() 有平移过渡动画。
- ViewPropertyAnimator#x() 如果有动画执行x()将取消。例如view.x(500).translationX(50),将只执行translationX(50),不执行x(500)
- ViewPropertyAnimator#By() 表示在当前位置的基础上进行操作
2.2.1 实现方式1: XML定义
创建 XML 文件路径:res/animator/view_animator.xml
XML示例代码
如下:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="sequentially">
<objectAnimator
android:duration="2000"
android:propertyName="translationX"
android:valueFrom="0"
android:valueTo="500"
android:valueType="floatType" />
<set android:ordering="together">
<objectAnimator
android:duration="3000"
android:propertyName="rotation"
android:valueFrom="0"
android:valueTo="360"
android:valueType="floatType" />
<set android:ordering="sequentially">
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
<objectAnimator
android:duration="1500"
android:propertyName="alpha"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</set>
</set>
动画加载 Java 示例代码如下:
Animator animator = AnimatorInflater.loadAnimator(context,R.animator.view_animator);
animator.setTarget(view);
animator.start();
2.2.2 实现方式2: 代码动态创建
ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0, 500f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).before(moveIn); // with / before / after
animSet.setDuration(5000);
animSet.start();
2.3 属性动画的原理
属性动画要求动画作用的对象提供该属性的 get 和 set 方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用 set 方法,每次传递给 set 方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终的值。
自定义属性示例:
public class SportsView extends View {
float progress = 0;
......
// 创建 getter 方法
public float getProgress() {
return progress;
}
// 创建 setter 方法
public void setProgress(float progress) {
this.progress = progress;
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);
......
}
}
......
// 创建 ObjectAnimator 对象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 执行动画
animator.start();
对 object 的属性 abc 做动画,如果想让动画生效,需要满足两个条件:
- object 必须提供 setAbc 方法,如果动画的时候没有传递初始值,那么还要提供 getAbc 方法,因此系统要去取 abc 属性的初始值。(如果这条不满足,程序直接Crash)
- object 的 setAbc 对属性 abc 的改变必须反映出来(requestLayout/invalidate),才会有效果。(如果这条不满足,动画无效果但不会Crash)
动画不生效,只满足条件1而未满足条件2,解决方法有 3 种:
给你的对象加上 get 和 set 方法,如果你有权限的话;
这个方法最简单,加上 get 和 set 就搞定了,但针对Android SDK内部实现的View往往没有权限不可行。用一个类来包装原始对象,间接为其提供 get 和 set 方法;
private void performAnimation(View view) {
ViewWrapper wrapper = new ViewWrapper(view);
ObjectAnimator.ofInt(wrapper, "width", 0)
.setDuration(5000)
.start();
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target) {
this.mTarget = target;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
- 采用 ValueAnimator,监听动画过程,自己实现属性的改变;
private void valueAnimation(View targetView, int start, int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private IntEvaluator evaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
Integer curValue = evaluator.evaluate(fraction, start, end);
targetView.getLayoutParams().width = curValue;
targetView.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
2.4 时间插值器 TimeInterpolator / 类型估值器 TypeEvaluator
TimeInterpolator:时间插值器
根据 时间流逝的百分比 来计算 当前属性值改变的百分比。
TypeEvaluator:类型估值算法
根据 当前属性值改变的百分比 来计算改变后的 属性值。
2.4.1 时间插值器 TimeInterpolator
android.animation.TimeInterpolator
android.view.animation.Interpolator
Interpolator extends TimeInterpolator
属性动画内部使用的是 TimeInterpolator,补间动画内部使用的是 Interpolator。
TimeInterpolator 接口是属性动画中新增的,用于兼容Interpolator 接口,这样原先 Interpolator 的实现类就可以直接在属性动画中使用
列举官方插值器:
Java类 | 描述 |
---|---|
LinearInterpolator | 匀速 |
AccelerateInterpolator | 加速 |
DecelerateInterpolator | 减速 |
AccelerateDecelerateInterpolator | 先加速后减速(默认) |
AnticipateInterpolator | 先退后然后加速前进 |
OvershootInterpolator | 完成动画,超出后回到结束位置 |
AnticipateOvershootInterpolator | 先退后再加速前进,超出终点后再回终点 |
BounceInterpolator | 最后阶段弹球效果 |
CycleInterpolator | 周期运动 |
PathInterpolator | 自定义动画完成度/时间完成度曲线(0-1) |
FastOutLinearInInterpolator | 加速 |
LinearOutSlowInInterpolator | 持续减速 |
FastOutSlowInInterpolator | 先加速再减速 |
最后三个是Android5.0(API21)新增,和之前的类似,但是轨迹稍有区别
代码举例说明:
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
// 仅贴出关键代码
...
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
// input的运算逻辑如下:
// 使用了余弦函数,因input的取值范围是0到1,那么cos函数中的取值范围就是π到2π。
// 而cos(π)的结果是-1,cos(2π)的结果是1
// 所以该值除以2加上0.5后,getInterpolation()方法最终返回的结果值还是在0到1之间。只不过经过了余弦运算之后,最终的结果不再是匀速增加的了,而是经历了一个先加速后减速的过程
// 所以最终,fraction值 = 运算后的值 = 先加速后减速
// 所以该差值器是先加速再减速的
}
}
2.4.2 类型估值器 TypeEvaluate
根据当前属性值变化的百分比、初始值、结束值来计算当前属性的具体数值。当前属性值 = 初始值 + (结束值 - 初始值) * 百分比
实现
- 实现
TypeEvaluator<T>
接口 - 重写
public T evaluate(float fraction, T startValue, T endValue)
方法
官方示例
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
完整示例
public class Point {
private float x;
private float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
float startValueX = startValue.getX();
float startValueY = startValue.getY();
float currentX = startValueX + (endValue.getX() - startValueX) * fraction;
float currentY = startValueY + (endValue.getY() - startValueY) * fraction;
return new Point(currentX, currentY);
}
}
public class PointView extends View {
public static final float RADIUS = 70f;
private Point ccurrentPoint;
private Paint mPaint;
public PointView(Context context) {
this(context, null);
}
public PointView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (ccurrentPoint == null) {
ccurrentPoint = new Point(RADIUS, RADIUS);
}
canvas.drawCircle(ccurrentPoint.getX(), ccurrentPoint.getY(), RADIUS, mPaint);
}
public Point getCurrentPoint() {
return ccurrentPoint;
}
public void setCurrentPoint(Point currentPoint) {
this.ccurrentPoint = currentPoint;
invalidate();
}
}
Point pointStart = new Point(70, 70);
Point pointEnd = new Point(700, 1000);
// 使用 ObjectAnimator
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(point_view, "currentPoint", new PointEvaluator(), pointStart, pointEnd);
objectAnimator.setDuration(1000);
objectAnimator.start();
// 使用 ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(), pointStart, pointEnd);
valueAnimator.setDuration(1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point animatedValue = (Point) animation.getAnimatedValue();
point_view.setCurrentPoint(animatedValue);
}
});
valueAnimator.start();
2.5 属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要:AnimatorUpdateListener、AnimatorPauseListener和 AnimatorListener。
Animator
public static interface AnimatorListener {
default void onAnimationStart(Animator animation, boolean isReverse) {
onAnimationStart(animation);
}
default void onAnimationEnd(Animator animation, boolean isReverse) {
onAnimationEnd(animation);
}
void onAnimationStart(Animator animation);
void onAnimationEnd(Animator animation);
void onAnimationCancel(Animator animation);
void onAnimationRepeat(Animator animation);
}
public static interface AnimatorPauseListener {
void onAnimationPause(Animator animation);
void onAnimationResume(Animator animation);
}
为了便于开发,系统还提供了 AnimatorListenerAdapter 这个类,它是AnimatorListener 和 AnimatorPauseListener 的适配器类,这样我们就可以有选择的实现上面的6个方法。
ValueAnimator extends Animator
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
AnimatorUpdateListener 比较特殊,它会监听整个动画过程,动画是由许多帧组成的,每播放一帧,onAnimationUpdate 就会被调用一次。
2.5.1 添加监听器
ViewPropertyAnimator # setListener() / setUpdateListener()
ObjectAnimator # addListener() / addUpdateListener / addPauseListener()
VauleAnimator # addListener() / addUpdateListener / addPauseListener()
2.5.2 移除监听器
ViewPropertyAnimator # setListener(null) / setUpdateListener(null) 填null来移除
ObjectAnimator # removeListener() / removeUpdateListener() / removePauseListener()
VauleAnimator # removeListener() / removeUpdateListener() / removePauseListener()
ObjectAnimator 支持pause()方法暂停
ViewPropertyAnimator 不支持setRepeatMode() / setRepeatCount() 方法
ViewPropertyAnimator 独有withStartAction(Runnable runnable) 和 withEndAction(Runnable runnable) 方法,可设置一次动画开始或结束的监听。即使重新开始动画,也不会回调,是一次性的。而AnimatorListener是持续有效的。
withEndAction() 只有在动画正常结束才会调用,而在动画被取消时是不会执行的。而 AnimatorListener.onAnimationEnd() 在取消之后也会被调用,在调用 onAnimationCancel()之后调用
2.6 PropertyValuesHolder 同一动画中改变多个属性
ObjectAnimator.ofPropertyValuesHolder
关键字:一边一边,一个动画属性同时执行,区别多个动画先后执行。一个动画需要共享开始时间/结束时间/Interpolator等等设定,PropertyValuesHolder不能有先后次序执行动画了。
很多时候,在同一个动画中需要改变多个属性,例如改变透明度的同时改变尺寸。
使用 ViewPropertyAnimator如下:
view.animate()
.scaleX(0.0f)
.scaleY(0.0f)
.alpha(0.0f)
但是ObjectAnimator,是不能这么用的。需要使用PropertyValuesHolder来同时在一个动画里改变多个属性
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 0.0f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 0.0f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 0.0f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();
ViewPropertyAnimator 动画完成之后会停留在结束位置,再次点击执行动作操作不会执行动画
ObjectAnimator 动画完成之后会停留在结束位置,再次点击执行动作操作会从最原始状态重新执行一次动画。且Animator没有Animation的setFillAfter() 和setFillBefore()方法
关于点击范围的测试说明(API27),属性动画执行后属性发生变化,即点击范围和位置会更新,但经测试当View完全不可见时,点击位置和范围为原始位置
ObjectAnimator.ofInt()
ObjectAnimator.ofFloat()
ObjectAnimator.ofMultiFloat()
ObjectAnimator.ofPropertyValuesHolder
2.7 AnimatorSet 多个动画配合执行
关键字:一边一边,一个动画属性同时执行,区别多个动画先后执行。一个动画需要共享开始时间/结束时间/Interpolator等等设定,PropertyValuesHolder不能有先后次序执行动画了。
区别 AnimationSet,只能通过设置单个动画的 setStartOffset 来延迟时间进行先后执行顺序。
animatorSet.play(a1) / playTogether(a1,a2) / playSequentially(a1,a2)
.with(a3)
.before(a4)
.after(a5)
其中以 playXXX 开始得到 AnimatorSet.Builder 对象,然后调用with/before/after进行管理。
2.8 PropertyValuesHolders.ofKeyframe() 把同一个属性拆分
把一个属性拆分成多段,执行更加精细的属性动画。
Keyframe keyframe1 = Keyframe.ofFloat(0.0f, 0);
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
Keyframe keyframe3 = Keyframe.ofFloat(1.0f, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextMessage, holder);
animator.start();
0x03 使用动画的注意
OOM问题
主要出现在帧动画中,但图片数量过多且图片较大时,极易出现OOM,尽量避免使用帧动画内存泄漏
在 属性动画 中有一类无限循环的动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄漏。
View动画 不存在这个问题。兼容性问题
动画在 3.0 以下的系统上(API 11)有兼容性问题,在某些特殊场景可能无法正常工作,因此要做好适配View动画的问题
View动画是对View的影响做动画,并不是真正地改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GONE)失效,这时只需调用view.clearAnimation()清除View动画即可解决此问题。不要使用px
在进行动画的过程中,要尽量使用dp。使用px会导致在不同的设备上有不同的效果。动画元素的交互
将 view 移动(平移)后,在Android 3.0 以前的系统上,不管是 View动画 还是 属性动画,新位置 均无法触发单击事件,同时,老位置仍然可以触发单击事件。View只是在视觉上不存在了,移动回原位置后,单击事件继续生效。
从3.0开始,属性动画的单击事件触发位置为移动后的位置,但View动画仍然在原位置。硬件加速
在使用动画的过程中,开启硬件加速,会提高动画的流畅性。
但不可滥用!
1.硬件层会比普通view绘制多做很多的工作。首先将view绘制到GPU的一个层中,然后GPU再把这个层绘制到window上。
2.与所有的缓存一样,GPU的硬件缓存也会有失败几率。如果动画进行中调用 invalidate(),缓存的层会不得不重新渲染。如果不断有无效的硬件层产生的话,还不如不使用硬件加速。因为增加硬件层缓存会增加额外的开销。
简单的view绘制 使用硬件加速会增加不必要的开销,不建议开启。
使用示例:
// 设置硬件加速
myView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
// 设置动画
ObjectAnimator animator = ObjectAnimator.ofFloat(myView, View.TRANSLATION_X, 150);
// 设置一个回调,在动画完成时取消硬件加速
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
myView.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
// 开始动画
animator.start();
在API 14以上使用属性动画,可以更简洁:
myView.animate()
.translationX(150)
.withLayer()
.start();
0x04 参考资料
感谢以下文章作者
HenCoder Android 自定义 View 1-6:属性动画 Property Animation(上手篇)
Android:这是一份全面 & 详细的补间动画使用教程
Android开发艺术探索 第7章 Android动画深入分析
Android 属性动画详解与源码分析