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

本篇包括Android屬性動(dòng)畫的基本使用,理解插值器和估值器走搁,自定義屬性動(dòng)畫

簡(jiǎn)介

屬性動(dòng)畫是Android3.0及其以上才能使用,但是由于現(xiàn)在開發(fā)的軟件大多最低兼容都是4.0的两残,所以就不再介紹之前的View動(dòng)畫了劣纲,因?yàn)樗耆梢员粚傩詣?dòng)畫替代,下面進(jìn)入正題筑悴。

基本使用

屬性動(dòng)畫棋返,正如其名,它本質(zhì)就是通過set雷猪、get修改某個(gè)對(duì)象的某個(gè)屬性睛竣,然后改變UI,來實(shí)現(xiàn)動(dòng)畫求摇。所以屬性動(dòng)畫的要點(diǎn)就是你要改變的對(duì)象必須包含以下兩個(gè)要素:

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

如果包含了這兩點(diǎn),那么該對(duì)象的改屬性就能使用屬性動(dòng)畫了摔刁。

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

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

屬性動(dòng)畫除此之外還有其他的一些屬性動(dòng)畫断部,例如背景顏色等猎贴,這里就不一一列舉了;接下來就舉兩個(gè)例子:

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

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

直接來看嘱能,第一個(gè)參數(shù)Object吝梅,表示要改變的對(duì)象;第二個(gè)參數(shù)String惹骂,表示要改變的屬性苏携,當(dāng)然這個(gè)就是Y軸的平移,第三個(gè)參數(shù)是float ...对粪,接收的是一個(gè)變化的數(shù)組右冻,這里只有一個(gè)參數(shù);

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

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

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

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

沒錯(cuò)因惭,這其實(shí)是這樣的,當(dāng)?shù)谌齻€(gè)參數(shù)只有一個(gè)時(shí)绩衷,則會(huì)反射調(diào)用該對(duì)象的該屬性的get方法蹦魔,作為動(dòng)畫的起點(diǎn),再執(zhí)行咳燕;當(dāng)?shù)谌齻€(gè)參數(shù)有多個(gè)時(shí)勿决,就會(huì)把第一個(gè)參數(shù)作為動(dòng)畫的起點(diǎn);

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

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

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());//顏色變化推薦使用這個(gè)插值器
    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) {
    }
}

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

然后還設(shè)置了重復(fù)的模式和次數(shù),重復(fù)次數(shù)這個(gè)值為-1镰吆,就表示無限帘撰,默認(rèn)是0,就是不重復(fù)万皿,重復(fù)的模式有兩種摧找,這里的這種表示,從開始到結(jié)束牢硅,然后結(jié)束到開始蹬耘,再開始到結(jié)束,這樣依次循環(huán)减余;還有一種是ValueAnimator.RESTART综苔,表示從開始到結(jié)束,再?gòu)拈_始到結(jié)束位岔,依次循環(huán)休里;

然后就是使用到了AnimatorSet,然后就是一切播放赃承,接收的參數(shù)是一個(gè)動(dòng)畫的數(shù)組妙黍,這里又有一個(gè)細(xì)節(jié),如果AnimatorSet設(shè)置了duration瞧剖,那么它包含的子動(dòng)畫的duration屬性都會(huì)和AnimatorSet的duration的值一樣拭嫁;AnimatorSet還有其他的一些播放方式,例如:

  • playSequentially(Animator... items)抓于,表示動(dòng)畫依次播放;
  • play(Animator anim).with(Animator anim1)做粤,表示兩個(gè)動(dòng)畫一起播放;
  • play(Animator anim).before(Animator anim1)捉撮,表示在anim在anim1之前播放怕品;
  • play(Animator anim).after(Animator anim1),表示在anim在anim1之后播放巾遭;

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

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

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

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

還有一些其他的動(dòng)畫就不逐一展示了侠姑,都是大同小異的。

理解插值器和估值器

插值器

其作用是根據(jù)時(shí)間的流逝的百分比來計(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()方法圃酵,線性插值器柳畔,可以看到它表示時(shí)間百分比變化是多少,當(dāng)前的屬性百分比就變化多少郭赐,即勻速動(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)是一個(gè)先加速再減速的圖像,這里沒有圖观谦,相信你還沒有把這點(diǎn)數(shù)學(xué)還給老師拉盾。cos(PI)cos(2PI)的取值是[-1,1]豁状,所以getInterpolation()的返回值也是[0捉偏,1]。

估值器

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

這個(gè)也是很直接讹躯,就是一個(gè)方法,獲取改變后的值缠劝;

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

第二個(gè)參數(shù)startValue剩彬,表示變化的起始值酷麦;

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

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

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

自定義屬性動(dòng)畫

屬性動(dòng)畫之所以好馆揉,是因?yàn)樗軐?duì)每個(gè)對(duì)象都能改變其屬性,這就讓他有了很強(qiáng)的擴(kuò)展性抖拦,這里就以用動(dòng)畫改變一個(gè)View的寬度為例升酣,我們知道View是沒有提供setWidth和getWidth方法的,所以系統(tǒng)不支持width的屬性動(dòng)畫态罪,而我們大展身手的機(jī)會(huì)就來了噩茄。理論上我們有三種方式去實(shí)現(xiàn)這個(gè)功能。

  • 改變View的源碼复颈,增加View的這個(gè)屬性set和get方法(可惜沒權(quán)限)绩聘;
  • 使用一個(gè)類去包裝原始對(duì)象,間接提供set和get方法耗啦;
  • 使用ValueAnimator凿菩,監(jiān)聽動(dòng)畫過程,自己去改變屬性帜讲;

顯然第二種方式擴(kuò)展性更強(qiáng)衅谷,改動(dòng)也最小,直接上代碼:

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

代碼很簡(jiǎn)單似将,我也簡(jiǎn)單的封裝了一層获黔,方便使用,這個(gè)動(dòng)畫就是循環(huán)改變View的寬度玩郊,從當(dāng)前寬度到兩倍自身寬度肢执,再?gòu)膬杀秾挾茸兊阶陨韺挾确浚h(huán)往返既峡。

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

源碼

Android屬性動(dòng)畫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刨沦,隨后出現(xiàn)的幾起案子诗宣,更是在濱河造成了極大的恐慌,老刑警劉巖想诅,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件召庞,死亡現(xiàn)場(chǎng)離奇詭異岛心,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)篮灼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門忘古,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人诅诱,你說我怎么就攤上這事髓堪。” “怎么了娘荡?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵干旁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我炮沐,道長(zhǎng)争群,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任央拖,我火速辦了婚禮祭阀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鲜戒。我一直安慰自己专控,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布遏餐。 她就那樣靜靜地躺著伦腐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪失都。 梳的紋絲不亂的頭發(fā)上柏蘑,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音粹庞,去河邊找鬼咳焚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庞溜,可吹牛的內(nèi)容都是我干的革半。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼流码,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼又官!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起漫试,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤六敬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后驾荣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體外构,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡普泡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了典勇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劫哼。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叮趴,死狀恐怖割笙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眯亦,我是刑警寧澤伤溉,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站妻率,受9級(jí)特大地震影響乱顾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宫静,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一走净、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孤里,春花似錦伏伯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至虏等,卻和暖如春弄唧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背霍衫。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工候引, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敦跌。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓澄干,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親峰髓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傻寂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容