一、前言
为了使用户的交互更加流畅自然,动画也就成为了一个应用中必不可少的元素之一。在 Android 中常用的动画分类无外乎三种,最早的 帧动画 、补间动画 以及 3.0 之后加入的 属性动画,是它们组成了 Android 中各种炫酷亮眼的动画效果。这里是官方的动画相关开发文档链接。
二、动画
2.1 动画分类
Android中有三种动画,分别是补间动画(Tween Animation),帧动画(Frame Animation)和属性动画(Property Animation)。
2.2 补间动画
这类动画比较简单,一般就是平移、缩放、旋转、透明度,或者其组合,可以用代码或者xml文件的形式。 四个动画效果实现类:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation、组合动画实现类:AnimationSet,对应的的XML标签为translate、 scale、 rotate、alpha、set,其中set里还可以放set,然后放在放置在res/anim/目录下。
2.2.1 平移(TranslateAnimation)
共有属性: duration 表示这一次动画持续的时间 fillAfter 表示动画结束时,是否保持最后一帧的样子 fillBefore 表示动画结束时,是否保持第一帧的样子 repeatCount 表示动画循环的次数,默认为 0 次不循环,-1 为无限循环。 repeatMode 表示是循环的模式,reverse 是从一次动画结束开始,restart 是从动画的开始处循环 interpolator 是一个插值器资源,它可以控制动画的播放速度 shareInterpolator 表示是否与 set 中其他动画共享插值器,false为各自使用各自的插值器
平移动画属性: fromXDelta fromYDelta 起始时,X/Y 方向的位置 toXDelta toYDelta 终止时,X/Y 方向的位置 这四个属性都支持同样的单位,是三种表达方式,浮点数、num% 和 num%p
- 浮点数 位置为 View 的左边距/上边距 + 此数值 正数为右,负数为左
- num% 位置为 View 的左边距/上边距 + View宽的百分之num 正数为右,负数为左
- num%p 位置为 View 的左边距/上边距 + 父容器的百分之num 正数为右,负数为左
动画效果:
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="300"
android:toYDelta="300"
android:repeatCount="-1"
android:repeatMode="reverse"/>
Animation translateAnim = AnimationUtils.loadAnimation(this, R.anim.view_translate);
view.startAnimation(translateAnim);
2.2.2 缩放(ScaleAnimation)
属性: fromXScale fromYScale 代表缩放时,X/Y 坐标起始大小,浮点值,0.5代表自身的一半,2.0代表自身的两倍大小。 toXScale toYScale 代表缩放时,X/Y 缩放结束时候大小。 pivotX 浮点数。在对象缩放时要保持不变的 X 坐标。 pivotY 浮点数。在对象缩放时要保持不变的 Y 坐标。
动画效果:
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="0.1"
android:fromYScale="0.1"
android:pivotX="0"
android:pivotY="0"
android:toXScale="1.0"
android:toYScale="1.0"
android:repeatCount="-1"
android:repeatMode="reverse"/>
Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.view_scale);
view.startAnimation(scaleAnim);
2.2.3 旋转(RotateAnimation)
属性: fromDegrees 起始角度 单位度 浮点值 toDegrees 结尾角度 单位度 浮点值 pivotX 旋转中心点的 X 坐标,这个数值有三种表达方式 pivotY旋转中心点的 Y 坐标,这个数值有三种表达方式
- 纯数字 例如 20 ,代表相对于自身左边缘或顶边缘 + 20 像素
- num% 代表 相对于自身左边缘或顶边缘 + 自身宽 的百分之 num
- num%p 代表相对于自身左边缘或顶边缘 + 父容器 的百分之 num
动画效果:
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="reverse"
android:toDegrees="1800" />
Animation rotateAnim = AnimationUtils.loadAnimation(this, R.anim.view_rotate);
view.startAnimation(rotateAnim);
2.2.4 透明度(AlphaAnimation)
属性: fromAlpha 表示动画开始时的透明度 toAlpha 表示动画结束时候的透明度 取值为[0.0,1.0],0代表完全透明,1代表不透明。
动画效果:
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="0"
android:repeatCount="-1"
android:repeatMode="reverse"
android:toAlpha="1" />
Animation scaleAnim = AnimationUtils.loadAnimation(this, R.anim.view_scale);
view.startAnimation(scaleAnim);
2.2.5 组合(AnimationSet)
动画效果:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true"
android:fillAfter="true"
android:duration="15000">
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
<rotate
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="720" />
<scale
android:fromXScale="0.1"
android:fromYScale="0.1"
android:pivotX="0%"
android:pivotY="0%"
android:toXScale="1.0"
android:toYScale="1.0" />
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="300"
android:toYDelta="300" />
</set>
java代码:
AnimationSet set = (AnimationSet) AnimationUtils.loadAnimation(this,R.anim.view_animation_set);
view.startAnimation(set);
//动画监听
set.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
//结束动画
//view.clearAnimation();
2.2.6 TimeInterpolator 时间插值器
TimeInterpolator 的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。负责控制动画变化的速率,使得基本的动画效果能够以匀速、加速、减速、抛物线速率等各种速率变化。其实质是一个数学函数 y = f(x),定义域 x 属于 (0.0,1.0) 的 float 值,值域 y 也是 (0.0,1.0) 的 float 值,曲线的斜率是速度。插值器就是策略模式的一个应用。
(1)系统内置插值器: AccelerateDecelerateInterpolator 使用:@android:anim/accelerate_decelerate_interpolator 其变化开始和结束速率较慢,中间加速 AccelerateInterpolator 使用: @android:anim/accelerate_interpolator 其变化开始速率较慢,后面加速 DecelerateInterpolator 使用:@android:anim/decelerate_interpolator 其变化开始速率较快,后面减速 LinearInterpolator 使用:@android:anim/linear_interpolator 其变化速率恒定 AnticipateInterpolator 使用:@android:anim/anticipate_interpolator 其变化开始向后甩,然后向前 AnticipateOvershootInterpolator 使用:@android:anim/anticipate_overshoot_interpolator 其变化开始向后甩,然后向前甩,过冲到目标值,最后又回到了终值 OvershootInterpolator 使用:@android:anim/overshoot_interpolator 其变化开始向前甩,过冲到目标值,最后又回到了终值 BounceInterpolator 使用:@android:anim/bounce_interpolator 其变化在结束时反弹 CycleInterpolator 使用:@android:anim/cycle_interpolator 循环播放,其速率为正弦曲线 (2)自定义插值器 这里有一个网站可以模拟我们需要的插值器。点击链接,选择scaling,library选 择spring。如下图下面就是一个回弹的插值器。 factor = 0.2 pow(2, -10 * x) * sin((x - factor / 4) * (2 * PI) / factor) + 1 代码实现上面的插值器,需要继承Interpolator实现getInterpolation()方法。如下:
public class SpringScaleInterpolator implements Interpolator {
private float factor;//回弹因子,值越大效果越慢
public SpringScaleInterpolator(float factor) {
this.factor = factor;
}
@Override
public float getInterpolation(float input) {
return (float) (Math.pow(2, -10 * input) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1);
}
}
顺便提一下其他实现这种弹性动画的库: 1、Facebook推出的rebound库,这里是github链接。 2、谷歌官方的SpringAnimation,这里是官网地址。下面是一个官方文档上的效果展示。 要使用基于物理特性的支持库:
dependencies {
def dynamicanimation_version = "1.0.0"
implementation 'androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version'
}
说到这个库顺便说一下FlingAnimation投掷动画,投掷动画利用与对象速度成正比的摩擦力。您可以使用该动画为某个对象的某个属性添加动画效果,还可以使用该动画逐渐结束动画。该动画具有一个初始动量,主要从手势速度获得,然后逐渐变慢。 当动画速度足够低,在设备屏幕上没有任何可见变化时,动画便会结束。
2.3 帧动画
简单讲就是把几个静态的图片快速播放形成动画,可以使用AnimationDrawable,官方推荐使用XML文件,放在res/drawable/路径下
2.3.1 实现
xml中实现:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
<item android:drawable="@drawable/run_0" android:duration="100"/>
<item android:drawable="@drawable/run_1" android:duration="100"/>
<item android:drawable="@drawable/run_2" android:duration="100"/>
<item android:drawable="@drawable/run_3" android:duration="100"/>
<item android:drawable="@drawable/run_4" android:duration="100"/>
<item android:drawable="@drawable/run_5" android:duration="100"/>
</animation-list>
oneshot属性,用于设置是否需要循环播放,true为仅播放一次,false 为连续的循环播放。
AnimationDrawable animationDrawable = (AnimationDrawable) getResources().getDrawable(R.drawable.view_frame_anim);
imageView.setBackground(animationDrawable);
animationDrawable.start();
特别注意: AnimationDrawable 调用的 start()方法不能在 Activity 的 onCreate() 方法期间调用,因为 AnimationDrawable 尚未完全附加到窗口。如果您想立即播放动画而无需互动,那么需要从Activity 中的 onStart() 方法进行调用。
2.3.2 优化
由于需要多张图片,apk的体积会比较大,内存开销也会比较大。目前我认为比较好的方案: 1、将图片资源适当压缩,放到sd卡,预值或者网络下载,这样可以有效减小apk体积。 2、动画实现方式上不使用原生的方式,可以通过opengl去渲染。 3、可以通过播放视频的方式代替多张图片实现动画,视频文件可以压缩到很小。第三方视频动画框架:腾讯VAP(Video Animation Player),字节AlphaPlayer。
2.4 属性动画
需要属性动画的原因:是因为前面两种动画存在一些缺点,1、作用对象有限:只能够作用在View上 为了弥补视图动画的缺陷,于是Android在3.0(API 11)开始提供了一种全新的动画模式:属性动画(Property Animation)。属性动画,顾名思义,只要对象里有属性的set和get方法,就可以利用这个属性去做动画。
- 两个使用方法类:
ValueAnimator 类 和 ObjectAnimator 类 - 两个辅助使用类:插值器 & 估值器
属性动画支持使用 XML 声明属性动画,也可以以代码方式实现。属性动画的 XML 文件保存到 res/animator/ 目录中。对应标签:
- ValueAnimator -
<animator> - ObjectAnimator -
<objectAnimator> - AnimatorSet -
<set>
部分常用属性: android:valueTo 浮点数、整数或颜色。必需。动画的结束值。 android:valueFrom 浮点数、整数或颜色。必需。动画的开始值。 android:duration 整数。动画的时间,以毫秒为单位。默认为 300ms。 android:startOffset 整数。调用 start() 后动画延迟的毫秒数。。 android:repeatCount 整数。动画的重复次数。设为 “-1” 表示无限次重复,也可设为正整数。例如,值 “1” 表示动画在初次播放后重复播放一次,因此动画总共播放两次。默认值为 “0”,表示不重复。 android:repeatMode 整数。动画播放到结尾处的行为。 android:repeatCount 必须设置为正整数或 “-1”,该属性才有效。设置为 “reverse” 可让动画在每次迭代时反向播放,设置为 “repeat” 则可让动画每次从头开始循环播放。 android:valueType 关键字。intType动画值为整数,floatType(默认)动画值为浮点数 android:propertyName 字符串。必需。要添加动画的对象属性,通过其名称引用。例如,可以为 View 对象指定 “alpha ” 或 “backgroundColor ”。
xml示例:
<set android:ordering="sequentially">//依序播放此集合中的动画
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
代码中使用:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context,
R.animator.property_animator);
set.setTarget(Object);
set.start();
2.4.1 ValueAnimator
通过不断控制值的变化,再不断手动赋给对象的属性,从而实现动画效果。 ValueAnimator类中有3个重要方法:
ValueAnimator.ofInt(int values) ValueAnimator.ofFloat(float values) ValueAnimator.ofObject(int values)
用代码实现,以及动画监听:
ValueAnimator anim = ValueAnimator.ofInt(btn.getLayoutParams().width, 900);
// 初始值 = 当前按钮的宽度 结束值 = 900
// 允许传入一个或多个Int参数
// 1. 输入一个的情况(如a):从0过渡到a;
// 2. 输入多个的情况(如a,b,c):先从a平滑过渡到b,再从b平滑过渡到C
anim.setDuration(3000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获得改变后的值
int currentValue = (int) animation.getAnimatedValue();
// 将改变后的值赋给对象的属性值
btn.getLayoutParams().width = currentValue;
// 刷新视图,重新绘制
btn.requestLayout();
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
anim.start();
}
});
//anim.cancel();
动画效果如下:
动画监听,需要重写四个方法,通过监听动画开始 / 结束 / 重复 / 取消时刻来进行一系列操作.
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
AnimatorSet类、ValueAnimator、ObjectAnimator都可以使用addListener()监听器。有些时候我们并不需要监听动画的所有时刻,上面监听器是必须重写4个方法,所以可以采用动画适配器 AnimatorListenerAdapter 监听。
anim.addListener(new AnimatorListenerAdapter() {
// 向addListener()方法中传入适配器对象AnimatorListenerAdapter()
// 由于AnimatorListenerAdapter中已经实现好每个接口
// 所以这里不实现全部方法也不会报错
@Override
public void onAnimationStart(Animator animation) {
// 如想只想监听动画开始时刻,就只需要单独重写该方法就可以
}
});
ValueAnimator.ofInt()与ValueAnimator.oFloat()仅仅只是在估值器上的区别:
- ValueAnimator.oFloat()采用默认的浮点型估值器 (FloatEvaluator)
- ValueAnimator.ofInt()采用默认的整型估值器(IntEvaluator)
插值器(Interpolator)决定动画的变化速度(匀速、加速) 估值器(TypeEvaluator)决定属性值的在动画不同完成程度的值 那么如何从初始值过渡到结束值呢? 其实是系统内置了一个 FloatEvaluator估值器,内部实现了初始值与结束值以浮点型的过渡逻辑. 下边看一下具体实现:
//实现了TypeEvaluator接口
public class FloatEvaluator implements TypeEvaluator {
//重写evaluate()方法
public Object evaluate(float fraction, Object startValue, Object endValue) {
// 用结束值减去初始值,算出它们之间的差值
// 用上述差值乘以fraction系数
//fraction:表示动画完成度(根据它来计算当前动画的值),也是插值器getInterpolation()的返回值
// 再加上初始值,就得到当前动画的值
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
ValueAnimator.ofObject()的本质还是操作值,只是采用将值封装到一个对象里的方式对值操作。下边自定义估值器会介绍。
2.4.2 ObjectAnimator
ObjectAnimator animator = ObjectAnimator.ofInt(Object target, String propertyName, int… values) ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float …values); ObjectAnimator animator = ObjectAnimator.ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object… values) // Object object:需要操作的对象 // String property:需要操作的对象的属性 // float …values:动画初始值 & 结束值(不固定长度) //TypeEvaluator evaluator 估值器 // 若是两个参数a,b,则动画效果则是从属性的a值到b值 // 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值 // 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator anim.setDuration(500) ;// 设置动画运行的时长 anim.setStartDelay(500) ;// 设置动画延迟播放时间 anim.setRepeatCount(0) ;// 设置动画重复播放次数 = 重放次数+1;动画播放次数 = infinite时,动画无限重复 anim.setRepeatMode(ValueAnimator.RESTART) ;// 设置重复播放动画模式 // ValueAnimator.RESTART(默认):正序重放 // ValueAnimator.REVERSE:倒序回放
2.4.2.1 平移
动画效果: java代码实现:
float translationX = btn.getTranslationX();
ObjectAnimator animator = ObjectAnimator.ofFloat(btn, View.TRANSLATION_X, translationX, 300, translationX);
animator.setDuration(5000);
animator.start();
2.4.2.2 缩放
动画效果: java代码:
ObjectAnimator animator = ObjectAnimator.ofFloat(btn, View.SCALE_X, 1f, 0.5f, 1f);
animator.setDuration(5000);
animator.start();
2.4.2.3 旋转
动画效果: java代码:
ObjectAnimator animator = ObjectAnimator.ofFloat(btn, View.ROTATION, 0f, 360f);
animator.setDuration(5000);
animator.start();
2.4.2.4 透明度
动画效果:
java代码:
ObjectAnimator animator = ObjectAnimator.ofFloat(btn, View.ALPHA, 0f, 0.5f, 1f, 0f);
animator.setDuration(5000);
animator.start();
2.4.2.5 组合动画
AnimatorSet.play(Animator anim) :播放当前动画 AnimatorSet.after(long delay) :将现有动画延迟x毫秒后执行 AnimatorSet.with(Animator anim) :将现有动画和传入的动画同时执行 AnimatorSet.after(Animator anim) :将现有动画插入到传入的动画之后执行 AnimatorSet.before(Animator anim) : 将现有动画插入到传入的动画之前执行 AnimatorSet.playTogether(Animator... items) :几个动画同时播放
动画效果: java代码:
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animatorTranslation, animatorAlpha, animatorScale, animatorRotation);
animatorSet.start();
2.4.3 ViewPropertyAnimator
属性动画的一种简写方式: ViewPropertyAnimator 有助于使用单个底层 Animator 对象轻松为 View 的多个属性并行添加动画效果。它的行为方式与 ObjectAnimator 非常相似,因为它会修改视图属性的实际值,但在同时为多个属性添加动画效果时,它更为高效。此外,使用 ViewPropertyAnimator 的代码更加简洁,也更易读。 View.animate().xxx().xxx(); // 调用animate()方法返回值是一个ViewPropertyAnimator对象,之后的调用的所有方法都是通过该实例完成。 // 调用该实例的各种方法来实现动画效果。 // ViewPropertyAnimator所有接口方法都使用连缀语法来设计,每个方法的返回值都是它自身的实例 // 因此调用完一个方法后可直接连缀调用另一方法,即可通过一行代码就完成所有动画效果。
多个ObjectAnimator对象
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY", 50f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
单个ObjectAnimator对象
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", 50f);
ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY).start();
ViewPropertyAnimator
view.animate(). translationX(50f).translationY(50f);
2.4.4 Keyframe
如果要控制动画速率的变化,我们可以通过自定义插值器,也可以通过自定义估值器来实现。 为了解决方便的控制动画速率的问题,谷歌定义了一个Keyframe的类,Keyframe直译过来就是关键帧,意思就是我们只需要定义关键的帧,中间的部分系统会自动补上。 使用keyframe分三步:
第一步:生成Keyframe对象。 第二步:利用PropertyValuesHolder.ofKeyframe()生成PropertyValuesHolder对象。 第三步:ObjectAnimator.ofPropertyValuesHolder()生成对应的Animator对象。
关键帧的写法,这里用前面的平移动画举例: 代码实现:
//public static Keyframe ofFloat(float fraction, float value)
Keyframe kf0 = Keyframe.ofFloat(0, translationX); //动画进度0%时的值
Keyframe kf1 = Keyframe.ofFloat(0.5f, 300);
//kf0到kf1之间的插值器
kf1.setInterpolator(new LinearInterpolator());
Keyframe kf2 = Keyframe.ofFloat(1f, translationX); //动画进度100%时的值
//kf1到kf2之间的插值器
kf2.setInterpolator(new BounceInterpolator());
PropertyValuesHolder propertyValuesHolder = PropertyValuesHolder.ofKeyframe(View.TRANSLATION_X, kf0, kf1, kf2);
ObjectAnimator animatorTranslation = ObjectAnimator.ofPropertyValuesHolder(btn, propertyValuesHolder);
animatorTranslation.setDuration(5000);
animatorTranslation.start();
Keyframe可以定义自己的Interpolator,用来定义从上一个Keyframe到下一个Keyframe之间的动画变化规则,有了关键帧后,我们可以把一个大动画拆分成多个小动画,每个小动画可以定义自己的变化规则。 通过ObjectAnimator.ofFloat,ObjectAnimator.ofInt,ObjectAnimator.ofObject构造的属性动画,内部都会为其创建一个对应的PropertyValuesHolder,PropertyValuesHolder类是属性动画属性值的一个处理类,因为只有一个PropertyValuesHolder所以构造的属性动画只能改变一个属性值,如果你想在一个属性动画里同时改变多个属性值,那么就需要用ObjectAnimator.ofPropertyValuesHolder来构造属性动画,通过参数传入多PropertyValuesHolder。 直接跟Keyframe产生关系的是PropertyValuesHolder,而且一个PropertyValuesHolder会对应一个Keyframe集合,因为一个动画至少存在两个状态,开始状态和结束状态,所以至少会有两个Keyframe,当然我们也可以通过增加Keyframe来描述中间的状态。 为了描述PropertyValuesHolder和Keyframe一对多的关系,Android提供了KeyframeSet类来管理多个Keyframe,每个KeyframeSet都会实现Keyframes的接口,PropertyValuesHolder直接依赖于Keyframes。关系图如下:
2.4.5 自定义估值器
自定义估值器,先看效果:
估值器代码实现:
public class CharEvaluator implements TypeEvaluator<Character> {
@Override
public Character evaluate(float fraction, Character startValue, Character endValue) {
//在ASCII码表中,每个字符都是有数字跟他一一对应的,字母A到字母Z之间的所有字母对应的数字区间为65到90;在程序中,我们能通过数字强转成对应的字符,也可以通过字符转成数字。
int startInt = (int)startValue;
int endInt = (int)endValue;
int curInt = (int)(startInt + fraction *(endInt - startInt));
char result = (char)curInt;
return result;
}
}
动画代码实现:
ObjectAnimator colorAnimator = ObjectAnimator.ofInt(btn, "BackgroundColor", 0xffffffff, 0xffff00ff, 0xffffff00, 0xffffffff);
ObjectAnimator animator = ObjectAnimator.ofObject(btn, "CharText", new CharEvaluator(), new Character('A'), new Character('Z'));
AnimatorSet set = new AnimatorSet();
set.play(colorAnimator).with(animator);
set.setDuration(3000);
set.start();
自定义的按钮,增加CharText属性。
public class CharButton extends Button {
public CharButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setCharText(Character character) {
setText(String.valueOf(character));
}
}
三、动画原理分析
3.1 属性动画原理
(1) 动画是由许多的关键帧组成的,这是一个动画能够动起来的最基本的原理。 (2) 属性动画的主要组成是 PropertyValuesHolder,而 PropertyValuesHolder 又封装了关键帧。 (3) 动画开始后,其监听了 Choreographer 的 vsync,使得其可以不断地调用 doAnimationFrame() 来驱动动画执行每一个关键帧。 (4) 每一次的 doAnimationFrame() 调用都会去计算时间插值,而通过时间插值器计算得到 fraction 又会传给估值器,使得估值器可以计算出属性的当前值。 (5) 最后再通过 PropertyValuesHolder 所记录下的 Setter 方法,以反射的方式来修改目标属性的值。当属性值一帧一帧的改变后,形成连续后,便是我们所见到的动画。
3.2 属性动画与补间动画的区别
补间动画存在一些限制,它只支持对象的一部分来添加动画效果;例如,可以对视图的缩放和旋转添加动画效果,但无法对背景颜色添加动画效果。
补间动画的另一个缺点是它只会在绘制视图的位置进行修改,而不会修改实际的视图本身。例如,如果您为某个按钮添加了动画效果,使其可以在屏幕上移动,该按钮会正确绘制,但能够点击按钮的实际位置并不会更改。
属性动画在动画效果方面更丰富,例如颜色、位置或大小等。
四、动画框架
4.1 Lottie
Lottie (官网链接) ,(github地址)是 Airbnb开源的一套跨平台的完整的动画效果解决方案,设计师可以使用 Adobe After Effects 设计出漂亮的动画之后,使用 Lottie 提供的 Bodymovin 插件将设计好的动画导出成 JSON 格式,就可以直接运用在 iOS、Android、Web 和 React Native之上,无需其他额外操作。 最主要的两个类:
LottieAnimationView :继承自 AppCompatImageView,是加载 Lottie 动画的默认和最简单的方式。 LottieDrawable :继承自 Drawable,具有大多数与 LottieAnimationView 相同的 API,因此可以在任何视图上使用它。 常用属性: lottie_fileName 设置播放动画的 json 文件名称 ,在app/src/main/assets目录下 lottie_rawRes 设置播放动画的 json 文件资源 lottie_autoPlay 设置动画是否自动播放 lottie_loop 设置动画是否循环 lottie_repeatMode 设置动画的重复模式 lottie_repeatCount 设置动画的重复次数 lottie_scale 设置动画的比例(默认为1f) lottie_progress 设置动画的播放进度 lottie_cacheStrategy 设置动画的缓存策略(默认为weak) : CacheStrategy 可以是None、Weak 和 Strong 三种形式来让 LottieAnimationView 对加载和解析动画的使用强或弱引用的方式。弱或强表示缓存中组合的 GC 引用强度。 lottie_imageAssetsFolder 设置动画依赖的图片资源文件地址
在 res/raw 或 assets/ 中存放动画的 JSON 文件,然后就可以在 xml 中直接使用,如下: 下边是一个小动画,先上效果:
gradle中配置:
dependencies {
implementation 'com.airbnb.android:lottie:4.1.0'
}
xml使用:
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottieView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:lottie_rawRes="@raw/voice_anim"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_repeatMode="restart"/>
在代码中实现,用代码加载本地动画资源。
mLottieView.setAnimation(R.raw.voice_anim);
mLottieView.setRepeatMode(LottieDrawable.RESTART);
//mLottieView.setAnimation("find_anchor.json");//assetName
//动画里有图片,有些图片资源使用本地的图片资源
//mLottieView.setImageAssetsFolder("images/");//src/main/assets/images/
mLottieView.addAnimatorListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
mLottieView.cancelAnimation();//取消动画
}
});
mLottieView.addAnimatorUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
mLottieView.playAnimation();//开始
mLottieView.pauseAnimation();//暂停
mLottieView.resumeAnimation();//继续
mLottieView.cancelAnimation();//取消
4.2 腾讯PAG
PAG(Portable Animated Graphics)是一套完整的动画工作流。提供从AE(Adobe After Effects)导出插件,到桌面预览工具,再到各端的跨平台渲染 SDK。PAG 的目标是降低或消除动画研发成本,打通设计师创作到素材交付上线的流程,不断输出运行时可编辑的高质量动画内容。 PAG接入文档 下面是一个小动画,先上效果: xml中的写法:
<org.libpag.PAGView
android:id="@+id/pag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
java代码:
PAGFile pagFile = PAGFile.Load(getAssets(),"gift_icon.pag");
//也可以加载本地sd卡的文件
mPAGView.setFile(pagFile);
//动画监听
mPAGView.addListener(new PAGView.PAGViewListener() {
@Override
public void onAnimationStart(PAGView pagView) {
}
@Override
public void onAnimationEnd(PAGView pagView) {
}
@Override
public void onAnimationCancel(PAGView pagView) {
}
@Override
public void onAnimationRepeat(PAGView pagView) {
mPAGView.stop();//停止动画
}
});
mPAGView.setRepeatCount(-1);
mPAGView.play();
4.3 Lottie vs PAG
Lottie 最早是为了解决矢量图形类动画的问题。Lottie 库和插件是 Airbnb 于2017年前后发布的一款跨平台的动画解决方案,设计师通过 bodymovin 从 AE 中将动画导出 json 文件,开发只需将其导入资源文件夹直接引用即可。
Lottie 早期的版本不支持图片类动画,导出 json 之后会自动生成一个 img 的资源文件夹,播放 .json 文件时,需要解压资源压缩包到本地目录才能正常播放。从 bodymovin V 5.1.15 之后,Lottie 将图片转为 base 64 编码,使用字符代替图像地址,并封装在 json 里,直接播放一个 .json 文件,不用再拖着一个资源文件夹了。
PAG SDK整套方案是基于 C++ 和 OpenGL 的跨平台架构研发的,不依赖平台相关的UI框架,除了能做到跨端渲染完全一致外,还能轻松移植到各个原生平台,其中也包含服务器端的渲染能力。
在性能方面,PAG应用了游戏渲染里的大量的优化经验,设计了从中间渲染数据到局部位图的多级缓存架构,加上帧预测的技术,每帧渲染耗时平均可以做到Lottie的50%左右。
由于采用二进制格式,不存在JSON的字符串解析,解码耗时平均比Lottie文件的快12倍,相同的动画内容导出文件只有Lottie一半左右大小,同时二进制文件格式也更容易做到单文件集成图片,音频,视频等任意资源。
PAG 方案的优势:
1、文件更小
PAG是二进制文件格式,并采用了可变长编码整形以及动态按位聚合这些压缩技术,让相同动画导出的文件大小平均只有 Lottie 的一半左右(都经过zip压缩后对比)。PAG 除了对特效类动画的支持可圈可点,对矢量动画的支持也是非常优秀,甚至强于 Lottie。以一个生长动画为例,Lottie 文件 14k,而 PAG 文件只有1k。
2、解码更快
由于采用二进制格式,不存在JSON的字符串解析,解码耗时平均只有Lottie文件的7.6%,同时二进制文件格式也更容易做到单文件集成图片,音频,视频等任意资源。
3、支持更多AE特性
PAG目前支持Lottie在移动端几乎所有的功能,并且额外在文本,遮罩,滤镜方面比Lottie支持更加全面。除了矢量导出,PAG还增加了视频序列帧导出,能够支持所有的AE特性。
4、性能更好
基于 C++ 和 OpenGL 硬件加速渲染,除了能做到两端渲染完全一致外,应用了游戏渲染里的大量的优化经验,从中间渲染数据到局部位图的多级缓存架构,每帧渲染耗时平均可以做到Lottie的50%左右。
5、编辑性更高
除了运行时文本编辑和占位图替换功能外,PAG还支持图层级别的任意组合修改。为复杂的应用场景提供更加灵活的编程扩展能力。
6、支持服务端渲染
PAG支持服务端渲染能力,以C++方式接入,可以支持服务端照片转特效视频以及一键大片模板等功能,结合H5快速实现运营活动页。
|