Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android屬性動畫爬坑之路

android屬性動畫爬坑之路

編輯:關於Android編程

1.什麼是Android動畫

Android的動畫一般分為3種,分別是幀動畫(Frame Animation),補間動畫( Tween Animation)和屬性動畫(Property Animation)。所謂幀動畫就是每一個畫面都是一幀,然後很多張圖片連續播放形成動畫,好處是變化比較自由,缺點是因為每一幀都是一張圖片,占用體積很大。補間動畫就是有制定開始和結束的狀態,然後由系統去計算和繪制中間狀態。優點是設置簡單,基本兼容安卓所有版本,缺點是補間動畫操作的屬性非常有限,只有平移、旋轉、縮放、透明度。而且,補間動畫只是改變了View的繪制的位置,並沒有真正的改變View,所以實際上View還在原來的位置上。例如給一個View設置了一個點擊事件,然後View做一個平移動畫。View的位置雖然改變了,但是它的點擊事件還在原來的位置上。正是由於這些問題Google爸爸在Android 3.0(API11)以後,加入了屬性動畫。

2.屬性動畫的優點

屬性動畫顧名思義就是改變了View的屬性,而不僅僅是繪制的位置。屬性動畫可以操作的屬性相比於補間動畫大大增加,除了常用的平移、旋轉、縮放、透明度還有顏色等,基本上能通過View.setXX來設置的屬性,屬性動畫都可以操作,這大大增加了我們在使用動畫時的靈活性。一般來說我們設置屬性動畫,常用的就是ValueAnimator和ObjectAnimator,這裡我們主要講這兩個。

3.ValueAnimation

例如我們要寫一個水平移動的動畫

    public void horizontalMove(View view) {  
        ValueAnimator animator = ValueAnimator.ofFloat(0, 1000);  
        animator.setDuration(1000).start();  
    }  

如果你寫過補間的動畫,你肯定一眼就可以發現問題。這竟然沒有設定動畫要改變的屬性,這也能跑?當然不能,所謂值動畫就是只是改變值,並不改變任何屬性的動畫。如果需要改變具體的屬性,例如我們要它水平移動,我們還需要加上以下代碼:

public void horizontalMove(View view) {  
    ValueAnimator animator = ValueAnimator.ofFloat(0, 1000);  
    anim.addUpdateListener(new AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {                
        mView.setTranslationX(animation.getAnimatedValue());
        }  
    }); 
    animator.setDuration(1000).start();  
} 

簡單的來說我們這裡做的就是把改變的值拿過了,賦值給我們需要的對象和屬性。這樣就比Tween Animation要高明的多,一個值的監聽就可以賦值給任意的屬性,使用起來非常的靈活。但是,我們也發現了一個問題,我們只是做了一個view的水平移動,結果就寫了這麼多代碼,如果我們的動畫比較復雜,是不是就要寫一堆的監聽,然後改變一堆的值?當然不是,Google爸爸還為我們准備另一個好東西—ObjectAnimator。

4.ObjectAnimation

同樣的,我們來實現一個平移動畫。

    ObjectAnimator animator=ObjectAnimator.ofFloat (mView,"translationX",0,1000);
    animator.setDuration (1000);
    animator.start ();

我們可以看到,我們只用了簡單的幾行就實現了,感覺比ValueAnimator簡單多了,很多初學者也會比較多的使用ObjectAnimator而不是ValueAnimator。但是實際上ValueAnimator要更加強大和靈活,因為它直接操作的對象就是要改變的值,我們可以根據一組值,做出我們要實現的任何東西。他們兩個的關系就像是廚房和微波加熱的食物,微波爐食物雖然方便快捷但是如果家裡來了客人,我們要做一桌菜,微波爐實現起來就很困難,但是廚房就可以輕易的做到,當然前提是你會的話。

5.動畫監聽

其實我們之前的對ValueAnimator的值進行監聽也是一種動畫監聽,但是那個我們都會了。這裡我們要說的是動畫監聽(AnimatorListener)。如下:

animator.addListener (new Animator.AnimatorListener () {
        @Override
        public void onAnimationStart (Animator animator) {
            Log.d ("ObjectAnimator", "onAnimationStart: ");
        }

        @Override
        public void onAnimationEnd (Animator animator) {
            Log.d ("ObjectAnimator", "onAnimationEnd: ");
        }

        @Override
        public void onAnimationCancel (Animator animator) {
            Log.d ("ObjectAnimator", "onAnimationCancel: ");
        }

        @Override
        public void onAnimationRepeat (Animator animator) {
            Log.d ("ObjectAnimator", "onAnimationRepeat: ");
        }
    });

這裡有4個方法,分別對應動畫的開始、結束、取消、重復。如果我們需要根據動畫的進度來完成一些操作的話就可以在這裡進行,但是我們也看到這裡有4個方法需要重寫,但是我們往往並不需要這麼多的方法,這裡就有一個動畫監聽的適配器AnimatorListenerAdapter,通過它我們可以只重寫我們需要的方法,而不需要寫這麼多無效的代碼。

常用屬性和設置

屬性動畫的設置其實和補間動畫差不多,常用的屬性有duration,repeatMode,repeatCount,Interpolator,startDelay等等。這裡我要提的一點就是這個startDelay。通過這個方法我們可以設置動畫開始的延遲時間,可以達做到很多炫酷的動畫。比如說之前我們要做一個文字的位移動畫,但是我們有3行文本,我希望這3行文本的位移有一定的延遲,1先開始,然後2開始,最後3開始。如果要用補間動畫的話,我們能設置的播放順序只有同步和依次。但是通過startDelay我們就可以方便的設置一些不同步的動畫效果。而且設置完延遲後我們依然可以使用同時啟動,並不會忽略delay的時間。

AnimatorSet

屬性動畫的animatorSet和補間動畫的animationSet類似,都是可以同時控制多個動畫。AnimatorSet除了提供一般的playTogether()和playAfter()和playBefore()這些方法見名知義,使用並不困難,就不在詳細敘述。

動畫差值器

前面我們說過,值動畫就是根據一系列值的變化來改變屬性,從而做出各種各樣的動畫效果。但是在實際開發中我們的動畫效果可能並不是線性的,這就需要我們給我們的動畫設置一些差值器,來改變值的變化速率。android本身給我們提供了一些基本的差值器:
1. AccelerateInterpolator 加速插值器
2. DecelerateInterpolator 減速插值器
3. AccelerateDecelerateInterpolator 加速減速插值器
4. LinearInterpolator 線性插值器
5. BounceInterpolator 彈跳插值器
6. AnticipateInterpolator 回蕩秋千插值器
7. AnticipateOvershootInterpolator
8. CycleInterpolator 正弦周期變化插值器
9. OvershootInterpolator
這幾個差值器中 我們常用的主要是線性,加速,減速,正選和彈跳。
這幾個差值器的使用都比較簡單,具體信息和使用方法可以移步https://my.oschina.net/banxi/blog/135633
這裡我想補充的是另一種相對少見的差值器類型:貝塞爾差值器
public class EaseCubicInterpolator implements Interpolator {

private final static int ACCURACY = 4096;
private int mLastI = 0;
private final PointF mControlPoint1 = new PointF();
private final PointF mControlPoint2 = new PointF();

/**
 * 設置中間兩個控制點.

 * 在線工具: http://cubic-bezier.com/

 * 
 * @param x1
 * @param y1
 * @param x2
 * @param y2
 */
public EaseCubicInterpolator(float x1, float y1, float x2, float y2) {
    mControlPoint1.x = x1;
    mControlPoint1.y = y1;
    mControlPoint2.x = x2;
    mControlPoint2.y = y2;
}

@Override
public float getInterpolation(float input) {
    float t = input;
    // 近似求解t的值[0,1]
    for (int i = mLastI; i < ACCURACY; i++) {
        t = 1.0f * i / ACCURACY;
        double x = MathUtil.cubicCurves(t, 0, mControlPoint1.x,
                mControlPoint2.x, 1);
        if (x >= input) {
            mLastI = i;
            break;
        }
    }
    double value = MathUtil.cubicCurves(t, 0, mControlPoint1.y,
            mControlPoint2.y, 1);
    if (value > 0.999d) {
        value = 1;
        mLastI = 0;
    }
    return (float) value;
}

}
這就是一個簡單的貝塞爾差值器的代碼,原理就是根據貝塞爾曲線來控制值的變化,我們平常的工作組一般二階的貝塞爾曲線就足夠滿足我們的需求了,這裡給大家一個可以圖形化生成貝塞爾控制點的網址http://cubic-bezier.com/ ,在這裡可以方便的生成貝塞爾控制點。https://www.desmos.com/calculator這個網站可以幫我們生成函數圖形,也能方便我們進行自定義差值器的工作。

動畫的內存洩露問題

如果我們的項目中動畫的應用比較多,那麼我們為了控制動畫,通常會對動畫進行監聽,如果頁面的邏輯比較復雜,我們甚至會和handler進行結合。

private void createCircleClockwiseAccelerateAnimator() {
        mCircleClockwiseAccelerateAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mCircleClockwiseAccelerateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float value = (float) animation.getAnimatedValue();
                mRotation = value * 720;
                mIvConnectedAnimCircle1.setRotation(mRotation);
                mIvConnectedAnimCircle2.setRotation(mRotation *0.2f);
            }
        });
        mCircleClockwiseAccelerateAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                if (mHandler != null) {
                    mRotation = 0;
                    mHandler.sendEmptyMessage(MESSAGE_CIRCLE_ROTATE_ACCELERATE_START);
                    mIvConnectedAnimCircle1.setVisibility(View.VISIBLE);
                    mIvConnectedAnimCircle2.setVisibility(View.VISIBLE);
                    mIvMiddleImage.setVisibility(View.VISIBLE);
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (mHandler != null) {
                    mHandler.sendEmptyMessage(MESSAGE_CIRCLE_ROTATE_ACCELERATE_END);
                    mIvConnectedAnimCircle1.setVisibility(View.INVISIBLE);
                    mIvConnectedAnimCircle2.setVisibility(View.INVISIBLE);
                    mIvConnectedAnimCircle3.setRotation(mRotation);
                    mIvConnectedAnimCircle3.setVisibility(View.VISIBLE);
                    mIvMiddleImage.setVisibility(View.INVISIBLE);
                    Logger.d("onAnimationEnd","======"+mRotation);

                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        mCircleClockwiseAccelerateAnimator.setInterpolator(new LinearInterpolator());
        mCircleClockwiseAccelerateAnimator.setRepeatCount(ValueAnimator.INFINITE);
        mCircleClockwiseAccelerateAnimator.setDuration(6000);
    }

這是我們項目中的一個簡單的動畫,這裡看到我們動畫的持續時間是ValueAnimator.INFINITE,但是我們在動畫的生命周期裡又引用了handler,這就很容易導致整個View層的洩露。
為了避免這種情況,在activity或者fragment的onDestory方法中我們要及時的解除動畫,移除handler。

 @Override
    protected void onDestroy() {
        stopAnim();
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
            mHandler = null;
        }
       if (mPresenter!=null){
           mPresenter.stopDetect();
       }
        super.onDestroy();
    }

private void stopAnim() {
        if (mCircleAntiClockwiseAccelerateAnimator != null) {
            mCircleAntiClockwiseAccelerateAnimator.cancel();                   mCircleAntiClockwiseAccelerateAnimator.removeAllUpdateListeners();                       mCircleAntiClockwiseAccelerateAnimator.removeAllListeners();
      mCircleAntiClockwiseAccelerateAnimator = null;
      } 
 }

相對應的animatorSet也有自己的removeListener方法,但是為了確保安全,我們在處理animatorSet的時候最好還是遍歷一下,然後移除每一個動畫的監聽,再移除集合的監聽,最後把集合置空。

補充:

解決動畫被遮擋的問題:

android:clipChildren="false"
android:clipToPadding="false"

有些朋友在做動畫的時候尤其是動畫的移動范圍比較大的時候,會發現我的動畫竟然你會被父控件遮擋,一開始我以為是層級的問題,於是修改布局,發現動畫依然會被遮擋。經過反復的檢查發現我漏掉了上面兩個屬性。上面兩個屬性默認情況下都是true,在為true的情況下,子控件在做動畫的時候會被padding和父控件遮擋,結果就是你只能子啊父控件的范圍內做動畫,這讓人很不爽。但是有兩點要注意:1,這兩個屬性只有配置在根布局才會有效。2,這兩個屬性一旦配置,就會應用於整個頁面。
動畫性能優化:
隨著android的發展和對用戶體驗的重視,android應用中的動畫越來越多。但是過多的動畫也給我們的機器帶來很大的負擔。就以我們上面說的ValueAnimator為例,我們一般是監控值的變化,來進行動畫。如果我們在log裡面打印值的變化的時候我們會發現,這些值的變化往往非常小,這也保證了我們動畫的流暢度,但是這也意味著我們在onAnimationUpdate中的代碼會被調用很多遍,很容易產生一些性能的浪費,所以如果我們頁面的動畫過多而onAnimationUpdate中的代碼又比較多的話我們可以對值進行過濾,具體的代碼就不貼了,思路就是判斷這一次的值和上一次的值如果變化過小就不再響應(已知問題:如果UI線程卡頓,有一定幾率出現動畫跳躍。具體情況,可以自己在log中查看值的變化)。其實動畫的優化可以做的有很多,遠不止上面的值過濾,常用的還有動畫暫停,動畫合並等等。但是現在android硬件大多性能足夠,很多時候並不需要做太多處理。這個可以根據自己的項目進行酌情優化。

暫時就這麼多,有空再來補充。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved