Android学习感悟之属性动画

本篇包括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的宽度,从当前宽度到两倍自身宽度,再从两倍宽度变到自身宽度,循环往返。

好了到这里相信对属性动画也有了一定的了解,等以后再进一步分析属性动画的实现原理,现在就到这里啦!下面奉上源码。

源码

Android属性动画

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 2,579评论 0 9
  • Animation Animation类是所有动画(scale、alpha、translate、rotate)的基...
    四月一号阅读 1,868评论 0 10
  • 转载一篇高质量博文,原地址请戳这里转载下来方便今后查看。1 背景不能只分析源码呀,分析的同时也要整理归纳基础知识,...
    Elder阅读 1,898评论 0 24
  • 近期看到一篇文章,里面收录了很多不错的第三方库,收藏起来,原文如下: 原文 由 Instagram 开发人员制作,...
    好雨知时节浩宇阅读 347评论 0 3
  • 也许这个词并不是那么适合作文题,也许又那么恰到好处。或许因为我觉得总有人与我有心灵上的共鸣,或许因为这就是个人们相...
    汉斯小姐阅读 653评论 0 2