屬性動畫ValueAnimator在自定義View中的使用

功能強大的屬性動畫(property animation)

最近在學習有關(guān)自定義View的內(nèi)容肝箱,在Github上看到好多開源的View控件锨推,如果涉及動畫距辆,基本上都使用的是屬性動畫芜果,真心覺得屬性動畫比以前的補間動畫強大太多了,也學習到了使用屬性動畫自定義View的方便和強大鹉戚。所以想記錄一下在自定義View時鲜戒,使用屬性動畫的幾個方面。

屬性動畫的強大之處在于可以對任意對象的任意屬性增加動畫效果抹凳,并且可以自定義值的類型和變化過程(TypeEvaluator)和過渡速度(Interpolator)。

這篇文章先來看看ValueAnimator的使用方法伦腐。
這篇文章也發(fā)步在我的博客

一.ValueAnimator

ValueAnimator是屬性動畫的核心類赢底,最常用的ObjectAnimator(下篇文章會講到)就是它的子類。此類只是以特定的方式(可以自定義)對值進行不斷的修改柏蘑,已達到某種想要的過渡效果幸冻。它提供設(shè)置播放次數(shù)、動畫間隔咳焚、重復模式洽损、開始動畫以及設(shè)置動畫監(jiān)聽器的方法。

看一個最簡單的例子吧革半。

ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setDuration(3000);
animator.setInterpolator(new LinearInterpolator());
animator.start();

以上代碼先使用ofFloat方法傳遞0碑定,1參數(shù)初始化了一個ValueAnimator對象,接著設(shè)置動畫播放的時間又官,設(shè)置變化速率為系統(tǒng)提供的線性變化延刘,最后啟動動畫。

效果是六敬,在3000毫秒內(nèi)動畫float值從0線性增加到1碘赖。
可以添加監(jiān)聽器獲取具體改變的值。

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (Float)animation.getAnimatedValue();
        Log.d(TAG, value);
    }
});
animator.start();

結(jié)果打印從0到1線性變化的值,并且耗時3000毫秒普泡。如果我們不設(shè)置Interpolator播掷,會調(diào)用默認的Interpolator,先加速增加后減速增加撼班。

二.怎樣使用ValueAnimator自定義View動畫叮趴?

當然,上面的代碼只是對數(shù)字的變化的操作权烧,并沒有涉及到動畫效果眯亦。接下來我們通過在動畫開始(start方法)前設(shè)置監(jiān)聽器來讓自定義View做出相應(yīng)的動畫。

如果想要做出如下圖所示的效果般码,使用ValueAnimator就特別簡單妻率。

這是效果圖:

橫向移動

簡單分析得知,由于效果是一個小球從左邊移動一段距離后板祝,變化的值只有小球圓心的X軸坐標宫静。所以可以利用ValueAnimator產(chǎn)生從開始位置到結(jié)束位置的一系列中間值,在監(jiān)聽器中把每次改變的值設(shè)置給代表小球圓心X軸坐標的變量券时,再通知view重新繪制孤里。我們看看這樣會不會產(chǎn)出想要的動畫效果。

onDraw方法:根據(jù)XPoint(x軸坐標)繪制圓形橘洞。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(XPoint, heightSpecSize / 2, 30, mPaint);
}

對外提供開始動畫的start方法:
創(chuàng)建ValueAnimator對象捌袜,設(shè)置必要的屬性,添加監(jiān)聽器把每次改變的值賦值給xPoint炸枣,并且通知view重繪虏等,最后開始動畫。

public void start() {
    final ValueAnimator animator = ValueAnimator.ofFloat(60, 600);
    animator.setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setRepeatMode(ValueAnimator.RESTART);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 獲取到動畫每次該變得float值适肠,賦值給xpoint
            XPoint = (Float)animation.getAnimatedValue();
            // 通知view重繪
            invalidate();
        }
    });
    animator.start();
}

以上代碼首先創(chuàng)建一個從60變化到600的ValueAnimator對象霍衫,接著設(shè)置動畫時間為2000毫秒、重播方式為從頭開始播放侯养、重播次數(shù)為無限和速度變化情況為線性變化敦跌,最后增加監(jiān)聽器,獲取每次變化的值賦值給xPoint逛揩,接著很重要的一點柠傍,調(diào)用invalidate()方法通知View重繪,即float值每次改變都需要View重繪息尺。
這樣就很方便的根據(jù)float值的改變携兵,給view增加了動畫的效果。

三.TypeEvaluator

前面的例子搂誉,創(chuàng)建ValueAnimator的時候徐紧,都是使用的ValueAnimator.ofFloat(float, float)方法,這個方法傳遞的參數(shù)為可變參數(shù),可以傳遞多個float值并级。其實創(chuàng)建ValueAnimator也可以使用ofInt等方法拂檩,這里有一個非常重要的方法:ValueAnimator.ofObject(TypeEvaluator, Object...),此方法和其他方法不同之處在于第一個參數(shù)TypeEvaluator,此處需要使用系統(tǒng)已經(jīng)實現(xiàn)好的或自定義子類,用于設(shè)定自定義類型嘲碧。

在使用ofInt或ofFloat方法時稻励,內(nèi)部其實是使用了FloatEvaluator、FloatArrayEvaluator`愈涩、IntEvaluator望抽、IntArrayEvaluator這些系統(tǒng)已經(jīng)實現(xiàn)好了的TypeEvaluator。我們使用這些方法創(chuàng)建ValueAnimator時就不必自定義類來繼承TypeEvaluator履婉。

下面我們試試自定義一個TypeEvaluator煤篙,實現(xiàn)我們想要的類型。

假如現(xiàn)在需要這個圓形斜著移動毁腿,使用ValueAnimator該怎樣實現(xiàn)辑奈?當然有很多實現(xiàn)方法,比如Path路徑的使用已烤,這里為了演示自定義TypeEvaluator鸠窗,使用自定義TypeEvaluator的方式來實現(xiàn)。

這是效果圖:

斜著移動

分析得知胯究,和上面的橫向移動不同稍计,斜著移動改變的是圓心的坐標,包括x軸和y軸坐標唐片,我們可以自定義或使用系統(tǒng)提供的Point來表示一個二維坐標下的點丙猬,實現(xiàn)TypeEvaluator接口,根據(jù)動畫完成的比例费韭,返回一個具體代表點坐標的Point對象。

自定義類PointEvaluator實現(xiàn)TypeEvaluator接口庭瑰。

class PointEvaluator implements TypeEvaluator{

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
        int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
            
        return new Point(x, y);
    }
}

這里需要實現(xiàn)evaluate方法星持,根據(jù)動畫完成的百分比返回對應(yīng)的值。其中fraction參數(shù)和下文的TimeInterpolator接口方法返回的值有關(guān)弹灭,可以理解為動畫執(zhí)行的完成程度督暂,比如動畫總時間為3000毫秒,現(xiàn)在執(zhí)行了1000毫秒穷吮,那么此刻傳遞進來的fraction參數(shù)值可以為三分之一逻翁。

在onDraw方法中依然還是簡單的繪制一個圓形,此圓的圓心坐標是成員變量mPoint的x,y值捡鱼。

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawCircle(mPoint.x, mPoint.y, 30, mPaint);
}

最后提供start方法開始動畫八回。

public void start() {
    final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), 
        new Point(30, 30), new Point(600, 600));
    animator.setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setRepeatMode(ValueAnimator.REVERSE);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            mPoint = (Point)animation.getAnimatedValue();
            invalidate();
        }
    });
    animator.start();
}

和上邊的代碼類似,只是ValueAnimotor操作的值從float改變成了Point(可以自定義類型)。我們使用ValueAnimator.ofObject方法創(chuàng)建了一個ValueAnimator對象缠诅,并且傳遞給了兩個Point對象溶浴,表示需要改變的Point值得范圍,接著設(shè)置動畫的執(zhí)時間為2000毫秒管引、重播方式為從頭開始播放士败、重播次數(shù)為無限和速度變化情況為線性變化,最后增加監(jiān)聽器褥伴,獲取每次變化的值賦值給mPoint 谅将,接著通知view重繪。

這個例子重慢,我們可以知道ValueAnimotor操作的值的類型是任意的饥臂,可以由我們來自定義,只要自定義類實現(xiàn)TypeEvaluator伤锚,并且實現(xiàn)此接口的唯一一個方法evaluate即可擅笔。

四.TimeInterpolator

TimeInterpolator表示動畫的速率,上邊代碼中我們就設(shè)置了動畫速率屯援,只不過使用的是API中已經(jīng)實現(xiàn)好了的LinearInterpolator猛们。

查詢API知道TimeInterpolator接口有很多已知的實現(xiàn)類,比如
AccelerateDecelerateInterpolator表示先加速后減速狞洋,
AccelerateInterpolator表示一直加速弯淘,
DecelerateInterpolator表示一直加速等。
BounceInterpolator可以模擬物理規(guī)律吉懊,實現(xiàn)反彈的效果

如果不設(shè)置setInterpolator,那么默認使用AccelerateDecelerateInterpolator庐橙。

在自定義TimeInterpolator之前,我們先看看API中提供的實現(xiàn)的例子:LinearInterpolator,AccelerateInterpolator借嗽。

TimeInterpolator接口态鳖,只有一個方法:getInterpolation(float input) ,此方法接收一個float類型的input值,此值的變化范圍為0~1恶导,并且根據(jù)動畫運行的時間浆竭,均勻增加,和TypeEvaluator接口方法中的參數(shù)fraction很像惨寿,fraction也是根據(jù)動畫運行的時間邦泄,均勻增加。

注意:getInterpolation函數(shù)的返回值傳遞給了fraction裂垦,最好讓此函數(shù)返回從0~1的值顺囊,并且遞增,這樣意義比較明確:代表動畫完成的程度蕉拢。自定義TypeEvaluator中也會比較好處理特碳。

LinearInterpolator源碼:

    public float getInterpolation(float input) {
            return input;
    }

從源碼中诚亚,可以看到getInterpolation的邏輯簡單到不能再簡單,直接返回input测萎,因為input本身表示的就是均勻增加的亡电。

AccelerateInterpolator源碼:

public float getInterpolation(float input) {
    if (mFactor == 1.0f) {
        return input * input;
    } else {
        return (float)Math.pow(input, mDoubleFactor);
    }
}

構(gòu)造函數(shù)接收一個mFactor表示加速的倍數(shù),接收1.0f以上的數(shù)硅瞧,mDoubleFactor = 2 * mFactor份乒。
在getInterpolation方法中,判斷mFactor如果等于1.0f腕唧,直接返回input * input(默認或辖,二次函數(shù)增長),否則返回input的mDoubleFactor次方(mDoubleFactor次函數(shù)增長)枣接。

看來要想實現(xiàn)一個自定義的TimeInterpolator颂暇,得要有一些必要的數(shù)學修養(yǎng)了。沒辦法但惶,數(shù)學沒有那么好耳鸯,只能實現(xiàn)一個簡單的TimeInterpolator,演示自定義TimeInterpolator的步驟膀曾。

下面我們實現(xiàn)一個以10給底數(shù)的負指數(shù)函數(shù)減速的例子:

    class LgDecelerateInterpolator implements TimeInterpolator {

        private float background;

        public LgDecelerateInterpolator() {
            background = 10;
        }

        @Override
        public float getInterpolation(float input) {
            return (1 - (float) Math.pow(background, -input));
        }
    }

然后在設(shè)置animator.setInterpolator(new LgDecelerateInterpolator());,就可以使用了县爬。

成員變量background表示底數(shù),在構(gòu)造方法中初始化為10添谊,因為是減速财喳,所以用到了負指數(shù),得到的值從1變化到0斩狱,所以再用1減去這個結(jié)果值耳高,就得到了最終的結(jié)果。

五.AnimatorSet

AnimatorSet表示動畫的集合所踊,可以把幾個動畫一起播放泌枪,或按次序播放。提供paly秕岛、with工闺、after等方法。

接下來瓣蛀,把以上用到的全部結(jié)合起來,播放一個動畫的效果:圓形從View的左上角移動到右下角雷厂,伴隨著顏色的變化惋增,移動速度和顏色變化的速率都由上面自定義的LgDecelerateInterpolator實現(xiàn)。

這是效果圖:

斜著指數(shù)減速移動伴隨顏色指數(shù)漸變

這是完整的代碼:

public class MyView extends View {

    private Paint mPaint;
    private Point mPoint;
    private int mColor;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public MyView(Context context) {
        super(context);
        initPaint();
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setColor(0xFFF00000);
        mPaint.setAntiAlias(true); // 抗鋸齒
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawCircle(mPoint.x, mPoint.y, 60, mPaint);
    }

    public void start() {
        final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),
                new Point(60, 60), new Point(990, 1050));
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPoint = (Point) animation.getAnimatedValue();
                invalidate();
            }
        });

        final ValueAnimator animator1 = ValueAnimator.ofArgb(0xFFF00000,0xFFFFFF00);
        animator1.setRepeatCount(ValueAnimator.INFINITE);
        animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mColor = (int) animation.getAnimatedValue();
                mPaint.setColor(mColor);
            }
        });

        AnimatorSet animationSet = new AnimatorSet();
        animationSet.setDuration(3000);
        animationSet.setInterpolator(new LgDecelerateInterpolator());

        animationSet.play(animator).with(animator1);
        animationSet.start();
    }

    class PointEvaluator implements TypeEvaluator {

        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            Point startPoint = (Point) startValue;
            Point endPoint = (Point) endValue;

            int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
            int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));

            return new Point(x, y);
        }
    }

    class LgDecelerateInterpolator implements TimeInterpolator {

        private float background;
        public LgDecelerateInterpolator() {
            background = 10;
        }

        @Override
        public float getInterpolation(float input) {
            return (1 - (float) Math.pow(background, -input));
        }
    }

}



start方法中創(chuàng)建了兩個ValueAnimator,第一個使用.ofObject方法改鲫,通過傳遞自定義的PointEvaluator,第二個使用API已經(jīng)實現(xiàn)的ofArgb使顏色值變化的動畫屬性诈皿,都添加監(jiān)聽器以實現(xiàn)對成員變量的修改林束,重繪View,最后創(chuàng)建AnimatorSet對兩個動畫進行疊加稽亏,在播放移動動畫的同時播放顏色漸變的動畫壶冒。

下篇文章為屬性動畫ObjectAnimator在自定義View中的使用(計劃),是更為常用的ObjectAnimator的使用截歉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胖腾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瘪松,更是在濱河造成了極大的恐慌咸作,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宵睦,死亡現(xiàn)場離奇詭異记罚,居然都是意外死亡,警方通過查閱死者的電腦和手機壳嚎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門桐智,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烟馅,你說我怎么就攤上這事说庭。” “怎么了焙糟?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵口渔,是天一觀的道長。 經(jīng)常有香客問我穿撮,道長缺脉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任悦穿,我火速辦了婚禮攻礼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘栗柒。我一直安慰自己礁扮,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布瞬沦。 她就那樣靜靜地躺著太伊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逛钻。 梳的紋絲不亂的頭發(fā)上僚焦,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音曙痘,去河邊找鬼芳悲。 笑死立肘,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的名扛。 我是一名探鬼主播谅年,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肮韧!你這毒婦竟也來了融蹂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤惹苗,失蹤者是張志新(化名)和其女友劉穎殿较,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桩蓉,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡淋纲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了院究。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洽瞬。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖业汰,靈堂內(nèi)的尸體忽然破棺而出伙窃,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站亿鲜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鳍怨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一跪妥、第九天 我趴在偏房一處隱蔽的房頂上張望鞋喇。 院中可真熱鬧,春花似錦眉撵、人聲如沸侦香。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罐韩。三九已至,卻和暖如春污朽,著一層夾襖步出監(jiān)牢的瞬間伴逸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留错蝴,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓颓芭,卻偏偏與公主長得像顷锰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亡问,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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