屬性動(dòng)畫(huà)詳細(xì)介紹(一)

[TOC]

屬性動(dòng)畫(huà)我決定用兩篇文章做總結(jié)

一暂幼、屬性動(dòng)畫(huà)基礎(chǔ)內(nèi)容

image

二搅轿、ValueAnimator

從名字就可以看出琴昆,這是針對(duì)值的動(dòng)畫(huà),它并不會(huì)對(duì)View做出任何動(dòng)畫(huà)效果嚎京。使用ValueAnimator可以讓某一個(gè)值在設(shè)定時(shí)間內(nèi)平滑過(guò)渡成另一個(gè)值嗡贺,根據(jù)值的變換過(guò)程,自己操作View的變換鞍帝。

2.1诫睬、使用ValueAnimator

要使用ValueAnimator非常簡(jiǎn)單,首先通過(guò)ValueAnimator提供的靜態(tài)方法創(chuàng)建其對(duì)象帕涌,然后設(shè)置動(dòng)畫(huà)的時(shí)長(zhǎng)摄凡,最后調(diào)用start()方法開(kāi)啟動(dòng)畫(huà)即可。下面我們來(lái)創(chuàng)建一個(gè)ValueAnimator蚓曼,它能在2秒內(nèi)從0變換到500:

ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
valueAnimator.setDuration(2000);
valueAnimator.start();

沒(méi)錯(cuò)就是這么簡(jiǎn)單亲澡,運(yùn)行后的確在兩秒內(nèi)值從0一直平滑過(guò)渡到了500∪野妫可是我們需要過(guò)程而不是結(jié)果呀谷扣,如果沒(méi)辦法監(jiān)聽(tīng)到值變換的過(guò)程,就沒(méi)辦法利用變換中的值給View設(shè)置動(dòng)畫(huà)了。要想監(jiān)聽(tīng)值變化的過(guò)程会涎,我們可以使用addUpdateListener()方法給valueAnimator加上監(jiān)聽(tīng)事件:

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int value = (int) animation.getAnimatedValue();
        Log.i("HurryYu", "value:" + value);
    }
});

在回調(diào)中裹匙,animation表示當(dāng)前ValueAnimator對(duì)象,使用animation.getAnimatedValue()就可以獲取到當(dāng)前變換的值(默認(rèn)是返回Object類型末秃,由于使用的是ofInt()創(chuàng)建的ValueAnimator對(duì)象砚著,因此可以直接強(qiáng)轉(zhuǎn)為int類型)夸政。此時(shí)觀察Logcat留搔,得到如下輸出:

2019-05-11 15:19:36.858 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:36.960 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:37.253 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:26
2019-05-11 15:19:37.626 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:121
2019-05-11 15:19:37.654 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:134
2019-05-11 15:19:37.669 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:140
2019-05-11 15:19:37.695 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:147
2019-05-11 15:19:37.705 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:153
.
.
.
2019-05-11 15:19:38.885 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:498
2019-05-11 15:19:38.903 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.941 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.958 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:500

現(xiàn)在明白ValueAnimator了吧译红,實(shí)際上就是對(duì)給定區(qū)間的值進(jìn)行平滑變換,我們需要自己監(jiān)聽(tīng)值的變換過(guò)程铃将,然后自己對(duì)View執(zhí)行相應(yīng)的動(dòng)畫(huà)效果项鬼。就拿上面的這個(gè)從0平滑過(guò)渡到500的ValueAnimator做實(shí)驗(yàn),現(xiàn)在我們準(zhǔn)備利用它讓View在X軸方向上向右平移劲阎,首先我們完成布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HurryYu" />
</LinearLayout>

效果如下圖:

image

下面完成代碼部分:

public class MainActivity extends AppCompatActivity {

    private View view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view = findViewById(R.id.view);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "看看有效果嗎?",
                        Toast.LENGTH_SHORT).show();
            }
        });
        startAnim();
    }

    private void startAnim() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                view.setTranslationX(value);
            }
        });
        valueAnimator.start();
    }
}

請(qǐng)注意看ValueAnimator的回調(diào)中绘盟,我們使用了view.setTranslationX(value)來(lái)改變view在X軸上的偏移量,其中value會(huì)在兩秒內(nèi)從0變換到500悯仙。最終動(dòng)畫(huà)完成后的效果如圖:

image

點(diǎn)擊動(dòng)畫(huà)完成后的View龄毡,仍然可以響應(yīng)點(diǎn)擊事件。不過(guò)奇怪的是锡垄,父布局是水平線性布局沦零,View向右移動(dòng)后,TextView應(yīng)該也會(huì)跟著向右移動(dòng)呀货岭,可是為什么沒(méi)有呢路操?因?yàn)槲覀兪褂玫氖?code>setTranslationX()去改變view的偏移量,它的確能改變view的位置千贯,但它并不會(huì)改變view的LayoutParams中的margin屬性值寻拂,即不會(huì)影響getLeft()與getRight()。

2.2丈牢、ValueAnimator的常用方法

2.1.1、其它靜態(tài)工廠方法

之前我們已經(jīng)使用過(guò)ValueAnimator.ofInt()方法創(chuàng)建了對(duì)象瞄沙,除了ofInt()以外己沛,它還提供了如下靜態(tài)工程方法:

image

①、來(lái)看ofArgb距境,ofInt是對(duì)int類型的數(shù)字進(jìn)行變換申尼,而它的作用是可以對(duì)顏色進(jìn)行過(guò)度變換,我們還注意到垫桂,這些方法接收的都是可變參數(shù)师幕,意味可以傳入任意個(gè)參數(shù),它將依次變換。現(xiàn)在我想把按鈕的顏色從紅色過(guò)度到綠色再過(guò)度到藍(lán)色霹粥,首先創(chuàng)建ValueAnimator:

private void changeColor() {
    ValueAnimator valueAnimator = ValueAnimator.ofArgb(0xFFFF5454, 0xFF5DDE5D, 0xFF5DBEDE);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int color = (int) animation.getAnimatedValue();
            button.setBackgroundColor(color);
        }
    });
    valueAnimator.setDuration(2000);
    valueAnimator.start();
}

這里注意ofArgb需要接收ARGB的顏色灭将。效果如下所示:

image

②、ofFloat就不必多說(shuō)了吧后控。

③庙曙、ofObject這個(gè)我們后面再來(lái)研究,因?yàn)樗枰覀兲峁┳远x的Evaluator浩淘。

2.1.2捌朴、常用對(duì)象方法

前面我們已經(jīng)接觸過(guò)了幾個(gè)ValueAnimator提供的方法,比如setDuration用來(lái)設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)张抄,getAnimatedValue用來(lái)獲取當(dāng)前變換中的值砂蔽,start用來(lái)開(kāi)始動(dòng)畫(huà)。下面我們?cè)賮?lái)學(xué)習(xí)幾個(gè)常用的方法:

①署惯、setRepeatCount用于設(shè)置動(dòng)畫(huà)的重復(fù)次數(shù)左驾,傳入0表示不重復(fù),傳入ValueAnimator.INFINITE表示無(wú)限重復(fù)泽台,默認(rèn)是不重復(fù)什荣,這個(gè)方法比較好理解。

②怀酷、setRepeatMode用于設(shè)置動(dòng)畫(huà)重復(fù)的模式稻爬,有兩種選擇,一種是正序重復(fù)(ValueAnimator.RESTART)蜕依,另一種是倒序重復(fù)(ValueAnimator.REVERSE)桅锄,默認(rèn)是正序重復(fù)。這兩種有什么區(qū)別呢样眠?拿按鈕變顏色的例子來(lái)說(shuō)友瘤,如果我們把重復(fù)次數(shù)設(shè)定為無(wú)限重復(fù),那么每一次重復(fù)時(shí)檐束,都會(huì)從最后的藍(lán)色突然變成開(kāi)始的紅色然后繼續(xù)新一輪動(dòng)畫(huà)辫秧,這就是正序:

image

而如果將RepeatMode設(shè)置為ValueAnimator.REVERSE,將使用倒序的方式進(jìn)行動(dòng)畫(huà)的重復(fù)被丧,即:

第一輪:紅-綠-藍(lán)

第二輪:藍(lán)-綠-紅

第三輪:紅-綠-藍(lán)

以此類推盟戏,這樣就不存在突然從第三種顏色變?yōu)榈谝环N顏色的情況:

image

③、cancel用于取消當(dāng)前動(dòng)畫(huà)甥桂。

2.1.3柿究、常用監(jiān)聽(tīng)方法

我們已經(jīng)使用過(guò)一個(gè)監(jiān)聽(tīng)(addUpdateListener),它用于監(jiān)聽(tīng)動(dòng)畫(huà)執(zhí)行過(guò)程中值的變化黄选。ValueAnimator除了能監(jiān)聽(tīng)值的變化以外蝇摸,還能監(jiān)聽(tīng)動(dòng)畫(huà)執(zhí)行過(guò)程中的狀態(tài),可以使用addListener進(jìn)行添加:

valueAnimator.addListener(new 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) {
    }
});
  • onAnimationStart:顯然是在開(kāi)始執(zhí)行動(dòng)畫(huà)的時(shí)候調(diào)用。

  • onAnimationRepeat:在每次重復(fù)執(zhí)行時(shí)調(diào)用貌夕。

  • onAnimationCancel:在取消動(dòng)畫(huà)的時(shí)候調(diào)用律歼。

  • onAnimationEnd:在動(dòng)畫(huà)結(jié)束的時(shí)候調(diào)用,注意蜂嗽,無(wú)論是正常結(jié)束還是調(diào)用cancel結(jié)束苗膝,此方法都會(huì)被回調(diào)

如果我們只想監(jiān)聽(tīng)這四種動(dòng)畫(huà)狀態(tài)中的其中一個(gè)或是少數(shù)幾個(gè)植旧,實(shí)現(xiàn)Animator.AnimatorListener這個(gè)接口的匿名內(nèi)部類代價(jià)就太大了辱揭,因此我們可以使用AnimatorListenerAdapter這個(gè)抽象類,實(shí)際上它對(duì)Animator.AnimatorListener里面的方法都做了空實(shí)現(xiàn)病附,我們需要用到哪個(gè)方法就去重寫(xiě)哪個(gè)方法就行了:

valueAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
    }
});

順便提一句问窃,為何對(duì)Animator.AnimatorListener里的方法都提供了空實(shí)現(xiàn),還要把類聲明成abstract的呢完沪?這是因?yàn)閍bstract類不能被直接實(shí)例化域庇,這樣就能逼迫調(diào)用者至少去重寫(xiě)里面的一個(gè)方法。

2.3覆积、Interpolator插值器

插值器的作用是控制動(dòng)畫(huà)在執(zhí)行過(guò)程中的速度听皿,比如有一個(gè)valueAnimator,它能使一個(gè)值在兩秒內(nèi)從0過(guò)渡到200宽档,那過(guò)渡中的速度到底是怎么樣的呢尉姨?先快后慢?勻速吗冤?先慢后快又厉?控制值在變化中的速度,就是靠Interpolator來(lái)完成的椎瘟。

系統(tǒng)已經(jīng)為我們提供了非常多的插值器供我們選擇覆致,這里選擇最為簡(jiǎn)單的一個(gè)插值器(LinearInterpolator)來(lái)研究下Interpolator:

public class LinearInterpolator implements Interpolator {

    public LinearInterpolator() {
    }

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

我對(duì)LinearInterpolator類的代碼稍作刪減,便于查看肺蔚。首先它實(shí)現(xiàn)了Interpolator接口煌妈,只要實(shí)現(xiàn)了Interpolator(TimeInterpolator)接口的類就可以是一個(gè)插值器。我們來(lái)看看Interpolator接口的代碼:

public interface Interpolator extends TimeInterpolator {
}

這個(gè)接口只是繼承了TimeInterpolator接口宣羊,除此之外什么都沒(méi)做璧诵。來(lái)看最終的TimeInterpolator接口:

public interface TimeInterpolator {
    float getInterpolation(float input);
}

因此在插值器中只需要實(shí)現(xiàn)getInterpolation()方法就可以了。重點(diǎn)也就是在這個(gè)方法上段只,下面我將詳細(xì)說(shuō)明一下這個(gè)方法的作用:它接收一個(gè)float類型的參數(shù),這個(gè)參數(shù)的意思是表示當(dāng)前動(dòng)畫(huà)執(zhí)行到的進(jìn)度鉴扫,取值范圍是0~1赞枕,在動(dòng)畫(huà)剛剛開(kāi)始時(shí),值為0,在動(dòng)畫(huà)完成時(shí)炕婶,值為1姐赡。這個(gè)進(jìn)度值時(shí)系統(tǒng)幫我們計(jì)算出的,它永遠(yuǎn)是勻速增加的柠掂,不受任何設(shè)置的影響项滑。比如一秒內(nèi)值從1變換成10,在0.5秒時(shí)涯贞,值應(yīng)該為5枪狂,在0.8秒時(shí),值應(yīng)該為8宋渔,這就是勻速變化州疾。我們可以參考系統(tǒng)計(jì)算出的當(dāng)前動(dòng)畫(huà)的進(jìn)度值,計(jì)算并返回另一個(gè)進(jìn)度值皇拣,這個(gè)進(jìn)度值將會(huì)影響到AnimatorUpdateListener中數(shù)值的取值严蓖,最終達(dá)到控制數(shù)值變化速度的作用。LinearInterpolator是一個(gè)線性的插值器氧急,因此它能使動(dòng)畫(huà)在執(zhí)行過(guò)程中始終保持勻速執(zhí)行颗胡,因此它在getInterpolation方法中直接返回了參數(shù)input。我們可以使用setInterpolator()方法給動(dòng)畫(huà)設(shè)置插值器吩坝,在ValueAnimator中毒姨,如果沒(méi)有顯式設(shè)置插值器,會(huì)默認(rèn)使用AccelerateDecelerateInterpolator作為默認(rèn)插值器钾恢。AccelerateDecelerateInterpolator會(huì)使動(dòng)畫(huà)在開(kāi)始和結(jié)束時(shí)變化緩慢手素,在中間部分變化較快。除此之外還有

AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BaseInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, PathInterpolator

這些插值器瘩蚪,具體效果可查閱相關(guān)資料泉懦。

2.4、Evaluator

Evaluator的作用就是將插值器返回的進(jìn)度值轉(zhuǎn)換成對(duì)應(yīng)的數(shù)值疹瘦。在詳細(xì)介紹Evaluator前崩哩,我們先來(lái)梳理一下AnimatorUpdateListener中是怎么得到當(dāng)前變化的值?

image

通過(guò)上圖相信已經(jīng)能很清晰的區(qū)別出Interpolator與Evaluator的區(qū)別了言沐,前者是返回?cái)?shù)值區(qū)間變化的進(jìn)度邓嘹,范圍只能是0~1之間,而后者是返回當(dāng)前進(jìn)度對(duì)應(yīng)的具體值险胰,這個(gè)就跟我們使用哪種靜態(tài)工廠有關(guān)了汹押,如果我們使用ofInt,那么Evaluator計(jì)算后的返回值應(yīng)該是int類型起便;如果使用ofFloat棚贾,那么Evaluator計(jì)算后的返回值應(yīng)該是float類型窖维。所以說(shuō)Interpolator是可以通用的,而Evaluator是專用的妙痹。ValueAnimator中提供了設(shè)置Evaluator的方法:setEvaluator()铸史,下面我們以ofFloat為例,分析一下這個(gè)Evaluator:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 200);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    }
});
valueAnimator.start();

我們知道怯伊,如果沒(méi)有顯式指定Interpolator琳轿,它將默認(rèn)使用AccelerateDecelerateInterpolator,但是Evaluator呢耿芹?在PropertyValuesHolder這個(gè)類中崭篡,找到了如下內(nèi)容:

private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();

void init() {
    if (mEvaluator == null) {
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
        (mValueType == Float.class) ? sFloatEvaluator :null;
    }
    if (mEvaluator != null) {
        mKeyframes.setEvaluator(mEvaluator);
    }
}

如果是ofFloat,就會(huì)使用FloatEvaluator猩系,如果是ofInt媚送,就會(huì)使用IntEvaluator。如果之前設(shè)置過(guò)自定義Evaluator寇甸,就會(huì)使用自定義的Evaluator塘偎,如:

public static ValueAnimator ofArgb(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    anim.setEvaluator(ArgbEvaluator.getInstance());
    return anim;
}

ofArgb()就在靜態(tài)工廠方法中給ValueAnimator設(shè)置了ArgbEvaluator。下面我將詳細(xì)分析FloatEvaluator:

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

可以發(fā)現(xiàn)拿霉,它實(shí)現(xiàn)了TypeEvaluator接口:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

下面對(duì)參數(shù)作出解釋:

  • fraction:這個(gè)參數(shù)就是插值器返回的值吟秩,表示當(dāng)前動(dòng)畫(huà)對(duì)應(yīng)的值進(jìn)度(0~1)
  • startValue:表示我們所設(shè)置的值區(qū)間的開(kāi)始值(例如ofFloat(0, 200),則startValue為0)
  • endValue:表示我們所設(shè)置的值區(qū)間的結(jié)束值(例如ofFloat(0, 200)绽淘,則startValue為200)
  • 返回值表示計(jì)算后的具體值涵防,也就是我們?cè)贏nimatorUpdateListener->onAnimationUpdate中通過(guò)animation.getAnimatedValue()所得到的值

現(xiàn)在我們回過(guò)頭來(lái)看FloatEvaluator中的evaluate()方法:

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = startValue.floatValue();
    return startFloat + fraction * (endValue.floatValue() - startFloat);
}

這就應(yīng)該很好理解了,為了計(jì)算出在某一進(jìn)度時(shí)對(duì)應(yīng)的值沪铭,采用了如下公式:

當(dāng)前值 = 開(kāi)始值 + 進(jìn)度值 * (結(jié)束值 - 開(kāi)始值)

2.5壮池、ofObject

學(xué)習(xí)完了Evaluator以后,我們?cè)趤?lái)看下ValueAnimator提供的靜態(tài)工廠方法:

image

其中大部分我們都已經(jīng)使用過(guò)了杀怠,但是還有一個(gè)ofObject似乎我們從未提起過(guò)椰憋,之所以之前沒(méi)有提起,是因?yàn)檫€沒(méi)有學(xué)習(xí)Evaluator赔退,而現(xiàn)在是時(shí)候了解一下它了橙依!

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

這就是ofObject()方法的原型,可以看到它接收一個(gè)Evaluator和一個(gè)Object類型的可變參數(shù)硕旗。這就意味著我們可以處理任意類型的值窗骑。可是為什么還要傳入Evaluator呢漆枚?這是因?yàn)镋valuator的作用是根據(jù)進(jìn)度計(jì)算出當(dāng)前進(jìn)度所對(duì)應(yīng)的值创译,而現(xiàn)在這個(gè)Object是我們自己傳入的,系統(tǒng)并不知道如何去轉(zhuǎn)換墙基,因此必須要我們手動(dòng)提供一個(gè)自定義Evaluator软族。下面我將使用ofObject()方法來(lái)實(shí)現(xiàn)模擬小球下落(X坐標(biāo)與Y坐標(biāo)都會(huì)發(fā)生變化)辛藻,首先完成布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/view_ball"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@drawable/shape_ball" />

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="#35A9F7"/>
</shape>
image

因?yàn)槲覀儨?zhǔn)備實(shí)現(xiàn)的小球下落是X坐標(biāo)與Y坐標(biāo)都會(huì)發(fā)生變化,因此我們需要借助Point類來(lái)保存X坐標(biāo)與Y坐標(biāo)的值互订,在這里,我將自己定義Point類痘拆,而不使用android.graphics.Point仰禽,自定義Point如下:

public class Point {
    private int x;
    private int y;
    
    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

好了,基本的東西都準(zhǔn)備好了纺蛆,下面創(chuàng)建ValueAnimator:

ValueAnimator.ofObject(???, new Point(0, 0), new Point(300, 500));

一開(kāi)始就卡住了吐葵,原因是還沒(méi)有提供自定義Evaluator。下面我們定義一個(gè)BallEvaluator:

public class BallEvaluator implements TypeEvaluator<Point> {
    private Point point = new Point();

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        point.setX((int) (startValue.getX() + fraction * (endValue.getX() - startValue.getX())));
        point.setY((int) (startValue.getY() + fraction * (endValue.getY() - startValue.getY())));
        return point;
    }
}

現(xiàn)在我們可以繼續(xù)實(shí)現(xiàn)代碼了:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final View viewBall = findViewById(R.id.view_ball);

        ValueAnimator valueAnimator = ValueAnimator.ofObject(new BallEvaluator(),
                new Point(0, 0), new Point(300, 500));
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point point = (Point) animation.getAnimatedValue();
                viewBall.layout(point.getX(), point.getY(), point.getX() + viewBall.getWidth(),
                        point.getY() + viewBall.getHeight());
            }
        });
        valueAnimator.start();
    }
}

運(yùn)行效果:

image

三桥氏、ObjectAnimator

3.1温峭、這是什么

前面我們學(xué)習(xí)了ValueAnimator,難道大家沒(méi)發(fā)現(xiàn)一個(gè)問(wèn)題嗎字支?ValueAnimator只能針對(duì)值進(jìn)行動(dòng)畫(huà)改變凤藏,如果我們需要關(guān)聯(lián)到View的變化,就需要設(shè)置監(jiān)聽(tīng)事件堕伪,根據(jù)值得變化手動(dòng)去操作這個(gè)View變化揖庄。這相比補(bǔ)間動(dòng)畫(huà)要麻煩很多。為了能讓動(dòng)畫(huà)直接作用于View欠雌,Google基于ValueAnimator編寫(xiě)了ObjectAnimator蹄梢。也就是說(shuō)ObjectAnimator繼承自ValueAnimator,ValueAnimator能用的方法在ObjectAnimator中照樣可以用富俄。但是ObjectAnimator重新編寫(xiě)了幾個(gè)方法禁炒,例如:ofInt()、ofFloat()等霍比。

3.2幕袱、使用ObjectAnimator

既然說(shuō)ObjectAnimator能直接作用于View,想必一定很好用吧桂塞。下面我將使用ObjectAnimator實(shí)現(xiàn)讓TextView從不透明->透明->不透明的alpha動(dòng)畫(huà)凹蜂。按照慣例,先編寫(xiě)界面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HurryYu"
        android:textSize="18sp" />

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開(kāi)始動(dòng)畫(huà)" />

</LinearLayout>
image

然后編寫(xiě)業(yè)務(wù)代碼:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);
                objectAnimator.setDuration(2000);
                objectAnimator.start();
            }
        });
    }
}

關(guān)鍵部分就是ObjectAnimator了:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
objectAnimator.setDuration(2000);
objectAnimator.start();

ObjectAnimator的靜態(tài)工廠方法比ValueAnimator的多出了兩個(gè)參數(shù)阁危,下面我將介紹多出的兩個(gè)參數(shù)的含義:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
  • target:指定操作的是哪個(gè)控件
  • propertyName:指定要操作這個(gè)控件的哪個(gè)屬性

我們?nèi)extView查找是否真的存在alpha這個(gè)屬性玛痊,發(fā)現(xiàn)并沒(méi)有。接著我們?nèi)ニ母割愔胁檎铱翊颍矝](méi)有擂煞!那ObjectAnimator是如何改變透明度的呢?實(shí)際上它并不是直接修改第二個(gè)參數(shù)傳入的屬性趴乡,而是查找它對(duì)應(yīng)的set方法來(lái)設(shè)置值对省。例如上面的例子中蝗拿,我們傳入“alpha”,則它會(huì)去TextView中找setAlpha()方法蒿涎。那TextView中有這個(gè)方法嗎哀托?確實(shí)是有的,繼承自View劳秋。

因此我們要使用ObjectAnimator實(shí)現(xiàn)動(dòng)畫(huà)仓手,必須保證如下兩點(diǎn):

  1. 在要操作的控件中,必須存在對(duì)應(yīng)屬性的set方法玻淑,且該方法接收的參數(shù)類型要與靜態(tài)工廠方法所用類型一致嗽冒,例如ofInt()就要求set方法中接收的參數(shù)類型為int類型
  2. set方法的命名必須滿足駝峰命名法,例如屬性名為rotate补履,則對(duì)應(yīng)的set方法必須為setRotate()

注意ObjectAnimator會(huì)在設(shè)定時(shí)間內(nèi)不斷調(diào)用對(duì)應(yīng)屬性的set方法添坊,就像ValueAnimator中加監(jiān)聽(tīng)后會(huì)不斷回調(diào)onAnimationUpdate()方法一樣。不過(guò)它也是僅僅調(diào)用對(duì)應(yīng)屬性的set方法箫锤,而set方法中對(duì)控件的操作還是要我們自己去實(shí)現(xiàn)的贬蛙,只不過(guò)常用的操作系統(tǒng)已經(jīng)為我們實(shí)現(xiàn)好。

3.2.1谚攒、什么時(shí)候需要提供對(duì)應(yīng)屬性的get方法速客?

如果我們只給第三個(gè)參數(shù)傳入一個(gè)值,系統(tǒng)在執(zhí)行動(dòng)畫(huà)以前就會(huì)先去調(diào)用對(duì)應(yīng)屬性的get方法獲取初始值五鲫,如果沒(méi)有提供get方法溺职,則會(huì)使用默認(rèn)值。通過(guò)ofInt()構(gòu)造出的ObjectAnimator位喂,屬性的默認(rèn)值為0浪耘;ofFloat()構(gòu)造出來(lái)的,屬性的默認(rèn)值為0.0塑崖;ofObject()構(gòu)造出來(lái)的七冲,屬性的默認(rèn)值為null,這就有問(wèn)題了规婆,著名異常NullPointerException澜躺。因此,當(dāng)動(dòng)畫(huà)只傳入了一個(gè)過(guò)渡值時(shí)抒蚜,系統(tǒng)會(huì)調(diào)用屬性對(duì)應(yīng)的get方法來(lái)獲取初始值掘鄙,如果沒(méi)有提供get方法,則會(huì)使用屬性類型對(duì)應(yīng)的默認(rèn)值嗡髓,當(dāng)無(wú)法正常獲取到屬性的初始值時(shí)操漠,會(huì)直接報(bào)異常。

四饿这、AnimatorSet

之前我們使用的ObjectAnimator和ValueAnimator都只能同時(shí)播放一種動(dòng)畫(huà)浊伙,能不能讓多個(gè)動(dòng)畫(huà)同時(shí)播放或者按順序依次播放呢撞秋?答案是使用AnimatorSet。我們先來(lái)看看最基本的使用方法:

4.1嚣鄙、playSequentially()

AnimatorSet為我們提供了playSequentially()方法吻贿,此方法可以接收一個(gè)可變長(zhǎng)度的Animator或是一個(gè)List集合:

public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)

當(dāng)然我認(rèn)為一般情況下使用可變參數(shù)的那個(gè)更爽,省去了創(chuàng)建集合的麻煩哑子。這個(gè)方法的作用是能依次執(zhí)行傳入的動(dòng)畫(huà)廓八,一般情況下我們會(huì)傳入ObjectAnimator而不是ValueAnimator,原因相信大家看過(guò)前面的內(nèi)容后都能懂赵抢。需要特別說(shuō)明的是:它只能依次執(zhí)行傳入的動(dòng)畫(huà),如果其中一個(gè)動(dòng)畫(huà)是無(wú)限重復(fù)的声功,那么它后面的動(dòng)畫(huà)將都不會(huì)執(zhí)行烦却。必須確保執(zhí)行完成一個(gè)動(dòng)畫(huà)之后,才回去執(zhí)行下一個(gè)先巴。它的完整用法如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator alphaAnimator = 
                        ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
                ObjectAnimator rotateAnimator = 
                        ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
                ObjectAnimator scaleAnimator = 
                        ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
                ObjectAnimator translateAnimator = 
                        ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);
                
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playSequentially(alphaAnimator, rotateAnimator, scaleAnimator, translateAnimator);
                animatorSet.setDuration(2000);
                animatorSet.start();
            }
        });
    }
}

當(dāng)然也可以單獨(dú)給里面的每一個(gè)動(dòng)畫(huà)設(shè)置執(zhí)行時(shí)間其爵。但是請(qǐng)注意,如果單獨(dú)給每個(gè)動(dòng)畫(huà)設(shè)置了執(zhí)行時(shí)間伸蚯,就不要再去調(diào)用AnimatorSet的setDuration()摩渺,否則單個(gè)動(dòng)畫(huà)設(shè)置的時(shí)間會(huì)被覆蓋。

4.2剂邮、playTogether()

這個(gè)看名字就應(yīng)該能猜出是讓動(dòng)畫(huà)一起執(zhí)行的方法摇幻,同樣它也有一個(gè)重載:

public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)

其實(shí)這個(gè)方法只負(fù)責(zé)同時(shí)開(kāi)始傳入的所有動(dòng)畫(huà),至于里面的動(dòng)畫(huà)執(zhí)行時(shí)間是多長(zhǎng)挥萌?是不是一直重復(fù)绰姻?等等這些問(wèn)題都與它沒(méi)有關(guān)系。它的完整用法和上面的playSequentially()是一模一樣的引瀑。

4.3狂芋、AnimatorSet.Builder

Builder是AnimatorSet類中的內(nèi)部類,它里面提供了一些方法憨栽,我們可以使用這些方法來(lái)組合出一組動(dòng)畫(huà)帜矾,它可以控制這組動(dòng)畫(huà)中先執(zhí)行什么,后執(zhí)行什么屑柔,什么與什么一起執(zhí)行屡萤。下面我將列舉Builder中每個(gè)方法的含義:

方法名 說(shuō)明
with 設(shè)置當(dāng)前動(dòng)畫(huà)與前一個(gè)動(dòng)畫(huà)一起執(zhí)行
before 設(shè)置當(dāng)前動(dòng)畫(huà)在之前所有動(dòng)畫(huà)之后執(zhí)行,也可理解為之前的動(dòng)畫(huà)都在這個(gè)動(dòng)畫(huà)之前執(zhí)行
after 設(shè)置當(dāng)前動(dòng)畫(huà)在之前所有動(dòng)畫(huà)之前執(zhí)行掸宛,也可理解為之前的所有動(dòng)畫(huà)都在這個(gè)動(dòng)畫(huà)之后執(zhí)行

關(guān)于before與after的說(shuō)明灭衷,我個(gè)人的理解與網(wǎng)上很多文章的理解有一些不同,但實(shí)驗(yàn)結(jié)果卻是我這個(gè)說(shuō)法是對(duì)的旁涤。如果讀者發(fā)現(xiàn)我的理解錯(cuò)誤翔曲,請(qǐng)指出迫像,謝謝!

要得到AnimatorSet.Builder對(duì)象瞳遍,只能使用AnimatorSet的play()方法闻妓,下面我將使用AnimatorSet.Builder實(shí)現(xiàn)一個(gè)組合動(dòng)畫(huà):

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator alphaAnimator =
                        ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
                ObjectAnimator rotateAnimator =
                        ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
                rotateAnimator.setDuration(15000);
                ObjectAnimator scaleAnimator =
                        ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
                ObjectAnimator translateAnimator =
                        ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);

                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.play(alphaAnimator)
                        .with(rotateAnimator)
                        .after(scaleAnimator)
                        .before(translateAnimator);
                animatorSet.setDuration(2000).start();
            }
        });
    }
}

這個(gè)動(dòng)畫(huà)的執(zhí)行過(guò)程是:先執(zhí)行scaleAnimator,當(dāng)scaleAnimator執(zhí)行完成后掠械,alphaAnimator與rotateAnimator一起執(zhí)行由缆,等它們兩個(gè)執(zhí)行完成后,執(zhí)行translateAnimator猾蒂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末均唉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肚菠,更是在濱河造成了極大的恐慌舔箭,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚊逢,死亡現(xiàn)場(chǎng)離奇詭異层扶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)烙荷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)镜会,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人终抽,你說(shuō)我怎么就攤上這事戳表。” “怎么了昼伴?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵扒袖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我亩码,道長(zhǎng)季率,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任描沟,我火速辦了婚禮飒泻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吏廉。我一直安慰自己泞遗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布席覆。 她就那樣靜靜地躺著史辙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上聊倔,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天晦毙,我揣著相機(jī)與錄音,去河邊找鬼耙蔑。 笑死见妒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甸陌。 我是一名探鬼主播须揣,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钱豁!你這毒婦竟也來(lái)了耻卡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤牲尺,失蹤者是張志新(化名)和其女友劉穎卵酪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體秸谢,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年霹肝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了估蹄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沫换,死狀恐怖臭蚁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讯赏,我是刑警寧澤垮兑,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站漱挎,受9級(jí)特大地震影響系枪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜磕谅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一私爷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膊夹,春花似錦衬浑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春助币,著一層夾襖步出監(jiān)牢的瞬間浪听,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工奠支, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留馋辈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓倍谜,卻偏偏與公主長(zhǎng)得像迈螟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尔崔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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