Android 里动画从用途上讲,可以分为三类 View Animation(View动画)又称 Tween Animation(补间动画)、Drawable Animation (帧动画) 和 Property Animation(属性动画)。 这篇文章,我就介绍一下这三种类型的动画。
目录:
(一)View动画
View动画是基于View的渐变动画,只改变了View的绘制效果,View的实际属性值并未改变。
View动画的对象是View,它支持4种动画效果:
- TranslateAnimation(平移动画)
- ScaleAnimation(缩放动画)
- RotateAnimation(旋转动画)
- AlphaAnimation(透明度动画)
并提供了AnimationSet动画集合。实现原理是每次绘图时View所在的ViewGroup中的dispathDraw,流程如下图:
除了这四种典型的动画效果外,帧动画本质上也属于View动画。但是帧动画的表现形式和这4种动画不太一样,因此通常单拎出来归为一类。
这四种动画既可以通过XML来定义,也可以通过代码来动态创建。
使用XML之前我们首先需要创建XML文件,路径为:res/anim/filename.xml。
1. 透明度动画
- 代码实现:
AlphaAnimation animation = new AlphaAnimation(0, 1);// 透明度0变化到透明度为1
animation.setDuration(1000);// 动画执行时间1s
textView.startAnimation(animation);
- XML实现:
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="1000"
android:fromAlpha="0"
android:interpolator="@android:anim/accelerate_interpolator"
android:repeatCount="3"
android:fillAfter="true"
android:repeatMode="restart"
android:toAlpha="1" />
</set>
2. 旋转动画
- 代码实现:
RotateAnimation animation = new RotateAnimation(0, 360, 100, 100);
animation.setDuration(1000);
animation.setFillAfter(true); // 设置保持动画最后的状态
animation.setInterpolator(new AccelerateInterpolator()); // 设置插入器
textView.startAnimation(animation);
还可以通过系统提供参数来控制动画
new RotateAnimation(0f, 360f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
- xml实现:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromDegrees="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:repeatCount="infinite"
android:toDegrees="360.0">
</rotate>
参数 | 说明 |
---|---|
fromDegrees | 为动画起始时的旋转角度,此角度是当前为0及360,设置其他值则先跳至该角度的位置再由from - to的值: 负则正向转,正则反向转 |
toDegrees | 为动画旋转到的角度 |
pivotXType | 为动画在X轴相对于物件位置类型 |
pivotXValue | 为动画相对于物件的X坐标的开始位置,此值是以本身原始位置为原点,即如设为20%p,则向右移动父控件的20%位移,为负数则向左移 |
pivotYType | 为动画在Y轴相对于物件位置类型 |
pivotYValue | 为动画相对于物件的Y坐标的开始位置,此值是以本身原始位置为原点,即如设为20%p,则向下移动父控件的20%位移,为负数则向上移 |
3. 位移动画
- 代码实现:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 200);
translateAnimation.setDuration(1000);
textView.startAnimation(translateAnimation);
- xml实现:
<translate
android:duration="1000"
android:fillAfter="true"
android:fromXDelta="0"
android:fromYDelta="0"
android:repeatCount="3"
android:toXDelta="200"
android:toYDelta="000">
</translate>
参数 | 说明 |
---|---|
fromXDelta | 为动画起始时 X坐标上的移动位置 |
toXDelta | 为动画结束时 X坐标上的移动位置 |
fromYDelta | 为动画起始时Y坐标上的移动位置 |
toYDelta | 为动画结束时Y坐标上的移动位置 |
4. 缩放动画
- 代码实现:
ScaleAnimation animation = new ScaleAnimation(0,1,0,1);
animation.setDuration(1000);
textView.startAnimation(animation);
缩放动画也可以设置缩放的中心点
ScaleAnimation animation = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
- xml实现:
<scale
android:duration="1000"
android:fillAfter="true"
android:fillBefore="true"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="reverse"
android:startOffset="2000"
android:toXScale="1.0"
android:toYScale="1.0">
</scale>
参数 | 说明 |
---|---|
fromX | 为动画起始时 X坐标上的伸缩尺寸 0.0表示收缩到没有 |
toX | 为动画结束时 X坐标上的伸缩尺寸 1.0表示正常无伸缩 |
fromY | 为动画起始时Y坐标上的伸缩尺寸 值小于1.0表示收缩 |
toY | 为动画结束时Y坐标上的伸缩尺寸 值大于1.0表示放大 |
pivotXType | 为动画在X轴相对于物件位置类型 |
pivotXValue | 为动画相对于物件的X坐标的开始位置 |
pivotXType | 为动画在Y轴相对于物件位置类型 |
pivotYValue | 为动画相对于物件的Y坐标的开始位置 |
5. anim文件使用
Animation animation = AnimationUtils.loadAnimation(this, R.anim.alpha_anim);
textView.startAnimation(animation);
6. 动画集合
AnimationSet提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放,顺序播放等。
AnimationSet animationSet = new AnimationSet(true);
animationSet.setDuration(1000);
AlphaAnimation alpha=new AlphaAnimation(0,1);
alpha.setDuration(1000);
animationSet.addAnimation(alpha);
TranslateAnimation translate = new TranslateAnimation(100, 200, 0, 200);
translate.setDuration(1000);
animationSet.addAnimation(translate);
textView.startAnimation(animationSet);
7. 动画监听
对于动画事件,Android也提供了开始、结束和重复事件的监听方法。
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
8. View动画的特殊使用
(1)LayoutAnimation
layoutAnimation作用于viewGroup,为ViewGroup制定一个动画,他的每个子元素都会按照这种动画出场。这种效果常被用在ListView上。使用步骤如下:
1)定义LayoutAnimation
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.5"
android:animationOrder="reverse"
android:animation="@anim/animation_scale">
</layoutAnimation>
android:delay 表示子元素开始动画的时间延迟
android:animationOrder 表示子元素动画的顺序,有三个选项:
- normal:顺序显示
- reverse:表示逆向显示
- random:随机播放
android:animation 为子元素指定具体的入场动画
2)为子元素制定具体的入场动画
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXScale="0.1"
android:toXScale="1.5"
android:fromYScale="0.1"
android:toYScale="1.5"
android:duration="2000">
</scale>
3)为GroupView指定LayoutAnimation属性
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/xlv"
android:layoutAnimation="@anim/animation_layout"
android:background="#fff4f7f9"
android:divider="#ddd"
android:dividerHeight="1.0px"
android:listSelector="#999">
</ListView>
除了在XML文件中进行设置,我们还可以使用代码来完成:
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);
(2)Transition Animation(过渡动画)
Transition 也是基于属性动画的,与属性动画不同的是,它是可以实现布局改变的过渡动画。由于Transition的内容较多,具体请参照 Android Transition Framework(过渡动画框架) 。
(二)帧动画
帧动画就是加载一系列Drawable资源来创建动画,这种传统动画某种程度上就是创建不同图片序列,顺序播放,就像电影胶片。在代码中定义动画帧,使用AnimationDrawable类;XML文件能更简单的组成动画帧,在res/drawable文件夹,使用<animation-list>采用<item>来定义不同的帧。只能设置的属性是动画间隔时间。
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/img0" android:duration="100"/>
<item android:drawable="@drawable/img1" android:duration="100"/>
<item android:drawable="@drawable/img2" android:duration="100"/>
<item android:drawable="@drawable/img3" android:duration="100"/>
<item android:drawable="@drawable/img4" android:duration="100"/>
<item android:drawable="@drawable/img5" android:duration="100"/>
...
</animation-list>
然后对组件的属性(如ImageView的android:src/android:background)直接设置即可。
ImageView iv = findViewById(R.id.image_view);
//iv.setBackgroundResource(R.drawable.drawable_animation);
//获取背景,并将其强转成AnimationDrawable
AnimationDrawable animationDrawable = (AnimationDrawable) iv.getBackground();
//判断是否在运行
if(!animationDrawable.isRunning()){
//开启帧动画
animationDrawable.start();
}
注意:帧动画的使用非常简单,但是比较容易引起OOM,所以在使用帧动画时应尽量避免使用过多尺寸较大的图片。
(三)属性动画
属性动画的对象除了传统的View对象,还可以是Object对象,动画之后,属性值被实实在在的改变了。因此,属性动画能够通过改变View对象的实际属性来实现View动画。任何时候View属性的改变,View能自动调用invalidate()来刷新。
属性动画是在 Android 3.0 开始引入的新的动画形式,不过说它新只是相对的,它已经有好几年的历史了,而且现在的项目中的动画 99% 都是用的它,极少再用到 View Animation 了。属性动画不仅可以使用自带的 API 来实现最常用的动画,而且通过自定义 View 的方式来做出定制化的动画。
1. ViewPropertyAnimator
使用方式:
View.animate()
后跟 translationX()
等方法,动画会自动执行。
view.animate().translationX(500);
2. ObjectAnimator
使用方式:
- 如果是自定义控件,需要添加
setter
/getter
方法; - 用
ObjectAnimator.ofXXX()
创建ObjectAnimator
对象; - 用
start()
方法执行动画。
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();
3. 通用功能
(1)setDuration(int duration) 设置动画时长
(2)setInterpolator(Interpolator interpolator) 设置 Interpolator(插值器)
- AccelerateDecelerateInterpolator 先加速再减速 (默认的
Interpolator)
- LinearInterpolator 匀速
- AccelerateInterpolator 持续加速
- DecelerateInterpolator 持续减速直到 0
- AnticipateInterpolator 先回拉一下再进行正常动画轨迹
- OvershootInterpolator 动画会超过目标值一些,然后再弹回来
- AnticipateOvershootInterpolator 上面这两个的结合版:开始前回拉,最后超过一些然后回弹
- BounceInterpolator 在目标值处弹跳
- CycleInterpolator 这个也是一个正弦 / 余弦曲线
- FastOutLinearInInterpolator 用贝塞尔曲线持续加速
- LinearOutSlowInInterpolator 持续减速,初始速度更高
- PathInterpolator 自定义动画完成度 / 时间完成度曲线
Path interpolatorPath = new Path();
...
// 匀速
interpolatorPath.lineTo(1, 1);
(3)setListener() / ObjectAnimator.addListener() 设置监听器
参数类型是 AnimatorListener
,所以本质上其实都是一样的。 AnimatorListener
共有 4 个回调方法:
- onAnimationStart(Animator animation)
- onAnimationEnd(Animator animation)
- onAnimationCancel(Animator animation)
- onAnimationRepeat(Animator animation)
补充:setUpdateListener() /addUpdateListener()
和上面两个方法类似,但是这两个方法虽然名称和可设置的监听器数量不一样,但本质其实都一样的。它们的参数都是 AnimatorUpdateListener
。它只有一个回调方法:
- onAnimationUpdate(ValueAnimator animation)
4. TypeEvaluator
关于 ObjectAnimator,可以用 ofInt()
来做整数的属性动画和用 ofFloat()
来做小数的属性动画。这两种属性类型是属性动画最常用的两种,不过在实际的开发中,可以做属性动画的类型还是有其他的一些类型。当需要对其他类型来做属性动画的时候,就需要用到 TypeEvaluator
了。
(1)ArgbEvaluator
TypeEvaluator
最经典的用法是使用 ArgbEvaluator
来做颜色渐变的动画。
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new ArgbEvaluator());
animator.start();
在 Android 5.0 (API 21) 加入了新的方法 ofArgb()。
ObjectAnimator animator = ObjectAnimator.ofArgb(view, "color", 0xffff0000, 0xff00ff00);
animator.start();
(2)自定义 Evaluator
// 自定义 HslEvaluator
private class HsvEvaluator implements TypeEvaluator<Integer> {
float[] startHsv = new float[3];
float[] endHsv = new float[3];
float[] outHsv = new float[3];
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
// 把 ARGB 转换成 HSV
Color.colorToHSV(startValue, startHsv);
Color.colorToHSV(endValue, endHsv);
// 计算当前动画完成度(fraction)所对应的颜色值
if (endHsv[0] - startHsv[0] > 180) {
endHsv[0] -= 360;
} else if (endHsv[0] - startHsv[0] < -180) {
endHsv[0] += 360;
}
outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
if (outHsv[0] > 360) {
outHsv[0] -= 360;
} else if (outHsv[0] < 0) {
outHsv[0] += 360;
}
outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;
// 计算当前动画完成度(fraction)所对应的透明度
int alpha = startValue >> 24 + (int) ((endValue >> 24 - startValue >> 24) * fraction);
// 把 HSV 转换回 ARGB 返回
return Color.HSVToColor(alpha, outHsv);
}
}
ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xff00ff00);
// 使用自定义的 HslEvaluator
animator.setEvaluator(new HsvEvaluator());
animator.start();
(3)使用不限定类型的属性做动画
借助于 TypeEvaluator
,属性动画就可以通过 ofObject()
来对不限定类型的属性做动画了。
1)为目标属性写一个自定义的 TypeEvaluator
2)使用 ofObject()
来创建 Animator
,并把自定义的 TypeEvaluator
作为参数填入
private class PointFEvaluator implements TypeEvaluator<PointF> {
PointF newPoint = new PointF();
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + (fraction * (endValue.x - startValue.x));
float y = startValue.y + (fraction * (endValue.y - startValue.y));
newPoint.set(x, y);
return newPoint;
}
}
ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
new PointFEvaluator(), new PointF(0, 0), new PointF(1, 1));
animator.start();
5. PropertyValuesHolder
很多时候,我们需要在同一个动画中改变多个属性,例如在改变透明度的同时改变尺寸。如果使用 ViewPropertyAnimator
,你可以直接用连写的方式来在一个动画中同时改变多个属性。
view.animate()
.scaleX(1)
.scaleY(1)
.alpha(1);
而对于 ObjectAnimator
,是不能这么用的。不过你可以使用 PropertyValuesHolder
来同时在一个动画中改变多个属性。
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();
6. AnimatorSet
有的时候,我们不止需要在一个动画中改变多个属性,还会需要多个动画配合工作,比如,在内容的大小从 0 放大到 100% 大小后开始移动。这种情况使用 PropertyValuesHolder
是不行的,因为这些属性如果放在同一个动画中,需要共享动画的开始时间、结束时间、Interpolator 等一系列的设定,这样就不能有先后次序地执行动画了。这就需要用到 AnimatorSet
了。
ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());
AnimatorSet animatorSet = new AnimatorSet();
// 两个动画依次执行
animatorSet.playSequentially(animator1, animator2);
animatorSet.start();
AnimatorSet
还可以这么用:
// 两个动画同时执行
animatorSet.playTogether(animator1, animator2);
animatorSet.start();
以及这么用:
// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式来精确配置各个 Animator 之间的关系
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();
7. PropertyValuesHolders.ofKeyframe()
除了合并多个属性和调配多个动画,你还可以在 PropertyValuesHolder
的基础上更进一步,通过设置 Keyframe
(关键帧),把同一个动画属性拆分成多个阶段。例如,你可以让一个进度增加到 100% 后再「反弹」回来。
// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();
总结:「关于复杂的属性关系来做动画」,就这么三种:
- 使用
PropertyValuesHolder
来对多个属性同时做动画; - 使用
AnimatorSet
来同时管理调配多个动画; -
PropertyValuesHolder
的进阶使用:使用PropertyValuesHolder.ofKeyframe()
来把一个属性拆分成多段,执行更加精细的属性动画。