animation動畫小結

聲明下,這是轉載大神任玉剛的文章:http://blog.csdn.net/singwhatiwanna/article/details/17639987 然後自己閱讀的時候稍微加上了一些註釋。文章實現的是下面這樣的一個動畫:

動畫分類

View動畫:也叫漸變動畫,針對View的動畫,主要支持平移、旋轉、縮放、透明度(alpha,scale,translate,rotate)

Drawable動畫:也叫幀動畫,主要是設置View的背景,可以以動畫的形式爲View設置多張背景,這種情況就跟放電影似的。

對象屬性動畫(Android3.0新加入):可以對對象的屬性進行動畫而不僅僅是View,動畫默認時間間隔300ms,默認幀率10ms/幀。其可以達到的效果是:在一個時間間隔內完成對象從一個屬性值到另一個屬性值的改變,因此,屬性動畫幾乎是無所不能的,只要對象有這個屬性,它都能實現動畫效果,但是屬性動畫從Android3.0纔有,這就嚴重製約了屬性動畫的使用,這就是開源動畫庫nineoldandroids的作用,採用nineoldandroids,可以在3.0以前的系統上使用屬性動畫,nineoldandroids的網址是:http://nineoldandroids.com。說到屬性動畫,就不得不提到插值器(TimeInterpolator)和估值算法(TypeEvaluator),下面介紹。

TimeInterpolator和TypeEvaluator

TimeInterpolator中文翻譯爲時間插值器,它的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比,系統預置的有LinearInterpolator(線性插值器:勻速動畫)、AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快)和DecelerateInterpolator(減速插值器:動畫越來越慢)等;TypeEvaluator的中文翻譯爲類型估值算法,它的作用是根據當前屬性改變的百分比來計算改變後的屬性值,系統預置的有IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點型屬性)和ArgbEvaluator(針對Color屬性)。可能這麼說還有點晦澀,沒關係,下面給出一個實例就很好理解了。

 

看上述動畫,很顯然上述動畫是一個勻速動畫,其採用了線性插值器和整型估值算法,在40ms內,View的x屬性實現從0到40的變換,由於動畫的默認刷新率爲10ms/幀,所以該動畫將分5幀進行,我們來考慮第三幀(x=20 t=20ms),當時間t=20ms的時候,時間流逝的百分比是0.5 (20/40=0.5),意味這現在時間過了一半,那x應該改變多少呢,這個就由插值器和估值算法來確定。拿線性插值器來說,當時間流逝一半的時候,x的變換也應該是一半,即x的改變是0.5,爲什麼呢?因爲它是線性插值器,是實現勻速動畫的,下面看它的源碼:

 

public class LinearInterpolator implements Interpolator {

    public LinearInterpolator() {
    }
    
    public LinearInterpolator(Context context, AttributeSet attrs) {
    }
    
    public float getInterpolation(float input) {
        return input;
    }
}

 

 

很顯然,線性插值器的返回值和輸入值一樣,因此插值器返回的值是0.5,這意味着x的改變是0.5,這個時候插值器的工作就完成了。

具體x變成了什麼值,這個需要估值算法來確定,我們來看看整型估值算法的源碼:

 

public class IntEvaluator implements TypeEvaluator<Integer> {

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

 

 

 

 

上述算法很簡單,evaluate的三個參數分別表示:估值小數、開始值和結束值,對應於我們的例子就分別是:0.5,0,40。根據上述算法,整型估值返回給我們的結果是20,這就是(x=20 t=20ms)的由來。

說明:屬性動畫要求該屬性有set方法和get方法(可選);插值器和估值算法除了系統提供的外,我們還可以自定義,實現方式也很簡單,因爲插值器和估值算法都是一個接口,且內部都只有一個方法,我們只要派生一個類實現接口就可以了,然後你就可以做出千奇百怪的動畫效果。具體一點就是:自定義插值器需要實現Interpolator或者TimeInterpolator,自定義估值算法需要實現TypeEvaluator。還有就是如果你對其他類型(非int、float、color)做動畫,你必須要自定義類型估值算法。

nineoldandroids介紹

其功能和android.animation.*中的類的功能完全一致,使用方法完全一樣,只要我們用nineoldandroids來編寫動畫,就可以在所有的Android系統上運行。比較常用的幾個動畫類是:ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator繼承自ValueAnimator,AnimatorSet是動畫集,可以定義一組動畫。使用起來也是及其簡單的,下面舉幾個小栗子。

栗子1:改變一個對象(myObject)的 translationY屬性,讓其沿着Y軸向上平移一段距離:它的高度,該動畫在默認時間內完成,動畫的完成時間是可以定義的,想要更靈活的效果我們還可以定義插值器和估值算法,但是一般來說我們不需要自定義,系統已經預置了一些,能夠滿足常用的動畫。

ObjectAnimator.ofFloat(myObject, "translationY", -myObject.getHeight()).start();


栗子2:改變一個對象的背景色屬性,典型的情形是改變View的背景色,下面的動畫可以讓背景色在3秒內實現從0xFFFF8080到0xFF8080FF的漸變,並且動畫會無限循環而且會有反轉的效果

ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", /*Red*/0xFFFF8080, /*Blue*/0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatMode(ValueAnimator.REVERSE);
colorAnim.start();

栗子3:動畫集合,5秒內對View的旋轉、平移、縮放和透明度都進行了改變

AnimatorSet set = new AnimatorSet();
set.playTogether(
    ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
    ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
    ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
    ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
    ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
    ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
    ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
    ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();

栗子4:下面是個簡單的調用方式,其animate方法是nineoldandroids特有的

 

Button myButton = (Button)findViewById(R.id.myButton);

//Note: in order to use the ViewPropertyAnimator like this add the following import:
//  import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
animate(myButton).setDuration(2000).rotationYBy(720).x(100).y(100);

 

 

 

 

栗子5:一個採用nineoldandroids實現的稍微複雜點的動畫

佈局xml如下:

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

    <Button
        android:id="@+id/menu"
        style="@style/MenuStyle"
        android:background="@drawable/menu" />

    <Button
        android:id="@+id/item1"
        style="@style/MenuItemStyle"
        android:background="@drawable/circle1"
        android:visibility="gone" />

    <Button
        android:id="@+id/item2"
        style="@style/MenuItemStyle"
        android:background="@drawable/circle2"
        android:visibility="gone" />

    <Button
        android:id="@+id/item3"
        style="@style/MenuItemStyle"
        android:background="@drawable/circle3"
        android:visibility="gone" />

    <Button
        android:id="@+id/item4"
        style="@style/MenuItemStyle"
        android:background="@drawable/circle4"
        android:visibility="gone" />

    <Button
        android:id="@+id/item5"
        style="@style/MenuItemStyle"
        android:background="@drawable/circle5"
        android:visibility="gone" />
</FrameLayout>

 

代碼如下:

public class MainActivity extends Activity implements OnClickListener {

    private static final String TAG = "MainActivity";

    private Button mMenuButton;
    private Button mItemButton1;
    private Button mItemButton2;
    private Button mItemButton3;
    private Button mItemButton4;
    private Button mItemButton5;

    private boolean mIsMenuOpen = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void initView() {
        mMenuButton = (Button) findViewById(R.id.menu);
        mMenuButton.setOnClickListener(this);

        mItemButton1 = (Button) findViewById(R.id.item1);
        mItemButton1.setOnClickListener(this);

        mItemButton2 = (Button) findViewById(R.id.item2);
        mItemButton2.setOnClickListener(this);

        mItemButton3 = (Button) findViewById(R.id.item3);
        mItemButton3.setOnClickListener(this);

        mItemButton4 = (Button) findViewById(R.id.item4);
        mItemButton4.setOnClickListener(this);

        mItemButton5 = (Button) findViewById(R.id.item5);
        mItemButton5.setOnClickListener(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        mMenuButton.performClick();
        getMenuInflater().inflate(R.menu.main, menu);
        return false;
    }

    @Override
    public void onClick(View v) {
        if (v == mMenuButton) {
            if (!mIsMenuOpen) {
                mIsMenuOpen = true;
                doAnimateOpen(mItemButton1, 0, 5, 300);
                doAnimateOpen(mItemButton2, 1, 5, 300);
                doAnimateOpen(mItemButton3, 2, 5, 300);
                doAnimateOpen(mItemButton4, 3, 5, 300);
                doAnimateOpen(mItemButton5, 4, 5, 300);
            } else {
                mIsMenuOpen = false;
                doAnimateClose(mItemButton1, 0, 5, 300);
                doAnimateClose(mItemButton2, 1, 5, 300);
                doAnimateClose(mItemButton3, 2, 5, 300);
                doAnimateClose(mItemButton4, 3, 5, 300);
                doAnimateClose(mItemButton5, 4, 5, 300);
            }
        } else {
            Toast.makeText(this, "你點擊了" + v, Toast.LENGTH_SHORT).show();
        }

    }

    /**
     * 打開菜單的動畫
     * @param view 執行動畫的view
     * @param index view在動畫序列中的順序
     * @param total 動畫序列的個數
     * @param radius 動畫半徑
     */
    private void doAnimateOpen(View view, int index, int total, int radius) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        double degree = Math.PI * index / ((total - 1) * 2);
        int translationX = (int) (radius * Math.cos(degree));
        int translationY = (int) (radius * Math.sin(degree));
        Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
                degree, translationX, translationY));
        AnimatorSet set = new AnimatorSet();
        //包含平移、縮放和透明度動畫
        set.playTogether(
                ObjectAnimator.ofFloat(view, "translationX", 0, translationX),
                ObjectAnimator.ofFloat(view, "translationY", 0, translationY),
                ObjectAnimator.ofFloat(view, "scaleX", 0f, 1f),
                ObjectAnimator.ofFloat(view, "scaleY", 0f, 1f),
                ObjectAnimator.ofFloat(view, "alpha", 0f, 1));
        //動畫週期爲500ms
        set.setDuration(1 * 500).start();
    }

    /**
     * 關閉菜單的動畫
     * @param view 執行動畫的view
     * @param index view在動畫序列中的順序
     * @param total 動畫序列的個數
     * @param radius 動畫半徑
     */
    private void doAnimateClose(final View view, int index, int total,
            int radius) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        double degree = Math.PI * index / ((total - 1) * 2);
        int translationX = (int) (radius * Math.cos(degree));
        int translationY = (int) (radius * Math.sin(degree));
        Log.d(TAG, String.format("degree=%f, translationX=%d, translationY=%d",
                degree, translationX, translationY));
        AnimatorSet set = new AnimatorSet();
      //包含平移、縮放和透明度動畫
        set.playTogether(
                ObjectAnimator.ofFloat(view, "translationX", translationX, 0),
                ObjectAnimator.ofFloat(view, "translationY", translationY, 0),
                ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f),
                ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f),
                ObjectAnimator.ofFloat(view, "alpha", 1f, 0f));
        //爲動畫加上事件監聽,當動畫結束的時候,我們把當前view隱藏
        set.addListener(new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
            }

            @Override
            public void onAnimationRepeat(Animator animator) {
            }

            @Override
            public void onAnimationEnd(Animator animator) {
                view.setVisibility(View.GONE);
            }

            @Override
            public void onAnimationCancel(Animator animator) {
            }
        });

        set.setDuration(1 * 500).start();
    }
}

 

在自己重寫上面的demo的時候發現:在導入nineoldandroids 包的時候,假如android自帶的android.animation包也被導入,那麼就有可能產生某些方法比如playTogether會報錯,nineoldandroids的作用就是讓animator可以在低於3.0版本上也可以使用。

上面有講到過自定義插值器實現interpolater,常見的插值器如下:

Interpolator對象 資源ID 功能作用
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速再減速
AccelerateInterpolator @android:anim/accelerate_interpolator 加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先回退一小步然後加速前進
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 在上一個基礎上超出終點一小步再回到終點
BounceInterpolator @android:anim/bounce_interpolator 最後階段彈球效果
CycleInterpolator @android:anim/cycle_interpolator 週期運動
DecelerateInterpolator @android:anim/decelerate_interpolator 減速
LinearInterpolator @android:anim/linear_interpolator 勻速
OvershootInterpolator @android:anim/overshoot_interpolator 快速到達終點並超出一小步最後回到終點

 

然後我們可以這樣使用一個插值器:

 

<set android:interpolator="@android:anim/accelerate_interpolator">
...
</set>

 

 

如果只簡單地引用這些插值器還不能滿足需要的話,我們要考慮一下個性化插值器。我們可以創建一個插值器資源修改插值器的屬性,比如修改AnticipateInterpolator的加速速率,調整CycleInterpolator的循環次數等。爲了完成這種需求,我們需要創建XML資源文件,然後將其放於/res/anim下,然後再動畫元素中引用即可。我們先來看一下幾種常見的插值器可調整的屬性:

<accelerateDecelerateInterpolator> 無

<accelerateInterpolator> android:factor 浮點值,加速速率,默認爲1

<anticipateInterploator> android:tension 浮點值,起始點後退的張力、拉力數,默認爲2

<anticipateOvershootInterpolator> android:tension 同上 android:extraTension 浮點值,拉力的倍數,默認爲1.5(2  * 1.5)

<bounceInterpolator> 無

<cycleInterplolator> android:cycles 整數值,循環的個數,默認爲1

<decelerateInterpolator> android:factor 浮點值,減速的速率,默認爲1

<linearInterpolator> 無

<overshootInterpolator> 浮點值,超出終點後的張力、拉力,默認爲2

下面我們就拿最後一個插值器來舉例:

<?xml version="1.0" encoding="utf-8"?>
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:tension="7.0"/>


上面的代碼中,我們把張力改爲7.0,然後將此文件命名爲my_overshoot_interpolator.xml,放置於/res/anim下,我們就可以引用到自定義的插值器了:

 

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@anim/my_overshoot_interpolator"
    .../>


如果以上這些簡單的定義還不能滿足我們的需求,那麼我們就需要考慮一下自己定義插值器類了。

 

 

 

我們可以實現Interpolator接口,因爲上面所有的Interpolator都實現了Interpolator接口,這個接口定義了一個方法:float getInterpolation(float input);

此方法由系統調用,input代表動畫的時間,在0和1之間,也就是開始和結束之間。

線性(勻速)插值器定義如下:

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


加速減速插值器定義如下:

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

只要實現Interpolator接口,getInterpolation方法裏面怎麼寫就隨你自己咯。



 




 





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章