屬性動(dòng)畫ValueAnimator在自定義View中的使用

功能強(qiáng)大的屬性動(dòng)畫(property animation)

最近在學(xué)習(xí)有關(guān)自定義View的內(nèi)容,在Github上看到好多開源的View控件,如果涉及動(dòng)畫,基本上都使用的是屬性動(dòng)畫,真心覺得屬性動(dòng)畫比以前的補(bǔ)間動(dòng)畫強(qiáng)大太多了,也學(xué)習(xí)到了使用屬性動(dòng)畫自定義View的方便和強(qiáng)大。所以想記錄一下在自定義View時(shí),使用屬性動(dòng)畫的幾個(gè)方面。

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

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

一.ValueAnimator

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

看一個(gè)最簡(jiǎn)單的例子吧。

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

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

效果是,在3000毫秒內(nèi)動(dòng)畫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線性變化的值,并且耗時(shí)3000毫秒。如果我們不設(shè)置Interpolator,會(huì)調(diào)用默認(rèn)的Interpolator,先加速增加后減速增加。

二.怎樣使用ValueAnimator自定義View動(dòng)畫?

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

如果想要做出如下圖所示的效果,使用ValueAnimator就特別簡(jiǎn)單。

這是效果圖:

橫向移動(dòng)

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

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

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

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

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) {
            // 獲取到動(dòng)畫每次該變得float值,賦值給xpoint
            XPoint = (Float)animation.getAnimatedValue();
            // 通知view重繪
            invalidate();
        }
    });
    animator.start();
}

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

三.TypeEvaluator

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

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

下面我們?cè)囋囎远x一個(gè)TypeEvaluator,實(shí)現(xiàn)我們想要的類型。

假如現(xiàn)在需要這個(gè)圓形斜著移動(dòng),使用ValueAnimator該怎樣實(shí)現(xiàn)?當(dāng)然有很多實(shí)現(xiàn)方法,比如Path路徑的使用,這里為了演示自定義TypeEvaluator,使用自定義TypeEvaluator的方式來實(shí)現(xiàn)。

這是效果圖:

斜著移動(dòng)

分析得知,和上面的橫向移動(dòng)不同,斜著移動(dòng)改變的是圓心的坐標(biāo),包括x軸和y軸坐標(biāo),我們可以自定義或使用系統(tǒng)提供的Point來表示一個(gè)二維坐標(biāo)下的點(diǎn),實(shí)現(xiàn)TypeEvaluator接口,根據(jù)動(dòng)畫完成的比例,返回一個(gè)具體代表點(diǎn)坐標(biāo)的Point對(duì)象。

自定義類PointEvaluator實(shí)現(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);
    }
}

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

在onDraw方法中依然還是簡(jiǎn)單的繪制一個(gè)圓形,此圓的圓心坐標(biāo)是成員變量mPoint的x,y值。

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

最后提供start方法開始動(dòng)畫。

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)建了一個(gè)ValueAnimator對(duì)象,并且傳遞給了兩個(gè)Point對(duì)象,表示需要改變的Point值得范圍,接著設(shè)置動(dòng)畫的執(zhí)時(shí)間為2000毫秒、重播方式為從頭開始播放、重播次數(shù)為無限和速度變化情況為線性變化,最后增加監(jiān)聽器,獲取每次變化的值賦值給mPoint ,接著通知view重繪。

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

四.TimeInterpolator

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

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

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

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

TimeInterpolator接口,只有一個(gè)方法:getInterpolation(float input) ,此方法接收一個(gè)float類型的input值,此值的變化范圍為0~1,并且根據(jù)動(dòng)畫運(yùn)行的時(shí)間,均勻增加,和TypeEvaluator接口方法中的參數(shù)fraction很像,fraction也是根據(jù)動(dòng)畫運(yùn)行的時(shí)間,均勻增加。

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

LinearInterpolator源碼:

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

從源碼中,可以看到getInterpolation的邏輯簡(jiǎn)單到不能再簡(jiǎn)單,直接返回input,因?yàn)閕nput本身表示的就是均勻增加的。

AccelerateInterpolator源碼:

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

構(gòu)造函數(shù)接收一個(gè)mFactor表示加速的倍數(shù),接收1.0f以上的數(shù),mDoubleFactor = 2 * mFactor。
在getInterpolation方法中,判斷mFactor如果等于1.0f,直接返回input * input(默認(rèn),二次函數(shù)增長(zhǎng)),否則返回input的mDoubleFactor次方(mDoubleFactor次函數(shù)增長(zhǎng))。

看來要想實(shí)現(xiàn)一個(gè)自定義的TimeInterpolator,得要有一些必要的數(shù)學(xué)修養(yǎng)了。沒辦法,數(shù)學(xué)沒有那么好,只能實(shí)現(xiàn)一個(gè)簡(jiǎn)單的TimeInterpolator,演示自定義TimeInterpolator的步驟。

下面我們實(shí)現(xiàn)一個(gè)以10給底數(shù)的負(fù)指數(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,因?yàn)槭菧p速,所以用到了負(fù)指數(shù),得到的值從1變化到0,所以再用1減去這個(gè)結(jié)果值,就得到了最終的結(jié)果。

五.AnimatorSet

AnimatorSet表示動(dòng)畫的集合,可以把幾個(gè)動(dòng)畫一起播放,或按次序播放。提供paly、with、after等方法。

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

這是效果圖:

斜著指數(shù)減速移動(dòng)伴隨顏色指數(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)建了兩個(gè)ValueAnimator,第一個(gè)使用.ofObject方法,通過傳遞自定義的PointEvaluator,第二個(gè)使用API已經(jīng)實(shí)現(xiàn)的ofArgb使顏色值變化的動(dòng)畫屬性,都添加監(jiān)聽器以實(shí)現(xiàn)對(duì)成員變量的修改,重繪View,最后創(chuàng)建AnimatorSet對(duì)兩個(gè)動(dòng)畫進(jìn)行疊加,在播放移動(dòng)動(dòng)畫的同時(shí)播放顏色漸變的動(dòng)畫。

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

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

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

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