本篇包括Android属性动画的基本使用,理解插值器和估值器,自定义属性动画
简介
属性动画是Android3.0及其以上才能使用,但是由于现在开发的软件大多最低兼容都是4.0的,所以就不再介绍之前的View动画了,因为它完全可以被属性动画替代,下面进入正题。
基本使用
属性动画,正如其名,它本质就是通过set、get修改某个对象的某个属性,然后改变UI,来实现动画。所以属性动画的要点就是你要改变的对象必须包含以下两个要素:
- 具有要修改属性的set和get方法,例如:setScaleX和getScaleX等;
- 在set方法中包含更新UI的方法,例如:invalidate()等;
如果包含了这两点,那么该对象的改属性就能使用属性动画了。
下面就来看看,系统给我们提供了哪些自带的属性动画。首先可以知道包含了所有View动画中有的动画,包括:平移、旋转、缩放、透明度;在属性动画中对应着如下的属性:
- translationX:X轴的平移;
- translationY:Y轴的平移;
- rotation:旋转;
- rotationX:围绕X轴旋转;
- rotationY:围绕Y轴旋转;
- scaleX:X方向的缩放;
- scaleY:Y方向的缩放;
- alpha:透明度
属性动画除此之外还有其他的一些属性动画,例如背景颜色等,这里就不一一列举了;接下来就举两个例子:
(1)向下平移自己高度的距离,代码如下:
ObjectAnimator.ofFloat(vSquare, "translationY", vSquare.getHeight()).start();
直接来看,第一个参数Object,表示要改变的对象;第二个参数String,表示要改变的属性,当然这个就是Y轴的平移,第三个参数是float ...,接收的是一个变化的数组,这里只有一个参数;
总之,这句话的含义就是表示从当前位置向下平移自己高度的距离;
如果第三个参数不止传一个值,而是传多个,效果如何呢,先上代码:
ObjectAnimator translationY = ObjectAnimator.ofFloat(vSquare,"translationY", 0, vSquare.getHeight(),
vSquare.getHeight() / 2,vSquare.getHeight() * 2);
translationY.setDuration(3000);
translationY.setInterpolator(new LinearInterpolator());
translationY.start();
这个动画就是从当前位置匀速的向下移动自身高度的距离,再向上移动自身高度的一半的距离,再向下移动两倍自己高度的距离;其中有个细节,第一个参数是0;
没错,这其实是这样的,当第三个参数只有一个时,则会反射调用该对象的该属性的get方法,作为动画的起点,再执行;当第三个参数有多个时,就会把第一个参数作为动画的起点;
(2)改变背景颜色以及改变Y轴位置并一起循环播放
首先,我们分析一下,这里边包含了什么,属性包括:backgroundColor和translationY,还有就是循环播放;如果有View动画的基础的盆友,估计知道可以用AnimatorSet来统一管理,但是其实也可以直接创建两个动画一起执行,效果差不多(注意是差不多,不是一样);其实也挺简单,直接上代码:
if (bgAndTransYSet == null) {
ObjectAnimator colorAnim = ObjectAnimator.ofInt(vChangeBg, "backgroundColor", getResources().getColor(R.color.colorAccent),
getResources().getColor(R.color.colorPrimary));
colorAnim.setDuration(2000);
colorAnim.setEvaluator(new ArgbEvaluator());//颜色变化推荐使用这个插值器
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator translationY = ObjectAnimator.ofFloat(vChangeBg, "translationY", vSquare.getHeight());
translationY.setRepeatCount(ValueAnimator.INFINITE);
translationY.setRepeatMode(ValueAnimator.REVERSE);
bgAndTransYSet = new AnimatorSet();
bgAndTransYSet.addListener(new AnimListener());
bgAndTransYSet.playTogether(colorAnim, translationY);
}
if (!bgAndTransYSet.isStarted()) {
bgAndTransYSet.start();
}
public class AnimListener implements 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) {
}
}
里边可以看到给属性动画设置了估值器,目的是计算当前阶段应该返回什么值与插值器和设置的时间有关,越到最后就越接近最终的值;
然后还设置了重复的模式和次数,重复次数这个值为-1,就表示无限,默认是0,就是不重复,重复的模式有两种,这里的这种表示,从开始到结束,然后结束到开始,再开始到结束,这样依次循环;还有一种是ValueAnimator.RESTART,表示从开始到结束,再从开始到结束,依次循环;
然后就是使用到了AnimatorSet,然后就是一切播放,接收的参数是一个动画的数组,这里又有一个细节,如果AnimatorSet设置了duration,那么它包含的子动画的duration属性都会和AnimatorSet的duration的值一样;AnimatorSet还有其他的一些播放方式,例如:
- playSequentially(Animator... items),表示动画依次播放;
- play(Animator anim).with(Animator anim1),表示两个动画一起播放;
- play(Animator anim).before(Animator anim1),表示在anim在anim1之前播放;
- play(Animator anim).after(Animator anim1),表示在anim在anim1之后播放;
注意:这里同一个动画集不要重复start
总之播放顺序完全可以按照自己的想法来设置,非常的好用。
最后就是动画的监听,方法的意思都很明显,有开始、结束、取消以及重复的监听,这样我们就能在动画的不同阶段做不同的事,除此之外还有一个重要的方法,是属于ValueAnimator的,监听属性改变的回调:
public static interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
还有一些其他的动画就不逐一展示了,都是大同小异的。
理解插值器和估值器
插值器
其作用是根据时间的流逝的百分比来计算出当前属性值改变的百分比,上文中,用到了LinearInterpolator(线性插值器:表示匀速),除此之外系统还提供了一些其他的插值器,例如:AccelerateDecelerateInterpolator(加速减速插值器:两头慢中间快)、DecelerateInterpolator(减速插值器:越来越慢)等等。下面就分析LinearInterpolator的源码:
package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
其中可以看到最重要的就是getInterpolation()方法,线性插值器,可以看到它表示时间百分比变化是多少,当前的属性百分比就变化多少,即匀速动画。再看看AccelerateDecelerateInterpolator的源码:
package android.view.animation;
import android.content.Context;
import android.util.AttributeSet;
import com.android.internal.view.animation.HasNativeInterpolator;
import com.android.internal.view.animation.NativeInterpolatorFactory;
import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*/
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator extends BaseInterpolator
implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
可以看到getInterpolation()方法,其中用到的余弦函数,其中input的值是[0,1],范围就是[cos(PI)/2+0.5,cos(2PI)/2+0.5],而cos(PI)cos(2PI)是一个先加速再减速的图像,这里没有图,相信你还没有把这点数学还给老师。cos(PI)cos(2PI)的取值是[-1,1],所以getInterpolation()的返回值也是[0,1]。
估值器
目的在于根据当前属性改变的百分比来计算改变后的属性值。上文中用到了ArgbEvaluator(针对color属性),除此之外系统还提供了:IntEvaluator(针对整型属性)、FloatEvaluator(针对浮点数属性)等等。也来看看IntEvaluator的源码理解一下:
package android.animation;
/**
* This evaluator can be used to perform type interpolation between <code>int</code> values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* <code>fraction</code> representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
* where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
* and <code>t</code> is <code>fraction</code>.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type <code>int</code> or
* <code>Integer</code>
* @param endValue The end value; should be of type <code>int</code> or <code>Integer</code>
* @return A linear interpolation between the start and end values, given the
* <code>fraction</code> parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
这个也是很直接,就是一个方法,获取改变后的值;
第一个参数fraction就是插值器里边的getInterpolator返回的值;表示一个变化的百分比;
第二个参数startValue,表示变化的起始值;
第三个参数endValue,表示变化的终值;
其中也就是fraction是个变化的值,取值范围是[0,1]起始就是一个一次函数。
到这里其实可能已经意识到了,插值器与估值器一起使用就能打造出变化多端的动画,因为这些属性值的变化是变化多端的,这时候是否怀念当年的三角函数呢?
自定义属性动画
属性动画之所以好,是因为它能对每个对象都能改变其属性,这就让他有了很强的扩展性,这里就以用动画改变一个View的宽度为例,我们知道View是没有提供setWidth和getWidth方法的,所以系统不支持width的属性动画,而我们大展身手的机会就来了。理论上我们有三种方式去实现这个功能。
- 改变View的源码,增加View的这个属性set和get方法(可惜没权限);
- 使用一个类去包装原始对象,间接提供set和get方法;
- 使用ValueAnimator,监听动画过程,自己去改变属性;
显然第二种方式扩展性更强,改动也最小,直接上代码:
package net.arvin.androidart.anim;
import android.view.View;
/**
* created by arvin on 17/2/19 21:10
* email:1035407623@qq.com
*/
public abstract class BaseViewWrapper {
protected View mTarget;
public BaseViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
}
package net.arvin.androidart.anim;
import android.view.View;
/**
* created by arvin on 17/2/19 21:11
* email:1035407623@qq.com
*/
public class ViewWidthWrapper extends BaseViewWrapper {
public ViewWidthWrapper(View mTarget) {
super(mTarget);
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
public int getWidth() {
return mTarget.getWidth();
}
}
widthWrapper = new ViewWidthWrapper(vWidthChange);
if (widthAnim == null) {
widthAnim = ObjectAnimator.ofInt(widthWrapper, "width", ScreenUtil.dp2px(80));
widthAnim.setRepeatCount(ValueAnimator.INFINITE);
widthAnim.setRepeatMode(ValueAnimator.REVERSE);
}
if (!widthAnim.isStarted()) {
widthAnim.start();
}
代码很简单,我也简单的封装了一层,方便使用,这个动画就是循环改变View的宽度,从当前宽度到两倍自身宽度,再从两倍宽度变到自身宽度,循环往返。
好了到这里相信对属性动画也有了一定的了解,等以后再进一步分析属性动画的实现原理,现在就到这里啦!下面奉上源码。