Android學(xué)習(xí)感悟之屬性動畫

本篇包括Android屬性動畫的基本使用,理解插值器和估值器,自定義屬性動畫

簡介

屬性動畫是Android3.0及其以上才能使用,但是由于現(xiàn)在開發(fā)的軟件大多最低兼容都是4.0的,所以就不再介紹之前的View動畫了,因為它完全可以被屬性動畫替代,下面進(jìn)入正題。

基本使用

屬性動畫,正如其名,它本質(zhì)就是通過set、get修改某個對象的某個屬性,然后改變UI,來實現(xiàn)動畫。所以屬性動畫的要點就是你要改變的對象必須包含以下兩個要素:

  • 具有要修改屬性的set和get方法,例如:setScaleX和getScaleX等;
  • 在set方法中包含更新UI的方法,例如:invalidate()等;

如果包含了這兩點,那么該對象的改屬性就能使用屬性動畫了。

下面就來看看,系統(tǒng)給我們提供了哪些自帶的屬性動畫。首先可以知道包含了所有View動畫中有的動畫,包括:平移、旋轉(zhuǎn)、縮放、透明度;在屬性動畫中對應(yīng)著如下的屬性:

  • translationX:X軸的平移;
  • translationY:Y軸的平移;
  • rotation:旋轉(zhuǎn);
  • rotationX:圍繞X軸旋轉(zhuǎn);
  • rotationY:圍繞Y軸旋轉(zhuǎn);
  • scaleX:X方向的縮放;
  • scaleY:Y方向的縮放;
  • alpha:透明度

屬性動畫除此之外還有其他的一些屬性動畫,例如背景顏色等,這里就不一一列舉了;接下來就舉兩個例子:

(1)向下平移自己高度的距離,代碼如下:

ObjectAnimator.ofFloat(vSquare, "translationY", vSquare.getHeight()).start();

直接來看,第一個參數(shù)Object,表示要改變的對象;第二個參數(shù)String,表示要改變的屬性,當(dāng)然這個就是Y軸的平移,第三個參數(shù)是float ...,接收的是一個變化的數(shù)組,這里只有一個參數(shù);

總之,這句話的含義就是表示從當(dāng)前位置向下平移自己高度的距離;

如果第三個參數(shù)不止傳一個值,而是傳多個,效果如何呢,先上代碼:

ObjectAnimator translationY = ObjectAnimator.ofFloat(vSquare,"translationY", 0, vSquare.getHeight(),
        vSquare.getHeight() / 2,vSquare.getHeight() * 2);
translationY.setDuration(3000);
translationY.setInterpolator(new LinearInterpolator());
translationY.start();

這個動畫就是從當(dāng)前位置勻速的向下移動自身高度的距離,再向上移動自身高度的一半的距離,再向下移動兩倍自己高度的距離;其中有個細(xì)節(jié),第一個參數(shù)是0;

沒錯,這其實是這樣的,當(dāng)?shù)谌齻€參數(shù)只有一個時,則會反射調(diào)用該對象的該屬性的get方法,作為動畫的起點,再執(zhí)行;當(dāng)?shù)谌齻€參數(shù)有多個時,就會把第一個參數(shù)作為動畫的起點;

(2)改變背景顏色以及改變Y軸位置并一起循環(huán)播放

首先,我們分析一下,這里邊包含了什么,屬性包括:backgroundColor和translationY,還有就是循環(huán)播放;如果有View動畫的基礎(chǔ)的盆友,估計知道可以用AnimatorSet來統(tǒng)一管理,但是其實也可以直接創(chuàng)建兩個動畫一起執(zhí)行,效果差不多(注意是差不多,不是一樣);其實也挺簡單,直接上代碼:

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) {
    }
}

里邊可以看到給屬性動畫設(shè)置了估值器,目的是計算當(dāng)前階段應(yīng)該返回什么值與插值器和設(shè)置的時間有關(guān),越到最后就越接近最終的值;

然后還設(shè)置了重復(fù)的模式和次數(shù),重復(fù)次數(shù)這個值為-1,就表示無限,默認(rèn)是0,就是不重復(fù),重復(fù)的模式有兩種,這里的這種表示,從開始到結(jié)束,然后結(jié)束到開始,再開始到結(jié)束,這樣依次循環(huán);還有一種是ValueAnimator.RESTART,表示從開始到結(jié)束,再從開始到結(jié)束,依次循環(huán);

然后就是使用到了AnimatorSet,然后就是一切播放,接收的參數(shù)是一個動畫的數(shù)組,這里又有一個細(xì)節(jié),如果AnimatorSet設(shè)置了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之后播放;

注意:這里同一個動畫集不要重復(fù)start

總之播放順序完全可以按照自己的想法來設(shè)置,非常的好用。

最后就是動畫的監(jiān)聽,方法的意思都很明顯,有開始、結(jié)束、取消以及重復(fù)的監(jiān)聽,這樣我們就能在動畫的不同階段做不同的事,除此之外還有一個重要的方法,是屬于ValueAnimator的,監(jiān)聽屬性改變的回調(diào):

public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}

還有一些其他的動畫就不逐一展示了,都是大同小異的。

理解插值器和估值器

插值器

其作用是根據(jù)時間的流逝的百分比來計算出當(dāng)前屬性值改變的百分比,上文中,用到了LinearInterpolator(線性插值器:表示勻速),除此之外系統(tǒng)還提供了一些其他的插值器,例如: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()方法,線性插值器,可以看到它表示時間百分比變化是多少,當(dāng)前的屬性百分比就變化多少,即勻速動畫。再看看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()方法,其中用到的余弦函數(shù),其中input的值是[0,1],范圍就是[cos(PI)/2+0.5,cos(2PI)/2+0.5],而cos(PI)cos(2PI)是一個先加速再減速的圖像,這里沒有圖,相信你還沒有把這點數(shù)學(xué)還給老師。cos(PI)cos(2PI)的取值是[-1,1],所以getInterpolation()的返回值也是[0,1]。

估值器

目的在于根據(jù)當(dāng)前屬性改變的百分比來計算改變后的屬性值。上文中用到了ArgbEvaluator(針對color屬性),除此之外系統(tǒng)還提供了:IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點數(shù)屬性)等等。也來看看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));
    }
}

這個也是很直接,就是一個方法,獲取改變后的值;

第一個參數(shù)fraction就是插值器里邊的getInterpolator返回的值;表示一個變化的百分比;

第二個參數(shù)startValue,表示變化的起始值;

第三個參數(shù)endValue,表示變化的終值;

其中也就是fraction是個變化的值,取值范圍是[0,1]起始就是一個一次函數(shù)。

到這里其實可能已經(jīng)意識到了,插值器與估值器一起使用就能打造出變化多端的動畫,因為這些屬性值的變化是變化多端的,這時候是否懷念當(dāng)年的三角函數(shù)呢?

自定義屬性動畫

屬性動畫之所以好,是因為它能對每個對象都能改變其屬性,這就讓他有了很強的擴展性,這里就以用動畫改變一個View的寬度為例,我們知道View是沒有提供setWidth和getWidth方法的,所以系統(tǒng)不支持width的屬性動畫,而我們大展身手的機會就來了。理論上我們有三種方式去實現(xiàn)這個功能。

  • 改變View的源碼,增加View的這個屬性set和get方法(可惜沒權(quán)限);
  • 使用一個類去包裝原始對象,間接提供set和get方法;
  • 使用ValueAnimator,監(jiān)聽動畫過程,自己去改變屬性;

顯然第二種方式擴展性更強,改動也最小,直接上代碼:

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();
}

代碼很簡單,我也簡單的封裝了一層,方便使用,這個動畫就是循環(huán)改變View的寬度,從當(dāng)前寬度到兩倍自身寬度,再從兩倍寬度變到自身寬度,循環(huán)往返。

好了到這里相信對屬性動畫也有了一定的了解,等以后再進(jìn)一步分析屬性動畫的實現(xiàn)原理,現(xiàn)在就到這里啦!下面奉上源碼。

源碼

Android屬性動畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎(chǔ)知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,852評論 0 10
  • Animation Animation類是所有動畫(scale、alpha、translate、rotate)的基...
    四月一號閱讀 2,031評論 0 10
  • 轉(zhuǎn)載一篇高質(zhì)量博文,原地址請戳這里轉(zhuǎn)載下來方便今后查看。1 背景不能只分析源碼呀,分析的同時也要整理歸納基礎(chǔ)知識,...
    Elder閱讀 2,009評論 0 24
  • 近期看到一篇文章,里面收錄了很多不錯的第三方庫,收藏起來,原文如下: 原文 由 Instagram 開發(fā)人員制作,...
    好雨知時節(jié)浩宇閱讀 379評論 0 3
  • 也許這個詞并不是那么適合作文題,也許又那么恰到好處?;蛟S因為我覺得總有人與我有心靈上的共鳴,或許因為這就是個人們相...
    漢斯小姐閱讀 704評論 0 2

友情鏈接更多精彩內(nèi)容