Android动画篇——Property|Android动画篇——Property Animation(属性动画)
OverView
The property animation system is a robust framework that allows you to animate almost anything. You can define an animation to change any object property over time, regardless of whether it draws to the screen or not. A property animation changes a property's (a field in an object) value over a specified length of time. To animate something, you specify the object property that you want to animate, such as an object's position on the screen, how long you want to animate it for, and what values you want to animate between.之前我们讲过View Animation他是只针对View来进行设置和实现动画效果的,而Property Animator可以针对任意对象的指定属性值进行修改,
You can define an animation to change any object property over time
。所以Property Animator要比View Animation强大的多,可以实现一些View Animation实现不了的效果。而且与View Animation不同的事,PropertyAnimator是会真实修改Object的属性值的,还是以View Animation中的例子来举例:将一个Button通过PropertyAnimator移动的新的位置之后,他的点击事件将会在新的位置被触发,说明button的真实位置已经被修改。工作原理 讲解原理之前我们先明确几个Property Animator会用的属性,这些属性基本都在View Animation中使用过了,简单列一下不再详细说明:
- duration:动画执行时长
- interpolation:差值器
- repeat count and model:动画重复执行次数和模式
- Animator Set:属性动画的集合,可以设置一组属性动画
- frame refresh delay:帧刷新间隔,默认10 ms刷新一次,但是也跟硬件性能和当然系统状态有关。官方文档说的是
You can specify how often to refresh frames of your animation.
但是并未提供接口来设置此属性。可能说的是startDelay?
动画的执行时长设置为40 ms,对对象的X属性值进行处理,指定的startValue和endValue分别为0跟40,默认10 ms刷新一次。
文章图片
图例已经很直观了,10ms刷新一次一共有5帧动画对应的x值分别为0、10、20、30和40。典型的线性函数和匀速直线运动,不过多解释了。
上图的匀速直线运动是因为使用的interpolator是LinearInterpolator,interpolator是用来定义了动画速度变化曲线的。简单来说Interpolator规定动画什么时候该加速变化什么时候该减速变化。
文章图片
这个图明显不是匀速直线运动了,他使用的是non-linear interpolator,他定义了一个先加速后减速变化的一个动画效果。
关于interpolator和TypeEvaluator的原理简单来说就是:动画执行的每一帧都会计算一个当前帧时间与动画总时长的一个比值input,拿此值去interpolator中获取一个fraction表明动画执行进度的值(如fraction等于0表明当然动画应取值应该是动画起始位置,等于1则表明在结束位置,此值可以小于0或大于1),以LinearInterpolator为例会将输入的input原封不动的返回,所以他是一个匀速直线运动,然后再拿fraction去TypeEvaluator获取具体的动画数值,然后设置为object的具体属性的值。原理大概了解就行,后面我们会以代码为例详细说明。
实现 ObjectAnimator
ObjectAnimator是系统提供的用来给任意Object的指定属性(该属性必须有setter方法)实现动画效果的,实现方式也有xml与java code两种方式。
XML实现
在res下定义animator文件夹,新建xml文件,文件以
和
标签开头,当然也可以以
和
开头下面我们再说这两个内容。 ...
以translation为例具体实现如下:
vauleForm="0"
是指以自身位置为起始点,而不是屏幕坐标系的0。android:valueType="floatType"
可以不设置,系统会自动根据你指定的属性值translationY
自行判断需要使用的TypeEvaluator,如果需要指定一定要与propertyName="translationY"
的属性值类型保持一致,要不然动画不会生效。若指定属性值为color属性则不需要指定valueType。针对View常用的propertyName有以下几种:
Property Name | Description |
---|---|
alpha | view透明度变化 |
translationX | 在x轴方向移动View |
translationY | 在Y轴方向移动View |
scaleX | 在x轴方向缩放View |
scaleY | 在Y轴方向缩放View |
rotation | 以Z轴为轴进行旋转 |
rotationX | 以X轴为轴进行旋转 |
rotationY | 以Y轴为轴进行旋转 |
backgroudColor | 修改View背景色 |
textColor | 只针对TextView等修改字体颜色 |
代码实现
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1, 0);
alphaAnimator.setRepeatCount(1);
//重复播放次数
alphaAnimator.setDuration(3000);
//播放时间
alphaAnimator.setRepeatMode(ValueAnimator.REVERSE);
//重复播放模式,REVERSE倒播,RESTART重播
alphaAnimator.setStartDelay(0);
//开始播放前的延时时间
//设置监听,监听animator执行状态
alphaAnimator.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) {
}
});
//设置监听,监听animator执行过程的值变化情况
alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentAlpha = (float) animation.getAnimatedValue();
Log.d(TAG,"当前透明度 alpha = " + currentAlpha);
}
});
alphaAnimator.start();
可以通过
ObjectAnimator.ofFloat()
、ObjectAnimator.ofInt()
和ObjectAnimator.ofArgb()
三种常用的方法来构建ObjectAnimator对象,当然还有ObjectAnimator.ofObject()
和ObjectAnimator.ofPropertyValuesHolder()
这两种方法相对不常用我们后面再详细介绍。上面提到的那三种方法分别对应指定的属性值为float、int和color三种类型变量。前面我们说了很多次PropertyAnimator可以对任意Object的任意属性进行修改,我们举个例子来说明一下。当然这个例子可能不具有实际意义,只是单纯的举例来说明功能:
- 首先我们定义下一个Object类
public class CustomObject {
private static final String TAG = CustomObject.class.getSimpleName();
private int customParam;
public CustomObject() {
}public CustomObject(int customParam) {
this.customParam = customParam;
}public int getCustomParam() {
return customParam;
}public void setCustomParam(int customParam) {
Log.d(TAG,"CustomObject setter param " + customParam);
this.customParam = customParam;
}
}
只有一个int型的customParm等待修改。
- 然后我们定义ObjectAnimator如下
CustomObject customObject = new CustomObject();
ObjectAnimator customAnimator = ObjectAnimator.ofInt(customObject,"customParam", 1, 1000);
customAnimator.setDuration(10000);
customAnimator.start();
然后就会看到如下打印,说明我们成功了。
文章图片
AnimatorSet和PropertyValuesHolder
AnimatorSet
【Android动画篇——Property|Android动画篇——Property Animation(属性动画)】AnimatorSet很明显跟AnimationSet曲艺同工,就是一组Animator的集合。
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(3000);
//播放时间
animatorSet.setTarget(imageView);
animatorSet.setStartDelay(0);
ObjectAnimator colorAnimator = ObjectAnimator.ofArgb(imageView, "backgroundColor", 0xffff0000, 0xff0000ff);
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1, 0);
animatorSet.playTogether(colorAnimator, alphaAnimator);
//监听动画执行
animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation, boolean isReverse) {
Log.d(TAG, "动画开始");
}@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
Log.d(TAG, "动画结束");
}
});
animatorSet.start();
AnimatorSet提供了
play()
、with()
、after()
、playTogether()
和playSequentially()
来指定播放单一Animator、同时播放一组Animator和顺序播放一组Animator。PropertyValuesHolder
PropertyValuesHolder也是用来管理一组Animator对象的,但是不同于AnimatorSet PropertyValuesHolder与Animator没有继承关系,它本身并不是动画对象类。使用如下,可以看到这时候我们使用了上文提到的
ofPropertyValuesHolder()
方法使用PropertyValuesHolder来构建ObjectAnimator对象。PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("translationX", 0, 200);
PropertyValuesHolder valuesHolder2 = PropertyValuesHolder.ofFloat("translationX", 0, 200);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(imageView,valuesHolder1,valuesHolder2);
objectAnimator.setDuration(2000);
objectAnimator.start();
ValueAnimator
直接看代码吧
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = https://www.it610.com/article/(int) animation.getAnimatedValue();
view.setXXX(value);
view.setYYY(value);
}
});
valueAnimator.setTarget(view);
valueAnimator.setDuration(2000);
valueAnimator.setRepeatCount(1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.start();
看起来貌似跟ObjectAnimator没多少区别,ValueAnimator同样提供了
ValueAnimator.ofFloat()
、ValueAnimator.ofInt()
和ValueAnimator.ofArgb()
等方法来构建ValueAnimator对象,但是不同的是ValueAnimator并没有指定想要修改的属性值名称(当然可以通过valueAnimator.setTarget(view)设置目标Object,但是貌似没用啊。。),必须通过添加AnimatorUpdateListener监听来自行修改对应的属性。所以相比ObjectAnimator来说ValueAnimator更加灵活,不在要求目标属性必须有开放的Setter方法,甚至可以一次修改多个属性值。TypeEvaluator
TypeEvaluator接口是用来描述如何去计算一个PropertyAnimator所需要修改的对象的属性值的。系统提供了三种默认的实现方式:
- IntEvaluator 用来计算int型的属性值
- FloatEvaluator 用来计算float型的属性值
- ArgbEvaluator 用来计算十六进制的Color类型的属性值
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
通过查看源码可以看到IntEvaluator只有一个方法
evaluate()
返回了一个Integer对象。该方法只有三个参数fraction
、startValue
、endValue
,很明显fraction
就是我们在上文介绍原理时说的是Interpolator返回的表明动画执行进度的值,startValue
和endValue
分别对应动画的起始值和结束值。好了,既然原理清楚了接下来我们来自定义一个TypeEvaluator。new TypeEvaluator() {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point = new Point();
point.x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
point.y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
return point;
}
}
我们定义了一个TypeEvaluator来处理Point型数据,具体实现与IntEvaluator类似不过多解释了。
我们上面提到了有三种常见的方法来构建ObjectAnimator和ValueAnimator:
ofFloat()
、ofInt()
和ofArgb()
,你会发现没有适合用来构建我们自定义的TypeEvaluator指定类型的方法,这个时候就用到了我们上文提到的另一种构建方法:ofObject()
。ValueAnimator valueAnimator = ValueAnimator.ofObject(new TypeEvaluator() {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point = new Point();
point.x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
point.y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
return point;
}
}, new Point(0, 0), new Point(300, 400));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point point = (Point) animation.getAnimatedValue();
starImageView.setTranslationX(point.x);
starImageView.setTranslationY(point.y);
}
});
valueAnimator.setInterpolator(new OvershootInterpolator());
valueAnimator.setDuration(3000);
valueAnimator.setRepeatCount(1);
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
valueAnimator.start();
当然你也可以这样使用:
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(new Point(0, 0), new Point(300, 400));
moveAnimator.setEvaluator(new TypeEvaluator() {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
Point point = new Point();
point.x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
point.y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
return point;
}
});
Interpolator
在ViewAnimation中我们已经介绍过系统提供的Interpolator的特点和用法了,这次我们来尝试自定义一个Interpolator。通过查看LinearInterpolator的源码我们可以发现,Interpolator的实现同样很简单只需要重写一个getInterpolator方法就可以了。自定义Interpolator如下:
public class ParabolaInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return input * input;
}
}
是不是特别简单,我们定义了一个抛物线的Interpolator,参数input就是动画时间进度分数。我们前面已经介绍过Interpolator的实现原理了,如果还不理解我们后面会单开一篇我尝试定义一个展示Interpolator变化曲线的控件(Interpolator展示控件),看过之后相信你能够对Interpolator的原理有更深入的了解。
LayoutTransition
在View Animation中我们介绍了一种布局动画LayoutAnimation,可以用来指定ViewGroup的所有子控件的第一次进场动画。这次我们来介绍一种基于Property Animator的布局动画LayoutTransition,可以用来指定当ViewGroup的子控件消失或者添加时发生的一系列动画效果。来看下效果可以更直观的了解LayoutTransition的作用。
文章图片
可以看到当View被add和remove时,View自身会有一个渐隐渐现的动画效果,其他子控件有translation的动画效果。
这种效果的实现很简单:
android:animateLayoutChanges="true"
在ViewGroup控件上添加此行代码就可以出现如上图所示的动画效果。但是这样实现只能使用系统默认的效果,不能进行自定义动画效果。我们可以在Java Code中使用
setLayoutTransition()
方法来进行自定义的实现。LayoutTransiton提供了一下五种属性值,来指定针对不同情况下的不同对象的动画效果。(下表提到的View被添加和移除的行为包括GONE和VISIBLE控件可见性的修改)
属性值 | 作用对象 | 场景 |
---|---|---|
APPEARING | 被添加到容器中的子View | 当View被添加到容器中时 |
DISAPPEARING | 被从容器中移除的子View | 当View被送容器中移除时 |
CHANGE_APPEARING | 除被添加View的其他View | 当View被添加到容器中时 |
CHANGE_DISAPPEARING | 除被移除View的其他View | 当View被送容器中移除时 |
CHANGE | 子View在容器中位置改变时的过渡动画,不涉及删除或者添加操作 |
//初始化
LayoutTransition layoutTransition = new LayoutTransition();
//统一设置LayoutTransition的动画时间, ANIMATOR_DURATION定义的常量
layoutTransition.setDuration(LayoutTransition.DISAPPEARING,ANIMATOR_DURATION);
layoutTransition.setDuration(LayoutTransition.APPEARING, ANIMATOR_DURATION);
layoutTransition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, ANIMATOR_DURATION);
layoutTransition.setDuration(LayoutTransition.CHANGE_APPEARING, ANIMATOR_DURATION);
//view 动画改变时,布局中的每个子view动画的时间间隔
layoutTransition.setStagger(LayoutTransition.CHANGE_APPEARING, 1000);
layoutTransition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 1000);
//设置APPEARING行为
ObjectAnimator appearingAnimator = ObjectAnimator.ofFloat(rootView,"scaleY", 0, 1);
appearingAnimator.setDuration(layoutTransition.getDuration(LayoutTransition.APPEARING));
layoutTransition.setAnimator(LayoutTransition.APPEARING,appearingAnimator);
//设置DISAPPEARING行为
ObjectAnimator disAppearingAnimator = ObjectAnimator.ofFloat(rootView,"scaleY", 1,0);
disAppearingAnimator.setDuration(layoutTransition.getDuration(LayoutTransition.DISAPPEARING));
layoutTransition.setAnimator(LayoutTransition.DISAPPEARING,disAppearingAnimator);
PropertyValuesHolder pvhLeft = PropertyValuesHolder.ofInt("left", 0, 0);
PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 0);
PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 0);
PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 0);
//设置CHANGE_APPEARING行为
PropertyValuesHolder animator = PropertyValuesHolder.ofFloat("rotation", 0, 90, 0);
final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
this, pvhTop, pvhBottom,pvhLeft, pvhRight, animator).
setDuration(layoutTransition.getDuration(LayoutTransition.CHANGE_APPEARING));
layoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);
//设置CHANGE_DISAPPEARING
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1);
final ObjectAnimator changeOut = ObjectAnimator.ofPropertyValuesHolder(
this, pvhTop, pvhBottom,pvhLeft, pvhRight, pvhRotation).
setDuration(layoutTransition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
layoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeOut);
rootView.setLayoutTransition(layoutTransition);
效果
文章图片
通过代码设置了4中行为动画的duration,然后分别为四种行为设置动画效果。APPEARING和DISAPPEARING比较简单,就是定义了一个ObjectAnimator对象,然后通过
setAnimator()
方法来设置行为的动画效果。需要特别注意的是CHANGE_APPEARING和CHANGE_DISAPPEARING行为,需要注意的有这几点:- 预先定义了left、top、right和bottom四种动画
- CHANGE_APPEARING和CHANGE_DISAPPEARING的自定义动画效果必须用PropertyAnimator实现
- 通过ofPropertyValuesHolder方法实例化ObjectAnimator对象时,必须至少添加left、top、right和bottom中的两种动画,建议全都添加上。如果不想left、top、right和bottom属性发生改变可以在定义的时候使用0,0或者1,1这种参数。
- 自定义的CHANGE_APPEARING和CHANGE_DISAPPEARING动画效果在构建时需要指定三个参数(如:
PropertyValuesHolder.ofFloat("scaleX", 1, 1.5f, 1)
),且第三个参数与第一个参数只能保持一致,特别注意的是绝对不能只设置2个参数,会使动画效果无效。这样的理论基础是:View执行CHANGE_APPEARING和CHANGE_DISAPPEARING动画效果之后,不能改变其除因为View添加和移除带来的相对位置改变之外的其他属性值。如上图当Button1被移除时,其他View会有一个scaleX的放大效果,但是当动画结束时其他View不能保持被放大的效果,scaleX必须恢复到原始属性值。
在这插一个很简单的小demo(仿小米计算器切换效果),但是效果还是很炫酷的方便理解LayoutTransition和ObjectAnimator的实际应用。
总结 至此,属性动画的相关知识就介绍完了,属性动画是Android动画中最复杂和功能最强大的一部分内容,熟练的掌握属性动画是Android高级开发中必不可少的内容。
本文对以下文章有过引用和参考:
- https://developer.android.com/guide/topics/graphics/prop-animation.html#java
- https://developer.android.com/reference/android/animation/ObjectAnimator.html
- https://github.com/OCNYang/Android-Animation-Set/tree/master/property-animation
推荐阅读
- android第三方框架(五)ButterKnife
- 2018年11月19日|2018年11月19日 星期一 亲子日记第144篇
- 《魔法科高中的劣等生》第26卷(Invasion篇)发售
- 拍照一年啦,如果你想了解我,那就请先看看这篇文章
- Android中的AES加密-下
- 亲子日记第186篇,2018、7、26、星期四、晴
- 带有Hilt的Android上的依赖注入
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)
- 两短篇
- android|android studio中ndk的使用