最近整理了下自己学过的动画方面的知识.用百度脑图做了动画知识的思维脑图,哪里如果觉得不对,大家可以留言提出哦.
你没看错,掘金的文章的图片,电脑上看这种思维脑图根本就看不清楚,所以我准备一块块来讲. (掘金手机版 APP 倒是可以放大,看的挺清晰的.)
总结的图已经传到了 Github 上面,可以下载:
AnimationSummay 脑图
动画可以分为两类:Animation 和 Transition 二类.
Animation
因为一般来说第二块 Animation 用的比较多,所以我们先来看 Animation 这块:
好的,我们可以看到我们的 Animation 可以分成:
帧动画
View 动画
Property 动画 (属性动画)
我们可以按顺序一个个来看:
帧动画:
帧动画我就不多说了,就是提前准备好一个连续的图片,然后一张张切换,就类似 gif 图播放一样.注意点就是图片数量过多并且图片较大,容易出现 OOM.
View 动画:
1. 四种基本动画:
我们可以看到,其实 View 动画很简单,基本使用的是 "平移","缩放","旋转","透明度" 四种基本动画.
2. LayoutAnimation 及 界面切换动画:
然后我们看特殊场景下的 View 动画:
LayoutAnimaion : 在 ViewGroup 中,View 动画可以用来控制子元素的出场效果,比如我们的应用中的列表,我们在加载列表中的子项的时候,可以让 item 加载的时候不是突然出现,可以伴随各种动画.
比如:
这里的界面切换动画,与最刚开始的大分类的 Transition 不同,这里的过渡的动画用的是 View 动画,比如 Activity 的切换效果:
其中我们可以看到主要是用到
// 当启动一个 Activity 时
Intent intent = new Intent(this,XXXXX.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);
// 当 Activity 退出时
@Override
public void finish(){
super.finish();
overridePendingTransition(R.anim.enter_anim,R.anim.exit_anim);
}
overridePendingTransition
方法,这个方法必须放在 startActivity() 和 finish() 后面.
3. View 动画注意事项:
这里我们可以看到,View 动画其实并不是真得改变了 View 的状态,比如说我们写了一个按钮,点击按钮可以 Toast 一段内容,通过 Translate 动画从左边平移到了右边,这时候虽然按钮看上去在右边了,但是这时候你点击按钮,并不会出现 Toast 的内容,但是你点击左边的按钮初始位置,却有 Toast 内容.因为其实按钮只是影像移动过去而已.真正的按钮还是在原始位置.
也许有人会问,那如果我就是希望按钮移动到右边后,点击右边的按钮可以有点击事件,你可以选择后面提到的属性动画,或者如果你一定要用 View 动画,那你可以在右边目标位置,提前准备一个一模一样的并且隐藏的按钮,然后当左边的按钮移动到右边后,我们可以设置右边的隐藏的按钮出现,然后把左边的最初的按钮进行隐藏即可.
属性动画:
首先大家可以看下扔物线大佬的相关这个知识点的文章:
HenCoder Android 自定义 View 1-6: 属性动画(上手篇) [HenCoder Android 开发进阶] 自定义 View 1-7:属性动画(进阶篇)
1. ViewPropertyAnimator:
我直接引用了扔物线大佬文章里面的相关动画操作的图片:
用 ViewPropertyAnimator 来做属性动画是最简单的.特别方便.
ViewPropertyAnimator 多个动画进行:
如果想多个动画同时进行,只需要简单的:
view.animate()
.scaleX(1)
.scaleY(1)
.alpha(1);
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();
ObjectAnimation 多个动画同时进行 - PropertyValuesHolder:
ObjectAnimation 在多个动画一起进行的时候不能像 ViewPropertyAnimation 那样方便,不过你可以使用 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();
PropertyValuesHolder
PropertyValuesHolder
中,然后使用
ofPropertyValuesHolder()
统一放进 Animator.这样你就不用为每个属性单独创建一个 Animator 分别执行了.
把同一个属性拆分 除了合并多个属性和调配多个动画,你还可以在
PropertyValuesHolder - Keyframe:
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();
3. ValueAnimator:
ViewPropertyAnimator 和 ObjectAnimator 的底部都是用 ValueAnimator 实现的,从字面意思就可以看出是数值的动画,也就是数值的变化.
比如:
我们可以看到 ValueAnimator 是监听到当前变化到哪个值了.然后你拿着这个值想怎么处理,就是你的事了.所以 ValueAnimator 就更基础.
ValueAnimator valueAnimator = ValueAnimator.ofInt(1,5);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();// 当前的值
}
});
所以你看,ViewPropertyAnimator,ObjectAnimator,ValueAnimator 这三种 Animator,它们其实是一种递进的关系:从左到右依次变得更加难用,也更加灵活.但我要说明一下,它们的性能是一样的,因为 ViewPropertyAnimator 和 ObjectAnimator 的内部实现其实都是 ValueAnimator,ObjectAnimator 更是本来就是 ValueAnimator 的子类,它们三个的性能并没有差别.它们的差别只是使用的便捷性以及功能的灵活性.所以在实际使用时候的选择,只要遵循一个原则就行:尽量用简单的.能用 View.animate() 实现就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator.
4. AnimationSet:
AnimationSet 是用在为什么要把这个拎出来呢.AnimationSet 可以用在多个动画播放,很多人就说了,上面我们在 ValuePropertyAnimator 及 ObjectAnimation 中的 PropertyValuesHolder 已经可以用在多个动画一起播放了吗?没错,问题就出在这个 < 一起 > 这二个字上面,因为上面的二个都是只能 N 个动画同时播放,比如我现在的需求是先平移,然后平移结束后再放大和改变透明度.而 AnimationSet 及可以一起播放,又可以控制动画的先后顺序来.
使用 playSequentially(),就可以让两个动画依次播放,而不用为它们设置监听器来手动为他们监管协作. 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.start(); 以及这么用:
// 两个动画同时执行
animatorSet.playTogether(animator1, animator2);
有了 AnimatorSet ,你就可以对多个 Animator 进行统一规划和管理,让它们按照要求的顺序来工作.
// 使用 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();
5. 插值器 与 估值器
插值器(Interpolator)和 估值器(Evaluator)大家肯定也见过很多次,那这二个到底是用来干嘛的呢?
我们从头来分析一个平移的动画:
我们告诉一个 View,5 秒内,从左到右移动 500px 的距离.
时间经历到了 N 秒的时候,我们要知道整个动画到了哪个程度,比如动画执行了 50% 了.
当动画执行到某个程序的时候(比如执行了 50%),这时候我们的 X 轴的移动距离的值具体是多少 px.
然后 View 通过获取到的具体的移动距离的 px 值,去设置 View 的 translationX 的属性,让 View 去移动.
那我们的插值器和估值器是用在哪里呢:
插值器是用在第二步里面,时间经历了 N 秒,我们返回一个值,这个值是说明当前动画进行到哪个程度了.估值器是用在第三步,我们已经知道了动画执行到了哪个程序,然后我们返回具体的当前变化的数值.
比如我们来看
LinearInterpolator.java
和 IntEvaluator.java 的原来再理解下:
public class LinearInterpolator implements Interpolator{
public LinearInterpolator(){
}
public LinearInterpolator(Context context,AttributeSet attrs){
}
public float getInterpolation(float input){
// 比如时间是 5 秒,这时候过了 2.5 秒,这时候时间流逝了 0.5,所以 input 是 0.5
// 因为我们这个 LiearInterpolator 是线性插值器,
// 所以时间流失了 0.5,我们的动画的动画也执行了 0.5(也就是完成了 50% 的程度)
// 所以这里直接返回 input 即可.
return input;
}
}
public class IntEvaluator implements TypeEvaluator<Integer>{
public Integer evaluate(float fraction,Integer startValue,Integer endValue){
//fraction 就是我们上面插值器返回的,告诉我们动画执行到什么程度了.
//startValue 是我们起始值,endValue 是我们最后的目标值.
// 比如我们是 ofInt(0,500); 这时候我们 return 的值就是 (0+0.5 * (500-0))= 250
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startValue));
}
}
Transition
过于过渡,我也不费心思全部很详细的写出来,网上的基本介绍及使用有很多.
基本知识:
Android 过渡 (Transition) 动画解析之基础篇 酷炫的 Activity 切换动画,打造更好的用户体验
稍微深度过渡动画基本原理:
Activity 和 Fragment Transition 介绍 深入理解 Content Transition 深入理解共享元素变换(Shared Element Transition)- 上
这里我也用过渡写过相关效果的文章:
项目需求讨论 - 用 Transition 做一个漂亮的登录界面
来源: https://juejin.im/post/5a696dae51882573541c8fe8