Android動畫二:使用屬性動畫

前言

??Android系統(tǒng)支持原生動畫,這為應(yīng)用開發(fā)者開發(fā)絢麗的界面提供了極大的方便,有時(shí)候動畫是很必要的,當(dāng)你想做一個滑動的特效的時(shí)候,如果苦思冥想都搞不定,那么你可以考慮下動畫,說不定動畫輕易就搞定了。下面再簡單回顧下Android中的動畫,本文后面會介紹如何用屬性動畫做個稍微復(fù)雜點(diǎn)的動畫。

Android動畫系列:

動畫分類

分類 描述
View動畫 也叫漸變動畫,針對View的動畫,主要支持平移、旋轉(zhuǎn)、縮放、透明度
Drawable動畫 也叫幀動畫,主要是設(shè)置View的背景,可以以動畫的形式為View設(shè)置多張背景
對象屬性動畫 可以對對象的屬性進(jìn)行動畫而不僅僅是View,動畫默認(rèn)時(shí)間間隔300ms,默認(rèn)幀率10ms/幀。其可以達(dá)到的效果是:在一個時(shí)間間隔內(nèi)完成對象從一個屬性值到另一個屬性值的改變,因此,屬性動畫幾乎是無所不能的,只要對象有這個屬性,它都能實(shí)現(xiàn)動畫效果。

屬性動畫

??比較常用的幾個動畫類是:ValueAnimator、ObjectAnimator和AnimatorSet,其中ObjectAnimator繼承自ValueAnimator,AnimatorSet是動畫集,可以定義一組動畫。使用起來也是及其簡單的,下面舉個小栗子。

栗子:實(shí)現(xiàn)的稍微復(fù)雜點(diǎn)的動畫,先上效果圖


布局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"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="0" />

    <Button
        android:id="@+id/item1"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="1"
        android:visibility="gone" />

    <Button
        android:id="@+id/item2"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="2"
        android:visibility="gone" />

    <Button
        android:id="@+id/item3"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="3"
        android:visibility="gone" />

    <Button
        android:id="@+id/item4"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="4"
        android:visibility="gone" />

    <Button
        android:id="@+id/item5"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="5"
        android:visibility="gone" />

    <Button
        android:id="@+id/item6"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="6"
        android:visibility="gone" />

    <Button
        android:id="@+id/item7"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="7"
        android:visibility="gone" />

    <Button
        android:id="@+id/item8"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="8"
        android:visibility="gone" />

    <Button
        android:id="@+id/item9"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="9"
        android:visibility="gone" />

    <Button
        android:id="@+id/item10"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="10"
        android:visibility="gone" />

    <Button
        android:id="@+id/item11"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="11"
        android:visibility="gone" />

    <Button
        android:id="@+id/item12"
        android:layout_gravity="center"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:text="12"
        android:visibility="gone" />
</FrameLayout>

代碼如下:

public class AnimationActivity extends Activity implements View.OnClickListener {

    private static final String TAG = "AnimationActivity";

    private Button mMenuButton;
    private Button mItemButton1;
    private Button mItemButton2;
    private Button mItemButton3;
    private Button mItemButton4;
    private Button mItemButton5;
    private Button mItemButton6;
    private Button mItemButton7;
    private Button mItemButton8;
    private Button mItemButton9;
    private Button mItemButton10;
    private Button mItemButton11;
    private Button mItemButton12;

    private boolean mIsMenuOpen = false;
    private int menuNum = 12;
    private int range = 360; // 顯示角度范圍: 0 - 360
    private float centerX = 0;
    private float centerY = 0;

    private float radius = 400;

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

        initView();
    }


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

        mItemButton6 = (Button) findViewById(R.id.item6);
        mItemButton6.setOnClickListener(this);

        mItemButton7 = (Button) findViewById(R.id.item7);
        mItemButton7.setOnClickListener(this);

        mItemButton8 = (Button) findViewById(R.id.item8);
        mItemButton8.setOnClickListener(this);

        mItemButton9 = (Button) findViewById(R.id.item9);
        mItemButton9.setOnClickListener(this);

        mItemButton10 = (Button) findViewById(R.id.item10);
        mItemButton10.setOnClickListener(this);

        mItemButton11 = (Button) findViewById(R.id.item11);
        mItemButton11.setOnClickListener(this);

        mItemButton12 = (Button) findViewById(R.id.item12);
        mItemButton12.setOnClickListener(this);
    }


    @Override
    public void onClick(View v) {
        if (v == mMenuButton) {
            if (!mIsMenuOpen) {
                mIsMenuOpen = true;
                doAnimateOpen(mItemButton1, 0, menuNum);
                doAnimateOpen(mItemButton2, 1, menuNum);
                doAnimateOpen(mItemButton3, 2, menuNum);
                doAnimateOpen(mItemButton4, 3, menuNum);
                doAnimateOpen(mItemButton5, 4, menuNum);
                doAnimateOpen(mItemButton6, 5, menuNum);
                doAnimateOpen(mItemButton7, 6, menuNum);
                doAnimateOpen(mItemButton8, 7, menuNum);
                doAnimateOpen(mItemButton9, 8, menuNum);
                doAnimateOpen(mItemButton10, 9, menuNum);
                doAnimateOpen(mItemButton11, 10, menuNum);
                doAnimateOpen(mItemButton12, 11, menuNum);
            } else {
                mIsMenuOpen = false;
                doAnimateClose(mItemButton1, 0, menuNum);
                doAnimateClose(mItemButton2, 1, menuNum);
                doAnimateClose(mItemButton3, 2, menuNum);
                doAnimateClose(mItemButton4, 3, menuNum);
                doAnimateClose(mItemButton5, 4, menuNum);
                doAnimateClose(mItemButton6, 5, menuNum);
                doAnimateClose(mItemButton7, 6, menuNum);
                doAnimateClose(mItemButton8, 7, menuNum);
                doAnimateClose(mItemButton9, 8, menuNum);
                doAnimateClose(mItemButton10, 9, menuNum);
                doAnimateClose(mItemButton11, 10, menuNum);
                doAnimateClose(mItemButton12, 11, menuNum);
            }
        } else {
            Toast.makeText(this, "你點(diǎn)擊了" + v.toString(), Toast.LENGTH_SHORT).show();
        }

    }

    /**
     * 打開菜單的動畫
     * @param view 執(zhí)行動畫的view
     * @param index view在動畫序列中的順序
     * @param total 動畫序列的個數(shù)
     */
    private void doAnimateOpen(View view, int index, int total) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        float angle = index * (range/total);
        double degree = Math.PI * (angle/180);
        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();
        //包含平移、縮放、透明度、旋轉(zhuǎn)和倒轉(zhuǎn)顯示動畫
        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),
                ObjectAnimator.ofFloat(view, "rotation", 0, 360)
                //need sdk 21
//                ObjectAnimator.ofArgb((Button)view, "textColor", 0x000, 0xFFFF00),
//                ObjectAnimator.ofArgb(view, "backgroundColor", 0x000, 0xFF0000)

        );

        // add TimeInterpolator
//        set.setInterpolator(new LinearInterpolator());
//        set.setInterpolator(new AccelerateDecelerateInterpolator());
//        set.setInterpolator(new DecelerateInterpolator());

        //動畫周期為500ms
        set.setDuration(1 * 2000).start();
    }

    /**
     * 關(guān)閉菜單的動畫
     * @param view 執(zhí)行動畫的view
     * @param index view在動畫序列中的順序
     * @param total 動畫序列的個數(shù)
     */
    private void doAnimateClose(final View view, int index, int total) {
        if (view.getVisibility() != View.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        }
        float angle = index * (range/total);
        double degree = Math.PI * (angle/180);// index / ((total - 1) * (180/angle));
        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();
        //包含平移、縮放、透明度、旋轉(zhuǎn)和倒轉(zhuǎn)顯示動畫
        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),
                ObjectAnimator.ofFloat(view, "rotation", 360, 0)
                //need sdk 21
//                ObjectAnimator.ofArgb((Button)view, "textColor", 0xFFFF00, 0x000),
//                ObjectAnimator.ofArgb(view, "backgroundColor", 0xFF0000, 0x000)
        );
        //為動畫加上事件監(jiān)聽,當(dāng)動畫結(jié)束的時(shí)候,我們把當(dāng)前view隱藏
        set.addListener(new Animator.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) {
            }
        });

        // add TimeInterpolator
//        set.setInterpolator(new LinearInterpolator());
//        set.setInterpolator(new AccelerateDecelerateInterpolator());
//        set.setInterpolator(new DecelerateInterpolator());

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

??運(yùn)行后,就會出現(xiàn)上圖的效果,你可以用動畫屬性做出更為復(fù)雜的動畫效果。說到屬性動畫,就不得不提到插值器(TimeInterpolator)和估值算法(TypeEvaluator),下面介紹。

TimeInterpolator和TypeEvaluator

??TimeInterpolator中文翻譯為時(shí)間插值器,它的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算出當(dāng)前屬性值改變的百分比,系統(tǒng)預(yù)置的有

類型 描述
LinearInterpolator 線性插值器:勻速動畫
AccelerateDecelerateInterpolator 加速減速插值器:動畫兩頭慢中間快
DecelerateInterpolator 減速插值器:動畫越來越慢

??TypeEvaluator的中文翻譯為類型估值算法,它的作用是根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值,系統(tǒng)預(yù)置的有IntEvaluator(針對整型屬性)、FloatEvaluator(針對浮點(diǎn)型屬性)和ArgbEvaluator(針對Color屬性)。可能這么說還有點(diǎn)晦澀,沒關(guān)系,下面給出一個實(shí)例就很好理解了。

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

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,這個時(shí)候插值器的工作就完成了。

??具體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的三個參數(shù)分別表示:估值小數(shù)、開始值和結(jié)束值,對應(yīng)于我們的例子就分別是:0.5,0,40。根據(jù)上述算法,整型估值返回給我們的結(jié)果是20,這就是(x=20 t=20ms)的由來。

??說明:屬性動畫要求該屬性有set方法和get方法(可選);插值器和估值算法除了系統(tǒng)提供的外,我們還可以自定義,實(shí)現(xiàn)方式也很簡單,因?yàn)椴逯灯骱凸乐邓惴ǘ际且粋€接口,且內(nèi)部都只有一個方法,我們只要派生一個類實(shí)現(xiàn)接口就可以了,然后你就可以做出千奇百怪的動畫效果。具體一點(diǎn)就是:自定義插值器需要實(shí)現(xiàn)Interpolator或者TimeInterpolator,自定義估值算法需要實(shí)現(xiàn)TypeEvaluator。還有就是如果你對其他類型(非int、float、color)做動畫,你必須要自定義類型估值算法。

之前做的動畫加上TimeInterpolator的效果如下:

AccelerateDecelerateInterpolator效果
DecelerateInterpolator效果
LinearInterpolator效果

自定義TimeInterpolator

??在實(shí)際開發(fā)中,系統(tǒng)預(yù)置的時(shí)間差值器肯定是不能滿足我們的需求的,這就需要自定義了。這里自定義一個簡單的時(shí)間插值器,使其具有這樣的曲線特征:
基于中心點(diǎn)正負(fù)振幅逐漸趨于0的時(shí)間插值器,數(shù)學(xué)表達(dá)式:
pow(2, -10 * x) * sin((x - factor / 4) * (2 * PI) / factor) + 1

自定義曲線的特征:


image

上面已經(jīng)說過,自定義非常簡單,只要實(shí)現(xiàn)接口就OK,實(shí)現(xiàn)如下:

    public class MyInterpolator implements Interpolator {

        private static final float DEFAULT_FACTOR = 0.3f;

        // 因子數(shù)值越小振動頻率越高
        private float mFactor;

        public MyInterpolator() {
            this(DEFAULT_FACTOR);
        }

        public MyInterpolator(float factor) {
            mFactor = factor;
        }

        @Override
        public float getInterpolation(float input) {
            // pow(2, -10 * input) * sin((input - factor / 4) * (2 * PI) / factor) + 1
            return (float) (Math.pow(2, -10 * input) * Math.sin((input - mFactor / 4.0d) * (2.0d * Math.PI) / mFactor) + 1);

        }
    }

效果:


OK,屬性動畫就學(xué)習(xí)到這里,下一節(jié)進(jìn)行屬性動畫的深入分析

Demo源碼:https://github.com/yufenfenGH/Android-Animation.git

參考: https://blog.csdn.net/singwhatiwanna/article/details/17639987

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

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