Android动画

Android动画

原理:不停修改view的不同属性,刷新

动画实现方式

GIf

一般Gif或者帧动画 的性能差是由于图过多,每秒种几十帧,每一帧都是一张图。一个小动画往往需要几十张图。如果变成资源下载,资源包体积难以控制。

原生

Native 动画实则是属性动画或者补间动画。性能相比较gif而言性能比较好,但是有大量动画的需求时,往往生产力是不够的,一开始手动码动画,一个动画最多时写了3000多行code。
Native 动画优缺点:
开发成本高
必须发版
不能完全100%还原复杂动画,调参数比较繁
琐图片资源大,影响apk包大小

SVG

SVG图片格式,一种矢量图形。另一个角度来讲一张图或者一个动画,是由很多上下层级的图层构成。比如当前的简单的图,看到的是一张图,但在设计工具中是三个图层构成,有着不同的上下层级顺序。
原理:通过设置帧率,来生成一个配置文件,使得每一帧都有一个配置,每一帧都是关键帧,通过帧率去刷每一帧的画面,这个思路跟gif很像,但是通过配置使得动画过程中图片都可以得到复用。性能就提升上来了。

Lottie

完全按照设计工具的设计思路来进行还原,将动画脚本导出并解析。动画脚本非常的轻量。将所有的动画拆成多个层级,每个层级layer都有一个动画配置,播放时解析多个layer的配置,并给每个layer做相应的动画。也达到了图片可以复用。当需要解析高阶插值(二次线性方程,贝塞尔曲线方程)时,性能相对而言差一点。

Lottie使用注意
都是canvas 画布操作
lottie动画很卡顿,不流畅//硬件加速,开启之后瞬间丝滑
遮罩或者蒙版,性能将会受到影响。
如果你在一个列表中使用动画, 我们建议你配置 LottieAnimationView.setAnimation(String, CacheStrategy) 的第二个参数——缓存策略,这样动画就不必每次都反序列化。

Android中的动画

三种:补间动画、帧动画、属性动画。
补间动画是放置到res/anim/下面
帧动画是放置到res/drawable/下面,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长

上下左右浮动效果

1
2
3
4
5
6
7
8
9
10
11
12
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(this, "translationY", -6.0f,6.0f,-6.0f);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.setDuration(800);
objectAnimator.start();

animator2 = ObjectAnimator.ofFloat(ivHand2, "translationY", -20,20,-20);
animator2.setRepeatMode(ValueAnimator.RESTART);
animator2.setRepeatCount(-1);
animator2.setDuration(1000);
animator2.start();


帧动画

传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影,一张张图片不断的切换,形成动画效果,要自己指定每一帧。

  • 帧动画使用xml定义
    在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长

    1
    2
    3
    4
    5
    <animation-list xmlns:androandroid:oneshot="false">
    <item android:drawable="@drawable/g1" android:duration="200" />
    <item android:drawable="@drawable/g2" android:duration="200" />
    <item android:drawable="@drawable/g3" android:duration="200" />
    </animation-list>
  • 在屏幕上播放帧动画

    1
    2
    3
    4
    5
    6
    ImageView iv = (ImageView) findViewById(R.id.iv);
    //把动画文件设置为imageView的背景
    iv.setBackgroundResource(R.drawable.animations);
    AnimationDrawable ad = (AnimationDrawable) iv.getBackground();
    //播放动画
    ad.start();

补间动画

  1. 如果动画中的图像变换比较有规律时,例如图像的移动(TranslateAnimation)、旋转(RotateAnimation)、缩放(ScaleAnimation)、透明度渐变(AlphaAnimation),这些图像变化过程中的图像都可以根据一定的算法自动生成,我们只需要指定动画的第一帧和最后一帧图像即可,这种自动生成中间图像的动画就是补间动画。
  2. 补间动画,只是一个动画效果,组件其实还在原来的位置上,xy没有改变

属性动画

  1. 补间动画改变了View的显示效果而已,而不会真正去改变View的属性,比如说,屏幕的左上角有一个按钮,然后通过补间动画将它移动到了屏幕的右下角,现在去尝试点击这个按钮,点击事件是绝对不会触发的,因为实际上这个按钮还是停留在屏幕的左上角,只不过补间动画将这个按钮绘制到了屏幕的右下角而已。
  2. 动画的对象除了传统的View对象,还可以是Object对象,动画结束后,Object对象的属性值被实实在在的改变了。

ViewPropertyAnimator

使⽤ View.animate() 创建对象,以及使⽤ViewPropertyAnimator.translationX() 等⽅法来设置动画;
可以连续调⽤来设置多个动画;
可以⽤ setDuration() 来设置持续时间;
可以⽤ setStartDelay() 来设置开始延时;
以及其他⼀些便捷⽅法。

view.animate().translationX(500);
unknown_filename

ValueAnimator

ValueAnimator是整个属性动画机制当中最核心的一个类,属性动画的运行机制是通过不断地对值进行操作来实现的,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等,是一个非常重要的类。
可以在动画多的时候用

1
2
3
4
5
6
7
8
9
10
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();

ObjectAnimator

相比于ValueAnimator,ObjectAnimator可能才是我们最常接触到的类,因为ValueAnimator只不过是对值进行了一个平滑的动画过渡。而ObjectAnimator则就不同了,它是可以直接对任意对象的任意属性进行动画操作的,比如说View的alpha属性。 它其实是继承自ValueAnimator的,底层的动画实现机制也是基于ValueAnimator来完成的,因此ValueAnimator仍然是整个属性动画当中最核心的一个类。

1
2
3
4
5
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "translationX", 0, 100) ;//位移
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "scaleY", 0.1f, 2);//缩放
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "alpha", 0.1f, 1);//透明
ObjectAnimator oa = ObjectAnimator.ofFloat(bt, "rotation", 20, 270);//旋转
oa.start();

属性动画的优势在于,可以为⾃定义属性设置动画。

1
ObjectAnimator animator = ObjectAnimator.ofObject(view,"radius", Utils.dp2px(200));

另外,⾃定义属性需要设置 getter 和 setter ⽅法,并且 setter ⽅法⾥需要调⽤invalidate() 来触发重绘:

1
2
3
4
5
6
7
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
invalidate();
}

设置旋转起点

1
2
3
4
5
6
mArrowImageView.setPivotX(mArrowImageView.getMeasureWidth() / 2);
mArrowImageView.setPivotY(mArrowImageView.getMeasureHeight() / 2);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(
mArrowImageView, "rotate", fromDegress, toDegress);
objectAnimator.setDuration(100);
objectAnimator.start();

可以使用reverse,就不需要重新创建个动画了

Interpolator

插值器,⽤于设置时间完成度到动画完成度的计算公式,直⽩地说即设置动画的速度曲线,通过 setInterpolator(Interpolator) ⽅法来设置。常⽤的有 AccelerateDecelerateInterpolator、
AccelerateInterpolator、DecelerateInterpolator、LinearInterpolator 。

差值器和估值器是什么?
Interpolator 负责控制动画变化的速率,使得基本动画能够以匀速、加速、减速、抛物线速率等各种速率变化。
TypeEvaluator 设置属性值,从初始值过度到结束值的变化具体数值。

PropertyValuesHolder

⽤于设置更加详细的动画,例如多个属性应⽤于同⼀个对象:

1
2
3
4
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("radius", 
Utils.dp2px(200));
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("offset", Utils.dp2px(100));
ObjectAnimator animator = PropertyValuesHolder.ofPropertyValuesHolder(view, holder1, holder2);

或者,配合使⽤ Keyframe ,对⼀个属性分多个段:

1
2
3
4
5
6
Keyframe keyframe1 = Keyframe.ofFloat(0,Utils.dpToPixel(100));
Keyframe keyframe2 = Keyframe.ofFloat(0.5f,Utils.dpToPixel(250));
Keyframe keyframe3 = Keyframe.ofFloat(1,Utils.dpToPixel(200));
PropertyValuesHolder holder =PropertyValuesHolder.ofKeyframe("radius", keyframe1,
keyframe2, keyframe3);
ObjectAnimator animator =ObjectAnimator.ofPropertyValuesHolder(view, holder);

AnimatorSet

将多个 Animator 合并在⼀起使⽤,先后顺序或并列顺序都可以:

1
2
3
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(animator1, animator2);
animatorSet.start();

TypeEvaluator

⽤于设置动画完成度到属性具体值的计算公式。默认的 ofInt() ofFloat() 已经有了⾃带的 IntEvaluator FloatEvaluator ,但有的时候需要⾃⼰设置Evaluator。例如,对于颜⾊,需要为 int 类型的颜⾊设置 ArgbEvaluator,⽽不是让它们使⽤ IntEvaluator

1
animator.setEvaluator(new ArgbEvaluator());

如果你对 ArgbEvaluator 的效果不满意,也可以⾃⼰写⼀个 HsvEvaluator :

1
2
3
4
5
6
7
8
public class HsvEvaluator implements
TypeEvaluator<Integer> {
@Override
public Object evaluate(float fraction, Object
startValue, Object endValue) {
...
}
}

另外,对于不⽀持的类型,也可以使⽤ ofObject() 来在创建 Animator 的同时就设置上 Evaluator,⽐如 NameEvaluator :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NameEvaluator implements        
TypeEvaluator<String> {   
List<String> names = ...;   
@Override   
public String evaluate(float fraction, String  startValue, String endValue) {       
if (!names.contains(startValue)) {           
throw new IllegalArgumentException("Start value not existed");       
}       
if (!names.contains(endValue)) {           
throw new IllegalArgumentException("Endvalue not existed");       
}       
int index = (int) ((names.indexOf(endValue) - names.indexOf(startValue)) * fraction);       
return names.get(index);   
}
}
ObjectAnimator animator = ObjectAnimator.ofObject(view, "name", new NameEvaluator(), "Jack");

硬件加速

硬件加速是什么
使⽤ CPU 绘制到 Bitmap,然后把 Bitmap 贴到屏幕,就是软件绘制;
使⽤ CPU 把绘制内容转换成 GPU 操作,交给 GPU,由 GPU 负责真正的绘制,就叫硬件绘制;
使⽤ GPU 绘制就叫做硬件加速
怎么就加速了?
GPU 分摊了⼯作
GPU 绘制简单图形(例如⽅形、圆形、直线)在硬件设计上具有先天优势,会更快流程得到优化(重绘流程涉及的内容更少)
硬件加速的缺陷:
兼容性。由于使⽤ GPU 的绘制(暂时)⽆法完成某些绘制,因此对于⼀些特定的API,需要关闭硬件加速来转回到使⽤ CPU 进⾏绘制。

view动画的特殊使用场景

activity overridePendingTransition,可以是补间(下面的),也可以是熟悉动画
FragmentTransaction中的 setCustomAnimations 方法(不能是属性动画)

Image
Image.1

补间动画总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
alpha.xml:开端、最初
<alpha xmlns:androandroid:fromAlpha="0.0" 开始透明度的值,完全不透明
android:toAlpha="1.0" 结束透明度的值,完全透明
android:duration="2000"
android:repeatCount="0" 动画效果重复几次
android:repeatMode="restart" 动画效果重复的模式 参数有重新开始和倒着执行的模式
android:interpolator="@android:anim/decelerate_interpolator"减速加速器
>
</alpha>

rotate.xml:旋转、循环
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:androandroid:fromDegrees="0" 旋转开始的角度
android:toDegrees="90" 旋转结束的角度
android:pivotX="50%p" 代表当前的中间位置 加上p的意思是当前父布局管理器中间位置
android:pivotY="50%p" 代表当前的中间位置
android:duration="2000"
>
</rotate>

scale.xml:规模、比例
<scale xmlns:androandroid:duration="2000"
android:pivotx="0.0"
android:pivoty="50.0" Y轴中间
android:fromXScale="0.0" 开始x轴比例
android:toXScale="2.0" 结束x轴比例
android:fromYScale="0.0" 开始x轴比例
android:toYScale="2.0"
android:repeatMode="reverse" 重复相反的模式
android:repeatCount="1"
>
</scale>

translate.xml:转换、调动、解释、翻译
<translate xmlns:androandroid:duration="2000"
android:fromXDelta="0" 开始像素
android:toXDelta="100" 水平平移结束像素
android:fromYDelta="0"
android:toYDelta="100"
android:startOffset="1000" 动画开始时间 1秒之后开始播放动画
>
</translate>

动画
tween动画是放置到res/anim/下面
frame动画是放置到res/drawable/下面
动画Demo
public class DemoActivity extends Activity {
private ImageView iv;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
iv = (ImageView) this.findViewById(R.id.iv);
}
/**
* 播放透明度变化的动画
*
*/
public void alpha(View view){
//第一个参数是开始完全透明,第二个参数是完全不透明
// AlphaAnimation aa = new AlphaAnimation(0.0f, 1.0f);
// aa.setDuration(2000);
//AnimationUtils动画工具类,把一个资源文件转换成一个动画效果
Animation aa = AnimationUtils.loadAnimation(this, R.anim.alpha);
iv.startAnimation(aa);
}
/**
* 播放旋转变化的动画
*
*/
public void rotate(View view){
//第一个参数是从0角度开始,第二个参数到90角度结束
//RotateAnimation ra = new RotateAnimation(0, 90);
//RotateAnimation ra = new RotateAnimation(0, 90, (iv.getRight()+iv.getLeft())/2, (iv.getTop()+iv.getBottom())/2);
// 怎么去定义旋转的中间位置?
// RotateAnimation ra = new RotateAnimation(0, 90, 0.5f, 0.5f);
// ra.setDuration(2000);
Animation ra = AnimationUtils.loadAnimation(this, R.anim.rotate);
iv.startAnimation(ra);
}
//缩放的动画
public void scale(View view){
//第一二个参数是从x轴完全不可见到完全可见,第三四个参数是从y轴完全不可见到完全可见
// ScaleAnimation sa = new ScaleAnimation(0.0f, 2.0f, 0.0f, 2.0f);
// sa.setDuration(2000);
Animation sa = AnimationUtils.loadAnimation(this, R.anim.scale);
iv.startAnimation(sa);
}
//偏移的动画
public void translate(View view){
//第一个参数偏移开始x轴坐标,第二个参数偏移结束x轴坐标,第三个参数偏移开始y轴的坐标,第四个参数偏移结束y轴的坐标
// TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 200);
// ta.setDuration(2000);
Animation ta = AnimationUtils.loadAnimation(this, R.anim.translate);
iv.startAnimation(ta);
}
/**
* 动画的组合
*/
public void set(View view){
// AlphaAnimation aa = new AlphaAnimation(0.0f, 1.0f);
// aa.setDuration(2000);
// RotateAnimation ra = new RotateAnimation(0, 90);
// ra.setDuration(2000);
// TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 200);
// ta.setDuration(2000);
// AnimationSet set = new AnimationSet(false);
// set.addAnimation(ta);
// set.addAnimation(ra);
// set.addAnimation(aa);
// iv.startAnimation(set);
Animation aa = AnimationUtils.loadAnimation(this, R.anim.set);
iv.startAnimation(aa);
}
}

public class FrameActivity extends Activity {
private ImageView iv ;
private AnimationDrawable drawable;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
iv = (ImageView) this.findViewById(R.id.iv);
iv.setBackgroundResource(R.drawable.list);
drawable = (AnimationDrawable) iv.getBackground();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction()==MotionEvent.ACTION_DOWN){
//开始播放帧动画
drawable.start();
}
return super.onTouchEvent(event);
}
}

Android动画
http://peiniwan.github.io/2024/04/cf63d9e8503d.html
作者
六月的雨
发布于
2024年4月6日
许可协议