Android 動畫總結(jié)

在日常的Android開發(fā)中,經(jīng)常會使用到動畫,這里就對Android開發(fā)中的動畫做一下總結(jié)

Android 動畫分類

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

傳統(tǒng)動畫

幀動畫

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

如上圖中的京東加載動畫,代碼要做的事情就是把一幅幅的圖片按順序顯示,造成動畫的視覺效果。
京東動畫實(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();
    }

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

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

補(bǔ)間動畫

補(bǔ)間動畫又可以分為四種形式,分別是 alpha(淡入淡出),translate(位移),scale(縮放大?。?,rotate(旋轉(zhuǎn))。
補(bǔ)間動畫的實(shí)現(xiàn),一般會采用xml 文件的形式;代碼會更容易書寫和閱讀,同時也更容易復(fù)用。

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

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

alpha_anim.xml 動畫實(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 動畫實(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 透明變化的動畫效果。

也可以使用set 標(biāo)簽將多個動畫組合(代碼源自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>


可以看到組合動畫是可以嵌套使用的。

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

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

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

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

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

pivotX取值 含義
10 距離動畫所在view自身左邊緣10像素
10% 距離動畫所在view自身左邊緣 的距離是整個view寬度的10%
10%p 距離動畫所在view父控件左邊緣的距離是整個view寬度的10%

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

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

有時候,動畫的屬性值可能需要動態(tài)的調(diào)整,這個時候使用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決定了動畫在播放結(jié)束時是否保持最終的狀態(tài);animation.setRepeatCount和animation.setRepeatMode 決定了動畫的重復(fù)次數(shù)及重復(fù)方式,具體細(xì)節(jié)可查看源碼理解。

好了,傳統(tǒng)動畫的內(nèi)容就說到這里了。

屬性動畫

屬性動畫,顧名思義它是對于對象屬性的動畫。因此,所有補(bǔ)間動畫的內(nèi)容,都可以通過屬性動畫實(shí)現(xiàn)。

屬性動畫入門

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

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


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

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

當(dāng)然屬性動畫也是可以組合實(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();

可以看到這些動畫可以同時播放,或者是按序播放。

屬性動畫核心原理

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


屬性動畫核心原理,此圖來自于Android SDK API 文檔。

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

從上圖我們可以了解到,通過duration、startPropertyValue和endPropertyValue 等值,我們就可以定義動畫運(yùn)行時長,初始值和結(jié)束值。然后通過start方法開始動畫。
那么ValueAnimator 到底是怎樣實(shí)現(xiàn)從初始值平滑過渡到結(jié)束值的呢?這個就是由TypeEvaluator 和TimeInterpolator 共同決定的。

具體來說,TypeEvaluator 決定了動畫如何從初始值過渡到結(jié)束值。
TimeInterpolator 決定了動畫從初始值過渡到結(jié)束值的節(jié)奏。

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

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

如果不考慮屬性動畫,這樣的一個動畫純粹的使用Canvas+Handler的方式繪制也是有可能實(shí)現(xiàn)的。但是會復(fù)雜很多,而且加上各種線程,會帶來很多意想不到的問題。

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

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

這個動畫最關(guān)鍵的三點(diǎn)就是 運(yùn)動軌跡、小球半徑及顏色的變化;我們就從這三個方面展開。最后我們在結(jié)合Interpolator說一下TimeInterpolator的意義。

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

前面說了,TypeEvaluator決定了動畫如何從初始值過渡到結(jié)束值。這個TypeEvaluator是個接口,我們可以實(shí)現(xiàn)這個接口。

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;這個方法有三個參數(shù),第一個參數(shù)fraction 代表當(dāng)前動畫完成的百分比,這個值是如何變化的后面還會提到;第二個和第三個參數(shù)代表動畫的初始值和結(jié)束值。這里我們的邏輯很簡單,x的值隨著fraction 不斷變化,并最終達(dá)到結(jié)束值;y的值就是當(dāng)前x值所對應(yīng)的sin(x) 值,然后用x 和 y 產(chǎn)生一個新的點(diǎn)(Point對象)返回。

這樣我們就可以使用這個PointSinEvaluator 生成屬性動畫的實(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();
            }
        });


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

之前我們說過,使用ObjectAnimator 可以對任意對象的任意屬性進(jìn)行動畫操作,這句話是不太嚴(yán)謹(jǐn)?shù)?,這個任意屬性還需要有g(shù)et 和 set 方法。

public class PointAnimView extends View {

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

    .....

}


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

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)對color 屬性的值按照ArgbEvaluator 這個類的規(guī)律在給定的顏色值之間變化,這個ArgbEvaluator 和我們之前定義的PointSinEvaluator一樣,都是決定動畫如何從初始值過渡到結(jié)束值的,只不過這個類是系統(tǒng)自帶的,我們直接拿來用就可以,他可以實(shí)現(xiàn)各種顏色間的自由過渡。

對radius 這個屬性使用了ValueAnimator,使用了其ofFloat方法實(shí)現(xiàn)了一系列float值的變化;同時為其添加了動畫變化的監(jiān)聽器,在屬性值更新的過程中,我們可以將變化的結(jié)果賦給radius,這樣就實(shí)現(xiàn)了半徑動態(tài)的變化。

這里radius 也可以使用和color相同的方式,只需要把ArgbEvaluator 替換為FloatEvaluator,同時修改動畫的變化值即可;使用添加監(jiān)聽器的方式,只是為了介紹監(jiān)聽器的使用方法而已

好了,到這里我們已經(jīng)定義出了所有需要的動畫,前面說過,屬性動畫也是可以組合使用的。因此,在動畫啟動的時候,同時播放這三個動畫,就可以實(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 的屬性動畫
     */
    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ǔ)間動畫中我們就使用到了。他就是用來控制動畫快慢節(jié)奏的;而在屬性動畫中,TimeInterpolator 也是類似的作用;TimeInterpolator 繼承自Interpolator。我們可以繼承TimerInterpolator 以自己的方式控制動畫變化的節(jié)奏,也可以使用Android 系統(tǒng)提供的Interpolator。

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

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

XML 屬性動畫

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

<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)簽也和屬性動畫的類相對應(yīng)。
ValueAnimator --- <animator>
ObjectAnimator --- <objectAnimator>
AnimatorSet --- <set>

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

傳統(tǒng)動畫 VS 屬性動畫

相較于傳統(tǒng)動畫,屬性動畫有很多優(yōu)勢。那是否意味著屬性動畫可以完全替代傳統(tǒng)動畫呢。其實(shí)不然,兩種動畫都有各自的優(yōu)勢,屬性動畫如此強(qiáng)大,也不是沒有缺點(diǎn)。

從上面兩幅圖比較可以發(fā)現(xiàn),補(bǔ)間動畫中,雖然使用translate將圖片移動了,但是點(diǎn)擊原來的位置,依舊可以發(fā)生點(diǎn)擊事件,而屬性動畫卻不是。因此我們可以確定,屬性動畫才是真正的實(shí)現(xiàn)了view的移動,補(bǔ)間動畫對view的移動更像是在不同地方繪制了一個影子,實(shí)際的對象還是處于原來的地方。

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

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

使用幀動畫時需要注意,不要使用過多特別大的圖,容易導(dǎo)致內(nèi)存不足。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 在日常的Android開發(fā)中,經(jīng)常會使用到動畫,這里就對Android開發(fā)中的動畫做一下總結(jié)。 Android 動...
    IAM四十二閱讀 133,053評論 23 349
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎(chǔ)知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,746評論 0 10
  • 做法: 1、將小麥洗凈放在玻璃瓶或瓷碗里泡水過夜。備注:水要有過濾水(純凈水或礦物質(zhì)水)。 2、第二天,將水倒掉,...
    阿里123閱讀 424評論 0 0
  • 如果你不是一名iOS開發(fā)人員,閱讀本文只是想要大概的了解iOS10.3更新了哪些內(nèi)容,那么你看完下面兩幅圖就不要再...
    哆來閱讀 693評論 0 5
  • 我親愛的蒼鷹 你能否穿過皚皚雪山, 為我 銜一枝玫瑰。 夜晚 ,讓我穿過這荒涼原野上, 絢麗的彩霞在我頭上奔涌。 ...
    張新怡閱讀 257評論 0 4