Android 動(dòng)畫(huà)總結(jié)

在日常的Android開(kāi)發(fā)中,經(jīng)常會(huì)使用到動(dòng)畫(huà)俺泣,這里就對(duì)Android開(kāi)發(fā)中的動(dòng)畫(huà)做一下總結(jié)

Android 動(dòng)畫(huà)分類

總的來(lái)說(shuō)距潘,Android動(dòng)畫(huà)可以分為兩類,最初的傳統(tǒng)動(dòng)畫(huà)和Android3.0 之后出現(xiàn)的屬性動(dòng)畫(huà)圆恤;
傳統(tǒng)動(dòng)畫(huà)又包括 幀動(dòng)畫(huà)(Frame Animation)和補(bǔ)間動(dòng)畫(huà)(Tweened Animation)。

傳統(tǒng)動(dòng)畫(huà)

幀動(dòng)畫(huà)

幀動(dòng)畫(huà)是最容易實(shí)現(xiàn)的一種動(dòng)畫(huà)腔稀,這種動(dòng)畫(huà)更多的依賴于完善的UI資源盆昙,他的原理就是將一張張單獨(dú)的圖片連貫的進(jìn)行播放,
從而在視覺(jué)上產(chǎn)生一種動(dòng)畫(huà)的效果焊虏;有點(diǎn)類似于某些軟件制作gif動(dòng)畫(huà)的方式淡喜。

如上圖中的京東加載動(dòng)畫(huà),代碼要做的事情就是把一幅幅的圖片按順序顯示诵闭,造成動(dòng)畫(huà)的視覺(jué)效果炼团。
京東動(dòng)畫(huà)實(shí)現(xiàn)

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/a_0"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_2"
        android:duration="100" />
</animation-list>
protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        ImageView animationImg1 = (ImageView) findViewById(R.id.animation1);
        animationImg1.setImageResource(R.drawable.frame_anim1);
        AnimationDrawable animationDrawable1 = (AnimationDrawable) animationImg1.getDrawable();
        animationDrawable1.start();
    }

可以說(shuō),圖片資源決定了這種方式可以實(shí)現(xiàn)怎樣的動(dòng)畫(huà)

在有些代碼中疏尿,我們還會(huì)看到android:oneshot="false" 瘟芝,這個(gè)oneshot 的含義就是動(dòng)畫(huà)執(zhí)行一次(true)還是循環(huán)執(zhí)行多次。
這里其他幾個(gè)動(dòng)畫(huà)實(shí)現(xiàn)方式都是一樣润歉,無(wú)非就是圖片資源的差異模狭。

補(bǔ)間動(dòng)畫(huà)

補(bǔ)間動(dòng)畫(huà)又可以分為四種形式,分別是 alpha(淡入淡出)踩衩,translate(位移)嚼鹉,scale(縮放大小)驱富,rotate(旋轉(zhuǎn))锚赤。
補(bǔ)間動(dòng)畫(huà)的實(shí)現(xiàn),一般會(huì)采用xml 文件的形式褐鸥;代碼會(huì)更容易書(shū)寫(xiě)和閱讀线脚,同時(shí)也更容易復(fù)用。

XML 實(shí)現(xiàn)

首先叫榕,在res/anim/ 文件夾下定義如下的動(dòng)畫(huà)實(shí)現(xiàn)方式

alpha_anim.xml 動(dòng)畫(huà)實(shí)現(xiàn)

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:toAlpha="0.0" />


scale.xml 動(dòng)畫(huà)實(shí)現(xiàn)

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0"/>


然后浑侥,在Activity中

Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);

這樣就可以實(shí)現(xiàn)ImageView alpha 透明變化的動(dòng)畫(huà)效果。

也可以使用set 標(biāo)簽將多個(gè)動(dòng)畫(huà)組合(代碼源自Android SDK API)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>


可以看到組合動(dòng)畫(huà)是可以嵌套使用的晰绎。

各個(gè)動(dòng)畫(huà)屬性的含義結(jié)合動(dòng)畫(huà)自身的特點(diǎn)應(yīng)該很好理解寓落,就不一一闡述了;這里主要說(shuō)一下interpolator 和 pivot荞下。

Interpolator 主要作用是可以控制動(dòng)畫(huà)的變化速率 伶选,就是動(dòng)畫(huà)進(jìn)行的快慢節(jié)奏。

Android 系統(tǒng)已經(jīng)為我們提供了一些Interpolator 尖昏,比如 accelerate_decelerate_interpolator仰税,accelerate_interpolator等。更多的interpolator 及其含義可以在Android SDK 中查看抽诉。同時(shí)這個(gè)Interpolator也是可以自定義的陨簇,這個(gè)后面還會(huì)提到。

pivot 決定了當(dāng)前動(dòng)畫(huà)執(zhí)行的參考位置

pivot 這個(gè)屬性主要是在translate 和 scale 動(dòng)畫(huà)中迹淌,這兩種動(dòng)畫(huà)都牽扯到view 的“物理位置“發(fā)生變化塞帐,所以需要一個(gè)參考點(diǎn)。而pivotX和pivotY就共同決定了這個(gè)點(diǎn)巍沙;它的值可以是float或者是百分比數(shù)值葵姥。
我們以pivotX為例,

pivotX取值 含義
10 距離動(dòng)畫(huà)所在view自身左邊緣10像素
10% 距離動(dòng)畫(huà)所在view自身左邊緣 的距離是整個(gè)view寬度的10%
10%p 距離動(dòng)畫(huà)所在view父控件左邊緣的距離是整個(gè)view寬度的10%

pivotY 也是相同的原理句携,只不過(guò)變成的縱向的位置榔幸。如果還是不明白可以參考源碼,在Tweened Animation中結(jié)合seekbar的滑動(dòng)觀察rotate的變化理解矮嫉。

Java Code 實(shí)現(xiàn)

有時(shí)候削咆,動(dòng)畫(huà)的屬性值可能需要?jiǎng)討B(tài)的調(diào)整,這個(gè)時(shí)候使用xml 就不合適了蠢笋,需要使用java代碼實(shí)現(xiàn)

private void RotateAnimation() {
        animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF,
                pxValue, Animation.RELATIVE_TO_SELF, pyValue);
        animation.setDuration(timeValue);

        if (keep.isChecked()) {
            animation.setFillAfter(true);
        } else {
            animation.setFillAfter(false);
        }
        if (loop.isChecked()) {
            animation.setRepeatCount(-1);
        } else {
            animation.setRepeatCount(0);
        }

        if (reverse.isChecked()) {
            animation.setRepeatMode(Animation.REVERSE);
        } else {
            animation.setRepeatMode(Animation.RESTART);
        }
        img.startAnimation(animation);
    }



這里animation.setFillAfter決定了動(dòng)畫(huà)在播放結(jié)束時(shí)是否保持最終的狀態(tài)拨齐;animation.setRepeatCount和animation.setRepeatMode 決定了動(dòng)畫(huà)的重復(fù)次數(shù)及重復(fù)方式,具體細(xì)節(jié)可查看源碼理解昨寞。

好了瞻惋,傳統(tǒng)動(dòng)畫(huà)的內(nèi)容就說(shuō)到這里了厦滤。

屬性動(dòng)畫(huà)

屬性動(dòng)畫(huà),顧名思義它是對(duì)于對(duì)象屬性的動(dòng)畫(huà)歼狼。因此掏导,所有補(bǔ)間動(dòng)畫(huà)的內(nèi)容,都可以通過(guò)屬性動(dòng)畫(huà)實(shí)現(xiàn)羽峰。

屬性動(dòng)畫(huà)入門(mén)

首先我們來(lái)看看如何用屬性動(dòng)畫(huà)實(shí)現(xiàn)上面補(bǔ)間動(dòng)畫(huà)的效果

private void RotateAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
        anim.setDuration(1000);
        anim.start();
    }

    private void AlpahAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f);
        anim.setRepeatCount(-1);
        anim.setRepeatMode(ObjectAnimator.REVERSE);
        anim.setDuration(2000);
        anim.start();
    }


這兩個(gè)方法用屬性動(dòng)畫(huà)的方式分別實(shí)現(xiàn)了旋轉(zhuǎn)動(dòng)畫(huà)和淡入淡出動(dòng)畫(huà)趟咆,其中setDuration、setRepeatMode及setRepeatCount和補(bǔ)間動(dòng)畫(huà)中的概念是一樣的梅屉。

可以看到值纱,屬性動(dòng)畫(huà)貌似強(qiáng)大了許多,實(shí)現(xiàn)很方便坯汤,同時(shí)動(dòng)畫(huà)可變化的值也有了更多的選擇虐唠,動(dòng)畫(huà)所能呈現(xiàn)的細(xì)節(jié)也更多。

當(dāng)然屬性動(dòng)畫(huà)也是可以組合實(shí)現(xiàn)的

ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f);
ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f);
ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360);
ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400);
ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750);
AnimatorSet set = new AnimatorSet();
set.playTogether(alphaAnim,scaleXAnim,scaleYAnim,rotateAnim,transXAnim,transYAnim);
//set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
set.setDuration(3000);
set.start();

可以看到這些動(dòng)畫(huà)可以同時(shí)播放玫霎,或者是按序播放凿滤。

屬性動(dòng)畫(huà)核心原理

在上面實(shí)現(xiàn)屬性動(dòng)畫(huà)的時(shí)候,我們反復(fù)的使用到了ObjectAnimator 這個(gè)類庶近,這個(gè)類繼承自ValueAnimator翁脆,使用這個(gè)類可以對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫(huà)操作。而ValueAnimator是整個(gè)屬性動(dòng)畫(huà)機(jī)制當(dāng)中最核心的一個(gè)類鼻种;這點(diǎn)從下面的圖片也可以看出反番。


屬性動(dòng)畫(huà)核心原理,此圖來(lái)自于Android SDK API 文檔叉钥。

屬性動(dòng)畫(huà)的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的罢缸,而初始值和結(jié)束值之間的動(dòng)畫(huà)過(guò)渡就是由ValueAnimator這個(gè)類來(lái)負(fù)責(zé)計(jì)算的。它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫(huà)過(guò)渡投队,我們只需要將初始值和結(jié)束值提供給ValueAnimator枫疆,并且告訴它動(dòng)畫(huà)所需運(yùn)行的時(shí)長(zhǎng),那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果敷鸦。除此之外息楔,ValueAnimator還負(fù)責(zé)管理動(dòng)畫(huà)的播放次數(shù)、播放模式扒披、以及對(duì)動(dòng)畫(huà)設(shè)置監(jiān)聽(tīng)器等值依。

從上圖我們可以了解到,通過(guò)duration碟案、startPropertyValue和endPropertyValue 等值愿险,我們就可以定義動(dòng)畫(huà)運(yùn)行時(shí)長(zhǎng),初始值和結(jié)束值价说。然后通過(guò)start方法開(kāi)始動(dòng)畫(huà)辆亏。
那么ValueAnimator 到底是怎樣實(shí)現(xiàn)從初始值平滑過(guò)渡到結(jié)束值的呢风秤?這個(gè)就是由TypeEvaluator 和TimeInterpolator 共同決定的。

具體來(lái)說(shuō)褒链,TypeEvaluator 決定了動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值唁情。
TimeInterpolator 決定了動(dòng)畫(huà)從初始值過(guò)渡到結(jié)束值的節(jié)奏疑苔。

說(shuō)的通俗一點(diǎn)甫匹,你每天早晨出門(mén)去公司上班,TypeEvaluator決定了你是坐公交惦费、坐地鐵還是騎車兵迅;而當(dāng)你決定騎車后,TimeInterpolator決定了你一路上騎行的方式薪贫,你可以勻速的一路騎到公司恍箭,你也可以前半程騎得飛快,后半程騎得慢悠悠瞧省。

如果扯夭,還是不理解,那么就看下面的代碼吧鞍匾。首先看一下下面的這兩個(gè)gif動(dòng)畫(huà)交洗,一個(gè)小球在屏幕上以 y=sin(x) 的數(shù)學(xué)函數(shù)軌跡運(yùn)行,同時(shí)小球的顏色和半徑也發(fā)生著變化橡淑,可以發(fā)現(xiàn)构拳,兩幅圖動(dòng)畫(huà)變化的節(jié)奏也是不一樣的。

如果不考慮屬性動(dòng)畫(huà)梁棠,這樣的一個(gè)動(dòng)畫(huà)純粹的使用Canvas+Handler的方式繪制也是有可能實(shí)現(xiàn)的置森。但是會(huì)復(fù)雜很多,而且加上各種線程符糊,會(huì)帶來(lái)很多意想不到的問(wèn)題凫海。

這里就通過(guò)自定義屬性動(dòng)畫(huà)的方式看看這個(gè)動(dòng)畫(huà)是如何實(shí)現(xiàn)的。

屬性動(dòng)畫(huà)自定義實(shí)現(xiàn)

這個(gè)動(dòng)畫(huà)最關(guān)鍵的三點(diǎn)就是 運(yùn)動(dòng)軌跡男娄、小球半徑及顏色的變化行贪;我們就從這三個(gè)方面展開(kāi)。最后我們?cè)诮Y(jié)合Interpolator說(shuō)一下TimeInterpolator的意義沪伙。

用TypeEvaluator 確定運(yùn)動(dòng)軌跡

前面說(shuō)了瓮顽,TypeEvaluator決定了動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值。這個(gè)TypeEvaluator是個(gè)接口围橡,我們可以實(shí)現(xiàn)這個(gè)接口暖混。

public class PointSinEvaluator implements TypeEvaluator {

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());

        float y = (float) (Math.sin(x * Math.PI / 180) * 100) + endPoint.getY() / 2;
        Point point = new Point(x, y);
        return point;
    }
}


PointSinEvaluator 繼承了TypeEvaluator類,并實(shí)現(xiàn)了他唯一的方法evaluate翁授;這個(gè)方法有三個(gè)參數(shù)拣播,第一個(gè)參數(shù)fraction 代表當(dāng)前動(dòng)畫(huà)完成的百分比晾咪,這個(gè)值是如何變化的后面還會(huì)提到;第二個(gè)和第三個(gè)參數(shù)代表動(dòng)畫(huà)的初始值和結(jié)束值贮配。這里我們的邏輯很簡(jiǎn)單谍倦,x的值隨著fraction 不斷變化,并最終達(dá)到結(jié)束值泪勒;y的值就是當(dāng)前x值所對(duì)應(yīng)的sin(x) 值昼蛀,然后用x 和 y 產(chǎn)生一個(gè)新的點(diǎn)(Point對(duì)象)返回。

這樣我們就可以使用這個(gè)PointSinEvaluator 生成屬性動(dòng)畫(huà)的實(shí)例了圆存。

Point startP = new Point(RADIUS, RADIUS);//初始值(起點(diǎn))
        Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//結(jié)束值(終點(diǎn))
        final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentPoint = (Point) animation.getAnimatedValue();
                postInvalidate();
            }
        });


這樣我們就完成了動(dòng)畫(huà)軌跡的定義叼旋,現(xiàn)在只要調(diào)用valueAnimator.start() 方法,就會(huì)繪制出一個(gè)正弦曲線的軌跡沦辙。
顏色及半徑動(dòng)畫(huà)實(shí)現(xiàn)

之前我們說(shuō)過(guò)夫植,使用ObjectAnimator 可以對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫(huà)操作,這句話是不太嚴(yán)謹(jǐn)?shù)挠脱叮@個(gè)任意屬性還需要有g(shù)et 和 set 方法详民。

public class PointAnimView extends View {

    /**
     * 實(shí)現(xiàn)關(guān)于color 的屬性動(dòng)畫(huà)
     */
    private int color;
    private float radius = RADIUS;

    .....

}


這里在我們的自定義view中,定義了兩個(gè)屬性color 和 radius陌兑,并實(shí)現(xiàn)了他們各自的get set 方法沈跨,這樣我們就可以使用屬性動(dòng)畫(huà)的特點(diǎn)實(shí)現(xiàn)小球顏色變化的動(dòng)畫(huà)和半徑變化的動(dòng)畫(huà)。

ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
                Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
        animColor.setRepeatCount(-1);
        animColor.setRepeatMode(ValueAnimator.REVERSE);


        ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
        animScale.setRepeatCount(-1);
        animScale.setRepeatMode(ValueAnimator.REVERSE);
        animScale.setDuration(5000);
        animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radius = (float) animation.getAnimatedValue();
            }
        });


這里诀紊,我們使用ObjectAnimator 實(shí)現(xiàn)對(duì)color 屬性的值按照ArgbEvaluator 這個(gè)類的規(guī)律在給定的顏色值之間變化谒出,這個(gè)ArgbEvaluator 和我們之前定義的PointSinEvaluator一樣,都是決定動(dòng)畫(huà)如何從初始值過(guò)渡到結(jié)束值的邻奠,只不過(guò)這個(gè)類是系統(tǒng)自帶的笤喳,我們直接拿來(lái)用就可以,他可以實(shí)現(xiàn)各種顏色間的自由過(guò)渡碌宴。

對(duì)radius 這個(gè)屬性使用了ValueAnimator杀狡,使用了其ofFloat方法實(shí)現(xiàn)了一系列float值的變化;同時(shí)為其添加了動(dòng)畫(huà)變化的監(jiān)聽(tīng)器贰镣,在屬性值更新的過(guò)程中呜象,我們可以將變化的結(jié)果賦給radius,這樣就實(shí)現(xiàn)了半徑動(dòng)態(tài)的變化碑隆。

這里radius 也可以使用和color相同的方式恭陡,只需要把ArgbEvaluator 替換為FloatEvaluator,同時(shí)修改動(dòng)畫(huà)的變化值即可上煤;使用添加監(jiān)聽(tīng)器的方式休玩,只是為了介紹監(jiān)聽(tīng)器的使用方法而已

好了,到這里我們已經(jīng)定義出了所有需要的動(dòng)畫(huà),前面說(shuō)過(guò)拴疤,屬性動(dòng)畫(huà)也是可以組合使用的永部。因此,在動(dòng)畫(huà)啟動(dòng)的時(shí)候呐矾,同時(shí)播放這三個(gè)動(dòng)畫(huà)苔埋,就可以實(shí)現(xiàn)圖中的效果了。

animSet = new AnimatorSet();
        animSet.play(valueAnimator).with(animColor).with(animScale);
        animSet.setDuration(5000);
        animSet.setInterpolator(interpolatorType);
        animSet.start();



PointAnimView 源碼

public class PointAnimView extends View {

    public static final float RADIUS = 20f;

    private Point currentPoint;

    private Paint mPaint;
    private Paint linePaint;

    private AnimatorSet animSet;
    private TimeInterpolator interpolatorType = new LinearInterpolator();

    /**
     * 實(shí)現(xiàn)關(guān)于color 的屬性動(dòng)畫(huà)
     */
    private int color;
    private float radius = RADIUS;

    public PointAnimView(Context context) {
        super(context);
        init();
    }


    public PointAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PointAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
        mPaint.setColor(this.color);
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.TRANSPARENT);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(Color.BLACK);
        linePaint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            drawCircle(canvas);
//            StartAnimation();
        } else {
            drawCircle(canvas);
        }

        drawLine(canvas);
    }

    private void drawLine(Canvas canvas) {
        canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint);
        canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint);
        canvas.drawPoint(currentPoint.getX(), currentPoint.getY(), linePaint);

    }

    public void StartAnimation() {
        Point startP = new Point(RADIUS, RADIUS);
        Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
        final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentPoint = (Point) animation.getAnimatedValue();
                postInvalidate();
            }
        });

//
        ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
                Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
        animColor.setRepeatCount(-1);
        animColor.setRepeatMode(ValueAnimator.REVERSE);


        ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
        animScale.setRepeatCount(-1);
        animScale.setRepeatMode(ValueAnimator.REVERSE);
        animScale.setDuration(5000);
        animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radius = (float) animation.getAnimatedValue();
            }
        });


        animSet = new AnimatorSet();
        animSet.play(valueAnimator).with(animColor).with(animScale);
        animSet.setDuration(5000);
        animSet.setInterpolator(interpolatorType);
        animSet.start();

    }

    private void drawCircle(Canvas canvas) {
        float x = currentPoint.getX();
        float y = currentPoint.getY();
        canvas.drawCircle(x, y, radius, mPaint);
    }


    public void setInterpolatorType(int type ) {
        switch (type) {
            case 1:
                interpolatorType = new BounceInterpolator();
                break;
            case 2:
                interpolatorType = new AccelerateDecelerateInterpolator();
                break;
            case 3:
                interpolatorType = new DecelerateInterpolator();
                break;
            case 4:
                interpolatorType = new AnticipateInterpolator();
                break;
            case 5:
                interpolatorType = new LinearInterpolator();
                break;
            case 6:
                interpolatorType=new LinearOutSlowInInterpolator();
                break;
            case 7:
                interpolatorType = new OvershootInterpolator();
            default:
                interpolatorType = new LinearInterpolator();
                break;
        }
    }


    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void pauseAnimation() {
        if (animSet != null) {
            animSet.pause();
        }
    }


    public void stopAnimation() {
        if (animSet != null) {
            animSet.cancel();
            this.clearAnimation();
        }
    }
}


TimeInterpolator 介紹

Interpolator的概念其實(shí)我們并不陌生蜒犯,在補(bǔ)間動(dòng)畫(huà)中我們就使用到了组橄。他就是用來(lái)控制動(dòng)畫(huà)快慢節(jié)奏的;而在屬性動(dòng)畫(huà)中愧薛,TimeInterpolator 也是類似的作用晨炕;TimeInterpolator 繼承自Interpolator衫画。我們可以繼承TimerInterpolator 以自己的方式控制動(dòng)畫(huà)變化的節(jié)奏毫炉,也可以使用Android 系統(tǒng)提供的Interpolator。

下面都是系統(tǒng)幫我們定義好的一些Interpolator削罩,我們可以通過(guò)setInterpolator 設(shè)置不同的Interpolator瞄勾。

這里我們使用的Interpolator就決定了 前面我們提到的fraction。變化的節(jié)奏決定了動(dòng)畫(huà)所執(zhí)行的百分比弥激。不得不說(shuō)进陡,這么ValueAnimator的設(shè)計(jì)的確是很巧妙。

XML 屬性動(dòng)畫(huà)

這里提一下微服,屬性動(dòng)畫(huà)當(dāng)然也可以使用xml文件的方式實(shí)現(xiàn)趾疚,但是屬性動(dòng)畫(huà)的屬性值一般會(huì)牽扯到對(duì)象具體的屬性,更多是通過(guò)代碼動(dòng)態(tài)獲取以蕴,所以xml文件的實(shí)現(xiàn)會(huì)有些不方便糙麦。

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>


使用方式:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();


xml 文件中的標(biāo)簽也和屬性動(dòng)畫(huà)的類相對(duì)應(yīng)。
ValueAnimator --- <animator>
ObjectAnimator --- <objectAnimator>
AnimatorSet --- <set>

這些就是屬性動(dòng)畫(huà)的核心內(nèi)容〈园梗現(xiàn)在使用屬性動(dòng)畫(huà)的特性自定義動(dòng)畫(huà)應(yīng)該不是難事了赡磅。其余便簽的含義,結(jié)合之前的內(nèi)容應(yīng)該不難理解了宝与。

傳統(tǒng)動(dòng)畫(huà) VS 屬性動(dòng)畫(huà)

相較于傳統(tǒng)動(dòng)畫(huà)焚廊,屬性動(dòng)畫(huà)有很多優(yōu)勢(shì)。那是否意味著屬性動(dòng)畫(huà)可以完全替代傳統(tǒng)動(dòng)畫(huà)呢习劫。其實(shí)不然咆瘟,兩種動(dòng)畫(huà)都有各自的優(yōu)勢(shì),屬性動(dòng)畫(huà)如此強(qiáng)大诽里,也不是沒(méi)有缺點(diǎn)袒餐。

從上面兩幅圖比較可以發(fā)現(xiàn),補(bǔ)間動(dòng)畫(huà)中,雖然使用translate將圖片移動(dòng)了匿乃,但是點(diǎn)擊原來(lái)的位置桩皿,依舊可以發(fā)生點(diǎn)擊事件,而屬性動(dòng)畫(huà)卻不是幢炸。因此我們可以確定泄隔,屬性動(dòng)畫(huà)才是真正的實(shí)現(xiàn)了view的移動(dòng),補(bǔ)間動(dòng)畫(huà)對(duì)view的移動(dòng)更像是在不同地方繪制了一個(gè)影子宛徊,實(shí)際的對(duì)象還是處于原來(lái)的地方佛嬉。

當(dāng)我們把動(dòng)畫(huà)的repeatCount設(shè)置為無(wú)限循環(huán)時(shí),如果在Activity退出時(shí)沒(méi)有及時(shí)將動(dòng)畫(huà)停止闸天,屬性動(dòng)畫(huà)會(huì)導(dǎo)致Activity無(wú)法釋放而導(dǎo)致內(nèi)存泄漏暖呕,而補(bǔ)間動(dòng)畫(huà)卻沒(méi)有問(wèn)題。因此苞氮,使用屬性動(dòng)畫(huà)時(shí)切記在Activity執(zhí)行 onStop 方法時(shí)順便將動(dòng)畫(huà)停止湾揽。(對(duì)這個(gè)懷疑的同學(xué)可以自己通過(guò)在動(dòng)畫(huà)的Update 回調(diào)方法打印日志的方式進(jìn)行驗(yàn)證)。

xml 文件實(shí)現(xiàn)的補(bǔ)間動(dòng)畫(huà)笼吟,復(fù)用率極高库物。在Activity切換,窗口彈出時(shí)等情景中有著很好的效果贷帮。

使用幀動(dòng)畫(huà)時(shí)需要注意戚揭,不要使用過(guò)多特別大的圖,容易導(dǎo)致內(nèi)存不足撵枢。

好了民晒,關(guān)于Android 動(dòng)畫(huà)的總結(jié)就到這里。
最后 有興趣的同學(xué)可查看github 源碼歡迎star & fork

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锄禽,一起剝皮案震驚了整個(gè)濱河市潜必,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沟绪,老刑警劉巖刮便,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绽慈,居然都是意外死亡恨旱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén)坝疼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)搜贤,“玉大人,你說(shuō)我怎么就攤上這事钝凶∫敲ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掂名。 經(jīng)常有香客問(wèn)我据沈,道長(zhǎng),這世上最難降的妖魔是什么饺蔑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任锌介,我火速辦了婚禮,結(jié)果婚禮上猾警,老公的妹妹穿的比我還像新娘孔祸。我一直安慰自己,他們只是感情好发皿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布崔慧。 她就那樣靜靜地躺著,像睡著了一般穴墅。 火紅的嫁衣襯著肌膚如雪惶室。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天封救,我揣著相機(jī)與錄音拇涤,去河邊找鬼。 笑死誉结,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的券躁。 我是一名探鬼主播惩坑,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼也拜!你這毒婦竟也來(lái)了以舒?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慢哈,失蹤者是張志新(化名)和其女友劉穎蔓钟,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體卵贱,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滥沫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了键俱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兰绣。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖编振,靈堂內(nèi)的尸體忽然破棺而出缀辩,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布臀玄,位于F島的核電站瓢阴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏健无。R本人自食惡果不足惜炫掐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望睬涧。 院中可真熱鬧募胃,春花似錦、人聲如沸畦浓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)讶请。三九已至祷嘶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間夺溢,已是汗流浹背论巍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留风响,地道東北人嘉汰。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像状勤,于是被迫代替她去往敵國(guó)和親鞋怀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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

  • 在日常的Android開(kāi)發(fā)中持搜,經(jīng)常會(huì)使用到動(dòng)畫(huà)密似,這里就對(duì)Android開(kāi)發(fā)中的動(dòng)畫(huà)做一下總結(jié)。 Android 動(dòng)...
    IAM四十二閱讀 132,824評(píng)論 23 349
  • 1 背景 不能只分析源碼呀葫盼,分析的同時(shí)也要整理歸納基礎(chǔ)知識(shí)残腌,剛好有人微博私信讓全面說(shuō)說(shuō)Android的動(dòng)畫(huà),所以今...
    未聞椛洺閱讀 2,711評(píng)論 0 10
  • 做法: 1贫导、將小麥洗凈放在玻璃瓶或瓷碗里泡水過(guò)夜抛猫。備注:水要有過(guò)濾水(純凈水或礦物質(zhì)水)。 2脱盲、第二天邑滨,將水倒掉,...
    阿里123閱讀 398評(píng)論 0 0
  • 如果你不是一名iOS開(kāi)發(fā)人員钱反,閱讀本文只是想要大概的了解iOS10.3更新了哪些內(nèi)容掖看,那么你看完下面兩幅圖就不要再...
    哆來(lái)閱讀 658評(píng)論 0 5
  • 我親愛(ài)的蒼鷹 你能否穿過(guò)皚皚雪山匣距, 為我 銜一枝玫瑰。 夜晚 哎壳,讓我穿過(guò)這荒涼原野上毅待, 絢麗的彩霞在我頭上奔涌。 ...
    張新怡閱讀 254評(píng)論 0 4