Android學習手記之動畫

Android框架提供了兩種類型的動畫:View Animation(也稱視圖動畫)和Property Animation (也稱屬性動畫)牢屋,Property Animation只能在Android 3.0即以上版本才能使用。View Animation則不受版本限制束世,但是優(yōu)先考慮使用Property Animation 。除此之外可以利用Drawable Animation(也稱幀動畫)實現(xiàn)一幀一幀地顯示資源文件(多為圖片)來展示動畫呜魄。

1昼接、View Animation

1.1、View Animation分類

View Animation通過在視圖上面執(zhí)行補間動畫(tweened animation)生成動畫效果绑咱,補間動畫變換可以變換視圖對象的位置绰筛、大小、旋轉角度描融、透明度铝噩。Android的animation package提供了使用補間動畫所有的類。目前View動畫提供了平移動畫窿克、縮放動畫骏庸、旋轉動畫、透明度動畫年叮。各自對應的類和相關標簽如下表所示:

名 稱 標 簽 子 類 效 果
平移動畫 <translate> TranslateAnimation 移動View
縮放動畫 <scale> ScaleAnimation 縮放View
旋轉動畫 <rotate> RotateAnimation 旋轉View
透明度動畫 alpha AlphaAnimation 改變View的透明度

既可以在代碼中定義動畫具被,也可以在XML中配置,但優(yōu)先采用在XML配置的方式只损,因為復用性一姿、可讀性更高。既可以順序播放動畫跃惫,也可同時播放動畫叮叹。如果采用XML配置的方式,需要將XML文件保存到Android項目的res/anim/目錄下面爆存,XML只能有一個根元素蛉顽,XML只能為單個的<alpha><scale>先较、<translate>蜂林、<rotate>遥诉、插值元素或者組織多個動畫的<set>標簽(內部還可以嵌套其他<set>標簽).下面是XML中配置示例,具體語法說明參見Animation Resources.:

<set android:shareInterpolator="false">
    <scale
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:fromXScale="1.0"
        android:toXScale="1.4"
        android:fromYScale="1.0"
        android:toYScale="0.6"
        android:pivotX="50%"
        android:pivotY="50%"
        android:fillAfter="false"
        android:duration="700" />
    <set android:interpolator="@android:anim/decelerate_interpolator">
        <scale
           android:fromXScale="1.4"
           android:toXScale="0.0"
           android:fromYScale="0.6"
           android:toYScale="0.0"
           android:pivotX="50%"
           android:pivotY="50%"
           android:startOffset="700"
           android:duration="400"
           android:fillBefore="false" />
        <rotate
           android:fromDegrees="0"
           android:toDegrees="-45"
           android:toYScale="0.0"
           android:pivotX="50%"
           android:pivotY="50%"
           android:startOffset="700"
           android:duration="400" />
    </set>
</set>

假設文件在res/anim/目錄下保存為hyperspace_jump.xml,如果要想將其用在一個ImageView對象上面噪叙,下面是代碼示例:

   ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
   Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
   spaceshipImage.startAnimation(hyperspaceJumpAnimation);

如果是用硬編碼的方式創(chuàng)建動畫矮锈,如下所示:

  ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
  AlphaAnimation animation=new AlphaAnimation(0,1);
  animation.setDuration(300);
   spaceshipImage.startAnimation(animation);

除了使用startAnimation()啟動動畫外,你也可以用Animation.setStartTime()設置動畫的啟動時間睁蕾,然后用View.setAnimation()將動畫注冊到View上面苞笨。
注意:.不管動畫如何移動或者調整,View的邊界域不會自動地調整以便將動畫包含在View邊界域內子眶。即便如此瀑凝,當動畫超出了View的邊界域時仍然會繪制而不會發(fā)生剪切,但是如果超出了容納該View的父容器View的邊界域時候就會出現(xiàn)剪的現(xiàn)象

1.2臭杰、自定義View Animation

自定義動畫要繼承Animation粤咪,然后重寫它的[initialize](http://developer.android.com/reference/android/view/animation/Animation.html#initialize(int, int, int, int))和[applyTransformation](http://developer.android.com/reference/android/view/animation/Animation.html#applyTransformation(float, android.view.animation.Transformation))方法。在initialize中做一些初始化工作渴杆,比如Camera寥枝,然后在[applyTransformation](http://developer.android.com/reference/android/view/animation/Animation.html#applyTransformation(float, android.view.animation.Transformation))方法中獲取變換矩陣做相應的變換。參考ApiDemos中Rotate3dAnimation示例磁奖。

2囊拜、Property Animation

屬性動畫的強大之處在于它可以給所有的對象而不僅僅是View對象設置動畫。屬性動畫通過在特定的時間段內改變對象的某個屬性(需要為所添加動畫的對象的該屬性提供setter方法,getter方法則是可選)比搭,可以設置動畫的持續(xù)時間(Duration)冠跷、動畫在持續(xù)時間段內的屬性改變規(guī)則(Time interpolation時間插值)、動畫的重復次數(shù)身诺、動畫結束之后是否倒放蜜托、動畫組合(順序播放一組動畫或者同時播放一組動畫)、幀刷新的頻率(默認每隔10ms刷新一次幀霉赡,設置刷新頻率不一定就會按照所指定的頻率來橄务,取決于當前系統(tǒng)負載的任務數(shù)目、底層計時器有多快等)

2.1同廉、屬性動畫的工作原理

屬性動畫根據動畫流逝時間的百分比來計算出當前的屬性值的改變的百分比,然后根據計算出來的屬性值改變百分比、初值和終值來計算當前的當前時間點的屬性值(調用所添加的對象的某個屬性的set方法)柑司。

如何計算動畫

如上圖所示迫肖,ValueAnimator通過封裝定義動畫插值的TimeInterpolator以及定義如何計算添加了動畫的屬性的值TypeEvaluator

線性動畫示例圖

要想啟動一個動畫攒驰,需要創(chuàng)建ValueAnimator蟆湖,并設置初值、終值玻粪、動畫的持續(xù)時間隅津。調用start()方法啟動诬垂。在整個動畫的執(zhí)行過程中,ValueAnimator會計算時間流逝率( elapsed fraction )伦仍,也就是動畫已經消耗的時間所占總持續(xù)時間的百分比结窘,0表示0%,1表示100%充蓝。以上面的線性動畫示例圖為例隧枫,當 t = 10 ms時,因為總時間為40ms谓苟,流逝率為fraction=(10-40)/40=0.25官脓。當其計算出時間流逝率fraction后,會將該它傳入當前的ValueAnimator所設置的TimeInterpolator(默認是LinearInterpolator)的getInterpolation方法涝焙,該方法只需一個參數(shù)卑笨,對傳入的fraction通過某種算法生成一個插值率(interpolated fraction),不同的TimeInterpolator會有不同的實現(xiàn)仑撞,因此得到的插值率會不一樣赤兴,對于LinearInterpolator,得到的插值率就是傳入的流逝率派草,所以所以屬性的變化會隨著時間的變化是線性關系而搀缠,對于下面的圖,采用的是AccelerateDecelerateInterpolator近迁,也就是先加速后減速的時間插值艺普,在 t = 10 ms時雖然時間流失率為 0.25,但是AccelerateDecelerateInterpolatorgetInterpolation方法實現(xiàn)如下所示:

public float getInterpolation(float fraction) {
        return (float)(Math.cos((fraction+ 1) * Math.PI) / 2.0f) + 0.5f;
    }

fraction = 0.25,返回的值約為0.1464,顯然小于傳入的0.25鉴竭,最后將getInterpolation方法的返回值歧譬、初值、終值帶入相應的TypeEvaluator的[evaluate](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))方法得到此時的屬性值搏存,例如IntEvaluator的[evaluate](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))的實現(xiàn)為:

   public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }

因此當 t = 10 ms時,對于線性動畫瑰步,X=(int)(0+0.25x(40-0))=10,對于非線性動畫X=(int)(0+0.1464x(40-0))=6璧眠,如果是ObjectAnimator,還會在添加動畫的對象上通過反射調用修改屬性的set方法缩焦。

非線性動畫示例圖

2.2、Property Animation與View Animation的不同之處

Property Animation不僅僅可以對view對象添加動畫责静,還能對非view對象添加動畫袁滥,而View Animation如果要對非View對象添加動畫,需要自己實現(xiàn)比較麻煩灾螃,并且view Animation所能操縱的屬性也很少只能對view的縮放题翻、旋轉、平移設置動畫腰鬼,比如不能修改背景色嵌赠。view animation只是修改了view所繪制的地方塑荒,但并沒有修改view本身,比如對一個按鈕添加平移動畫姜挺,按鈕仍然處在原來的地方齿税,Property Animation則消除了這些限制〕跫遥可以對view和非View對象添加動畫偎窘,并且在動畫播放過程中修改自身。提供了插值(參見2.9)溜在、多動畫功能(參見2.5)陌知。

2.3、使用ValueAnimator設置動畫

ValueAnimator提供了ofInt()掖肋、 ofFloat()和 [ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))三中工廠方法來創(chuàng)建對象仆葡,ofInt()ofFloat()方法類似,前者傳入整數(shù)序列志笼,后者傳入浮點數(shù)序列沿盅,而 [ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))是用于自定義類型。
ofFloat()方法示例:

ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();

[ofObject()](https://developer.android.com/reference/android/animation/ValueAnimator.html#ofObject(android.animation.TypeEvaluator, java.lang.Object...))方法示例,其中MyTypeEvaluator需要實現(xiàn) TypeEvaluator纫溃,參見2.8:

ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();

由于上面兩處示例代碼并沒有作用到某個對象上腰涧,因此該動畫并沒有效果,要想動畫作用到某對象上上紊浩,需要添加動畫監(jiān)聽器窖铡,調用getAnimatedValue()方法獲取刷新到某個特定幀時計算所得屬性值,然后做相應的邏輯處理坊谁。

2.4费彼、使用ObjectAnimator設置動畫

ObjectAnimatorValueAnimator的子類,它內部封裝了計時引擎和ValueAnimator的值計算口芍,可以自動更新設置動畫的某個屬性而無需實現(xiàn)ValueAnimator.AnimatorUpdateListeneronAnimationUpdate()方法獲取每一幀的屬性值來手動刷新箍铲,參見2.6節(jié),從而讓代碼更加簡潔鬓椭,如下所示:

ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

注意

  • ObjectAnimator能夠自動更新屬性值颠猴,實現(xiàn)原理最終是通過反射調用在某個屬性上的setter方法。因此當對此某個對象某個屬性使用ObjectAnimator設置動畫的時候一定要確保該屬性有setter(setXxx()格式)方法小染。如果沒有對應的setter方法翘瓮,如果有權限,添加相關屬性的setter方法氧映,如果沒有添加該屬性setter方法的權限春畔,則使用一個包裝類脱货,或者改用ValueAnimator
  • 如果在調用ObjectAnimator的工廠方法(比如ofFloat())方法在參數(shù)values...上只傳入了一個值岛都,則只會將該值作為終值律姨,因此在設置動畫的某個屬性需要有getter方法(getXxx()格式),因為需要通過getter方法獲取初值臼疫。
  • 屬性的setter和getter方法操作的屬性值必須是同一數(shù)據類型作為傳入 ObjectAnimatorvalues...參數(shù)的初值和終值择份,比如當采用的是
ObjectAnimator.ofFloat(targetObject, "propName", 1f)

則屬性的setter和getter參數(shù)必須是float類型.

  • 有些屬性需要手動調用invalidate()刷新值。比如Drawable對象的顏色值烫堤。參見2.6

2.5荣赶、使用AnimatorSet同時設置多種動畫

使用AnimatorSet類可以實現(xiàn)同時播放、順序播放鸽斟、延時播放多個動畫拔创。下面的代碼是Android SDK中模擬小球落體壓扁后有反彈起來一段距離,然后消失的動畫,先播放bounceAnim富蓄,然后同時播放squashAnim1剩燥、squashAnim2stretchAnim1立倍、stretchAnim2灭红。接著播放bounceBackAnim,最后播放fadeAnim口注。所播放的動畫均為ValueAnimator類型

AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

2.6变擒、設置動畫監(jiān)聽器

Android提供了監(jiān)聽動畫播放事件的接口Animator.AnimatorListenerValueAnimator.AnimatorUpdateListener。下面是各自的源代碼:
Animator.AnimatorListener源碼:

/**
 * This is the superclass for classes which provide basic support for animations which can be
 * started, ended, and have <code>AnimatorListeners</code> added to them.
 */
public abstract class Animator implements Cloneable {
    ......
    /**
     * <p>An animation listener receives notifications from an animation.
     * Notifications indicate animation related events, such as the end or the
     * repetition of the animation.</p>
     */
    public static interface AnimatorListener {
        /**
         * <p>Notifies the start of the animation.</p>
         *
         * @param animation The started animation.
         */
        void onAnimationStart(Animator animation);

        /**
         * <p>Notifies the end of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which reached its end.
         */
        void onAnimationEnd(Animator animation);

        /**
         * <p>Notifies the cancellation of the animation. This callback is not invoked
         * for animations with repeat count set to INFINITE.</p>
         *
         * @param animation The animation which was canceled.
         */
        void onAnimationCancel(Animator animation);

        /**
         * <p>Notifies the repetition of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationRepeat(Animator animation);
    }
   ......
}

ValueAnimator.AnimatorUpdateListener源碼

public class ValueAnimator extends Animator {
   ......
    /**
     * Implementors of this interface can add themselves as update listeners
     * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
     * frame, after the current frame's values have been calculated for that
     * <code>ValueAnimator</code>.
     */
    public static interface AnimatorUpdateListener {
        /**
         * <p>Notifies the occurrence of another frame of the animation.</p>
         *
         * @param animation The animation which was repeated.
         */
        void onAnimationUpdate(ValueAnimator animation);

    }
   ......
}

需要說明的是,對于某些設置動畫的屬性寝志,需要手動調用invalidate()方法才能確保將屏幕區(qū)域用動畫得到的新的屬性值重新繪制自己娇斑。比如一個Drawable對象的顏色屬性,然而對于setAlpha()setTranslationX()則不需要手動調用invalidate()澈段。
監(jiān)聽示例代碼:

ValueAnimatorAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
}

2.7悠菜、給ViewGroup設置布局變換時動畫

拼命寫代碼
拼命寫代碼

Android屬性動畫提供了給ViewGroups布局改變時添加動畫的功能。所謂布局改變動畫是指在ViewGroup里的View败富,當你添加或者刪除它悔醋、或者調用它的setVisibility()方法將對應的值設為VISIBLEGONE兽叮、INVISIBLE時候所呈現(xiàn)出的出場動畫芬骄、退場動畫效果,當對某個ViewGroup內部的view做添加或者刪除的時候鹦聪,還可以給在ViewGroup中的其他view從原來的位置到新的位置添加動畫账阻。
用法:創(chuàng)建一個LayoutTransition對象,然后調用該對象的[setAnimator](https://developer.android.com/reference/android/animation/LayoutTransition.html#setAnimator(int, android.animation.Animator))(int transitionType, Animator animator)泽本,然后在需要添加ViewGroup布局改變動畫的某個ViewGroup對象上調用setLayoutTransition(LayoutTransition transition)方法淘太。
其中[setAnimator](https://developer.android.com/reference/android/animation/LayoutTransition.html#setAnimator(int, android.animation.Animator))(int transitionType, Animator animator)方法的第一個參數(shù)只能為LayoutTransition類中下面四個靜態(tài)常量。

  • APPEARING 某個View在ViewGroup中出現(xiàn)時的設置動畫
  • CHANGE_APPEARING 當新的View出現(xiàn)在ViewGroup中出現(xiàn)時給已經存在于ViewGroup中的其他View設置動畫
  • DISAPPEARING 某個View在ViewGroup中消失時的設置動畫
  • CHANGE_DISAPPEARING 當某個View從ViewGroup中消失時給ViewGroup中的其他View設置動畫。

代碼示例如下:


/**
 * This application demonstrates how to use LayoutTransition to automate transition animations
 * as items are removed from or added to a container.
 */
public class LayoutAnimations extends Activity {

    private int numButtons = 1;
    ViewGroup container = null;
    Animator defaultAppearingAnim, defaultDisappearingAnim;
    Animator defaultChangingAppearingAnim, defaultChangingDisappearingAnim;
    Animator customAppearingAnim, customDisappearingAnim;
    Animator customChangingAppearingAnim, customChangingDisappearingAnim;
    Animator currentAppearingAnim, currentDisappearingAnim;
    Animator currentChangingAppearingAnim, currentChangingDisappearingAnim;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_animations);

        container = new FixedGridLayout(this);
        container.setClipChildren(false);
        ((FixedGridLayout)container).setCellHeight(90);
        ((FixedGridLayout)container).setCellWidth(100);
        final LayoutTransition transitioner = new LayoutTransition();
        container.setLayoutTransition(transitioner);
        defaultAppearingAnim = transitioner.getAnimator(LayoutTransition.APPEARING);
        defaultDisappearingAnim =
                transitioner.getAnimator(LayoutTransition.DISAPPEARING);
        defaultChangingAppearingAnim =
                transitioner.getAnimator(LayoutTransition.CHANGE_APPEARING);
        defaultChangingDisappearingAnim =
                transitioner.getAnimator(LayoutTransition.CHANGE_DISAPPEARING);
        createCustomAnimations(transitioner);
        currentAppearingAnim = defaultAppearingAnim;
        currentDisappearingAnim = defaultDisappearingAnim;
        currentChangingAppearingAnim = defaultChangingAppearingAnim;
        currentChangingDisappearingAnim = defaultChangingDisappearingAnim;

        ViewGroup parent = (ViewGroup) findViewById(R.id.parent);
        parent.addView(container);
        parent.setClipChildren(false);
        Button addButton = (Button) findViewById(R.id.addNewButton);
        addButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Button newButton = new Button(LayoutAnimations.this);
                newButton.setText(String.valueOf(numButtons++));
                newButton.setOnClickListener(new View.OnClickListener() {
                    public void onClick(View v) {
                        container.removeView(v);
                    }
                });
                container.addView(newButton, Math.min(1, container.getChildCount()));
            }
        });

        CheckBox customAnimCB = (CheckBox) findViewById(R.id.customAnimCB);
        customAnimCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setupTransition(transitioner);
            }
        });

        // Check for disabled animations
        CheckBox appearingCB = (CheckBox) findViewById(R.id.appearingCB);
        appearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setupTransition(transitioner);
            }
        });
        CheckBox disappearingCB = (CheckBox) findViewById(R.id.disappearingCB);
        disappearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setupTransition(transitioner);
            }
        });
        CheckBox changingAppearingCB = (CheckBox) findViewById(R.id.changingAppearingCB);
        changingAppearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setupTransition(transitioner);
            }
        });
        CheckBox changingDisappearingCB = (CheckBox) findViewById(R.id.changingDisappearingCB);
        changingDisappearingCB.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setupTransition(transitioner);
            }
        });
    }

    private void setupTransition(LayoutTransition transition) {
        CheckBox customAnimCB = (CheckBox) findViewById(R.id.customAnimCB);
        CheckBox appearingCB = (CheckBox) findViewById(R.id.appearingCB);
        CheckBox disappearingCB = (CheckBox) findViewById(R.id.disappearingCB);
        CheckBox changingAppearingCB = (CheckBox) findViewById(R.id.changingAppearingCB);
        CheckBox changingDisappearingCB = (CheckBox) findViewById(R.id.changingDisappearingCB);
        transition.setAnimator(LayoutTransition.APPEARING, appearingCB.isChecked() ?
                (customAnimCB.isChecked() ? customAppearingAnim : defaultAppearingAnim) : null);
        transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingCB.isChecked() ?
                (customAnimCB.isChecked() ? customDisappearingAnim : defaultDisappearingAnim) : null);
        transition.setAnimator(LayoutTransition.CHANGE_APPEARING, changingAppearingCB.isChecked() ?
                (customAnimCB.isChecked() ? customChangingAppearingAnim :
                        defaultChangingAppearingAnim) : null);
        transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,
                changingDisappearingCB.isChecked() ?
                (customAnimCB.isChecked() ? customChangingDisappearingAnim :
                        defaultChangingDisappearingAnim) : null);
    }

    private void createCustomAnimations(LayoutTransition transition) {
        // Changing while Adding
        PropertyValuesHolder pvhLeft =
                PropertyValuesHolder.ofInt("left", 0, 1);
        PropertyValuesHolder pvhTop =
                PropertyValuesHolder.ofInt("top", 0, 1);
        PropertyValuesHolder pvhRight =
                PropertyValuesHolder.ofInt("right", 0, 1);
        PropertyValuesHolder pvhBottom =
                PropertyValuesHolder.ofInt("bottom", 0, 1);
        PropertyValuesHolder pvhScaleX =
                PropertyValuesHolder.ofFloat("scaleX", 1f, 0f, 1f);
        PropertyValuesHolder pvhScaleY =
                PropertyValuesHolder.ofFloat("scaleY", 1f, 0f, 1f);
        customChangingAppearingAnim = ObjectAnimator.ofPropertyValuesHolder(
                        this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScaleX, pvhScaleY).
                setDuration(transition.getDuration(LayoutTransition.CHANGE_APPEARING));
        customChangingAppearingAnim.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setScaleX(1f);
                view.setScaleY(1f);
            }
        });

        // Changing while Removing
        Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
        Keyframe kf1 = Keyframe.ofFloat(.9999f, 360f);
        Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
        PropertyValuesHolder pvhRotation =
                PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
        customChangingDisappearingAnim = ObjectAnimator.ofPropertyValuesHolder(
                        this, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhRotation).
                setDuration(transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
        customChangingDisappearingAnim.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotation(0f);
            }
        });

        // Adding
        customAppearingAnim = ObjectAnimator.ofFloat(null, "rotationY", 90f, 0f).
                setDuration(transition.getDuration(LayoutTransition.APPEARING));
        customAppearingAnim.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotationY(0f);
            }
        });

        // Removing
        customDisappearingAnim = ObjectAnimator.ofFloat(null, "rotationX", 0f, 90f).
                setDuration(transition.getDuration(LayoutTransition.DISAPPEARING));
        customDisappearingAnim.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator anim) {
                View view = (View) ((ObjectAnimator) anim).getTarget();
                view.setRotationX(0f);
            }
        });

    }
}

2.8蒲牧、使用類型求值器TypeEvaluator

通過實現(xiàn)TypeEvaluator接口可以創(chuàng)建Android系統(tǒng)沒有實現(xiàn)的類型撇贺。Android系統(tǒng)已經實現(xiàn)的TypeEvaluator的有IntEvaluator, FloatEvaluatorArgbEvaluator。只需要 [evaluate()](http://developer.android.com/reference/android/animation/TypeEvaluator.html#evaluate(float, T, T))方法就可以計算出設置動畫的屬性當前時間點的相應的值冰抢。 FloatEvaluator類的實現(xiàn)如下所示:

public class FloatEvaluator implements TypeEvaluator {
     /**
      * @param fraction   實現(xiàn)TimeInterpolator接口的某個特定插值器的getInterpolation(float fraction)的返回值
      * @param startValue 添加動畫效果的屬性的起始值
      * @param endValue   添加動畫效果的屬性的結束值
      * @return 添加動畫效果的屬性在當前時間點的值
      */
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
    }
}

2.9松嘶、使用插值器Interpolators

在2.1節(jié)有關屬性動畫的原理中已經說到TimeInterpolator的作用是根據時間流逝率來計算當前屬性的插值率。Android系統(tǒng)內置許多插值器如LinearInterpolator(勻速)挎扰、AccelerateInterpolator(一直加速)AccelerateDecelerateInterpolator(先加速后減速)翠订、BounceInterpolator(回彈效果)等多種類型,如果要實現(xiàn)自定義類型遵倦,需要實現(xiàn)TimeInterpolator接口的getInterpolation方法尽超,下面Android回彈效果插值器BounceInterpolator的源碼:

/**
 * An interpolator where the change bounces at the end.
 */
@HasNativeInterpolator
public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    public BounceInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public BounceInterpolator(Context context, AttributeSet attrs) {
    }

    private static float bounce(float t) {
        return t * t * 8.0f;
    }

    public float getInterpolation(float t) {
        // _b(t) = t * t * 8
        // bs(t) = _b(t) for t < 0.3535
        // bs(t) = _b(t - 0.54719) + 0.7 for t < 0.7408
        // bs(t) = _b(t - 0.8526) + 0.9 for t < 0.9644
        // bs(t) = _b(t - 1.0435) + 0.95 for t <= 1.0
        // b(t) = bs(t * 1.1226)
        t *= 1.1226f;
        if (t < 0.3535f) return bounce(t);
        else if (t < 0.7408f) return bounce(t - 0.54719f) + 0.7f;
        else if (t < 0.9644f) return bounce(t - 0.8526f) + 0.9f;
        else return bounce(t - 1.0435f) + 0.95f;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createBounceInterpolator();
    }
}

看起來BounceInterpolator 并沒有實現(xiàn)TimeInterpolator接口的getInterpolation方法,實際上BaseInterpolator是一個抽象類梧躺,它實現(xiàn)了Interpolator 接口但沒有給出實現(xiàn)橙弱,而Interpolator 接口又是繼承自TimeInterpolator的。

如果嫌自定義布局改變動畫比較麻煩燥狰,可以使用默認的效果棘脐,方法是在某個XML布局中設置android:animateLayoutchanges屬性為true。例如

<LinearLayout
    android:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="@+id/verticalContainer"
    android:animateLayoutChanges="true" />

現(xiàn)在對上面idverticalContainerLinearLayout內部的子view做添加龙致、刪除蛀缝、顯示和隱藏操作等就有布局發(fā)生改變的動畫效果。

2.10目代、使用Keyframe設置動畫

Keyframe的作用通過傳入一對值(時間流失率與屬性值)申明動畫在特定時間點的特定狀態(tài)屈梁。每一個keyframe 可以有自己的插值器來控制從前一幀到當前幀的動畫表現(xiàn)形式。
可以使用KeyframeofInt()榛了、ofFloat()或者ofObject()工廠方法來實例化特定的Keyframe對象在讶。然后通過PropertyValuesHolder 的工廠方法 [ofKeyframe()](http://developer.android.com/reference/android/animation/PropertyValuesHolder.html#ofKeyframe(android.util.Property, android.animation.Keyframe...))獲取一個PropertyValuesHolder對象,然和將獲取到的PropertyValuesHolder對象和需要添加動畫的對象傳入ObjectAnimator的工廠方法[ofPropertyValuesHolder](http://developer.android.com/reference/android/animation/ObjectAnimator.html#ofPropertyValuesHolder(java.lang.Object, android.animation.PropertyValuesHolder...))從而獲取一個ObjectAnimator對象.

   Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
   Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
   Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
   PropertyValuesHolder pvhRotation =  PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
   ObjectAnimator rotationAnim =  ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
   rotationAnim.setDuration(5000ms);

具體示例參考APIDemos里的 MultiPropertyAnimation 霜大。

2.11构哺、使用ViewPropertyAnimator設置動畫

使用ViewPropertyAnimator的好處是只需要一個底層的Animator對象就可以同時給各多個屬性添加動畫,效果跟于ObjectAnimator一樣战坤,都是通過實際通過修改view的屬性曙强,并且采用流式風格的代碼,通過鏈式調用途茫,代碼更加精簡易讀碟嘴。下面是同時給一個View的xy屬性添加動畫的三種不同的寫法比較:

  • 多個ObjectAnimator對象方式
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
  • 一個ObjectAnimator對象方式
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
  • ViewPropertyAnimator方式
myView.animate().x(50f).y(100f);

通過比較可以明顯看出ViewPropertyAnimator方式更加精簡易讀。更多有關ViewPropertyAnimator的內容需要參考Android開發(fā)官方博客Introducing ViewPropertyAnimator

2.12囊卜、XML聲明方式設置動畫

為了能夠區(qū)分出View AnimationProperty Animation,配置View Animation的XML文件存放在Android項目的res/anim/目錄下面娜扇,而配置Property Animation的XML文件存放在res/animator/目錄下面错沃。

類 名 稱 標 簽
ValueAnimator <animator>
ObjectAnimator <objectAnimator>
AnimatorSet <set>

XML配置動畫集:

<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>

配置 PropertyValuesHolder

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:duration="1000"
                android:repeatCount="1"
                android:repeatMode="reverse">
    <propertyValuesHolder android:propertyName="x" android:valueTo="400"/>
    <propertyValuesHolder android:propertyName="y" android:valueTo="200"/>
</objectAnimator>

配置Keyframe

<propertyValuesHolder android:propertyName="x" >
    <keyframe android:fraction="0" android:value="800" />
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
    <keyframe android:fraction="1"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="400" />
</propertyValuesHolder>
<propertyValuesHolder android:propertyName="y" >
    <keyframe/>
    <keyframe android:fraction=".2"
              android:interpolator="@android:anim/accelerate_interpolator"
              android:value="300"/>
    <keyframe android:interpolator="@android:anim/accelerate_interpolator"
              android:value="1000" />
</propertyValuesHolder>

加載動畫資源:

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

3、Drawable Animation

Drawable Animation(幀動畫)通過加載一個接一個Drawable資源文件來生成動畫雀瓢,類似于老式電影通過連續(xù)旋轉已經拍好的膠片放映電影捎废。雖然可以使用AnimationDrawable來在代碼中創(chuàng)建動畫,但最常用的方式是在你的Android項目的res/drawable/目錄下的XML文件定義動畫致燥,這個XML文件包含了構成該動畫的所有幀以及每一幀的持續(xù)時間筒捺。如下所示:

<animation-list 
     xmlns:android="http://schemas.android.com/apk/res/android" 
     android:oneshot="true">  
   <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />  
   <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />  
   <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

其中<animation-list>為該XML的根節(jié)點族淮,android:oneshot屬性設為true表示該動畫只播放一次,并且當動畫結束之后停留在最后一幀祭示,如果為false則表示動畫在播放完以后會循環(huán)播放断傲。先假設該上面的XML文件在res/drawable/目錄下的保存的文件名為rocket_thrust.xml脱吱,在一個Activity的ImageView中添加動畫的實例代碼如下:

AnimationDrawable rocketAnimation;

public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image); 
    rocketImage.setBackgroundResource(R.drawable.rocket_thrust); 
    rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) { 
         rocketAnimation.start(); 
          return true;
     } 
    return super.onTouchEvent(event);
}

注意:不能在Activity的onCreate()方法中調用AnimationDrawable 的start()方法,因為AnimationDrawable 并沒有完全加載到窗口里面來认罩,如果想在沒有交互的情況下(比如點擊某按鈕觸發(fā)動畫播放事件)就播放動畫箱蝠,在Activity的onWindowFocusChanged()方法中調用該方法。該方法會在窗口獲取到焦點時被調用垦垂。

4宦搬、注意事項

  • OOM問題
    主要出現(xiàn)在Drawable Animation中,應盡量避免加載數(shù)量較多且較大的圖片劫拗,盡量避免使用Drawable Animation间校。
  • 內存泄漏
    屬性動畫中的無限循環(huán)動畫需要在Activity退出的時候及時停止,否則將導致Activity無法釋放而造成內存泄露页慷。View Animation不存在這個問題憔足。
  • 兼容問題
    某些動畫在3.0以下系統(tǒng)上有兼容性問題,主要是Property Animation在Android 3.0以下不能使用酒繁。
  • View Animation的問題
    滓彰。View Animation是對View的影像做動畫,并不是真正的改變View的狀態(tài)州袒,因此有時候動畫完成之后view無法隱藏揭绑,即setVisibility(View.GONE)失效了,此時需要調用view.clearAnimation()清除view動畫才行郎哭。
  • 不要使用px
    盡量使用dp,px在不同的分辨率的手機下面會有不同的效果洗做。
  • 動畫交互問題
    在android3.0以前的系統(tǒng)上,view動畫和屬性動畫彰居,新位置均無法觸發(fā)點擊事件诚纸,同時,老位置仍然可以觸發(fā)單擊事件陈惰。從3.0開始畦徘,屬性動畫的單擊事件觸發(fā)位置為移動后的位置毕籽,view動畫仍然在原位置。
  • 動畫交互問題
    使用動畫的過程中井辆,建議開啟硬件加速关筒,這樣會提高動畫的流暢性。

5杯缺、參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蒸播,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子萍肆,更是在濱河造成了極大的恐慌袍榆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件塘揣,死亡現(xiàn)場離奇詭異包雀,居然都是意外死亡,警方通過查閱死者的電腦和手機亲铡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門才写,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人奖蔓,你說我怎么就攤上這事赞草。” “怎么了吆鹤?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵房资,是天一觀的道長。 經常有香客問我檀头,道長轰异,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任暑始,我火速辦了婚禮搭独,結果婚禮上,老公的妹妹穿的比我還像新娘廊镜。我一直安慰自己牙肝,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布嗤朴。 她就那樣靜靜地躺著配椭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪雹姊。 梳的紋絲不亂的頭發(fā)上股缸,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天,我揣著相機與錄音吱雏,去河邊找鬼敦姻。 笑死瘾境,一個胖子當著我的面吹牛,可吹牛的內容都是我干的镰惦。 我是一名探鬼主播迷守,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼旺入!你這毒婦竟也來了兑凿?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤茵瘾,失蹤者是張志新(化名)和其女友劉穎礼华,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體龄捡,經...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年慷暂,在試婚紗的時候發(fā)現(xiàn)自己被綠了聘殖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡行瑞,死狀恐怖奸腺,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情血久,我是刑警寧澤突照,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站氧吐,受9級特大地震影響讹蘑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜筑舅,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一座慰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧翠拣,春花似錦版仔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至谜慌,卻和暖如春然想,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背欣范。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工又沾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弊仪,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓杖刷,卻偏偏與公主長得像励饵,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子滑燃,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內容