天天看點

《Android開發藝術探索》之動畫深入分析(八)

                                                                            第七章  Android動畫深入分析

        Android的動畫可以分成三種:view動畫,幀動畫,屬性動畫。view動畫是通過對場景的對象不斷做圖像交換(平移、縮放、旋轉、透明度)而産生的動畫效果,漸進式可自定義。幀動畫就是播放一系列圖檔産生動畫效果(過多會導緻OOM)。屬性動畫通過動态的改變對象的屬性達到動畫效果(低版本可通過相容庫使用屬性動畫)。本章内容:View動畫以及自定義View動畫的方式,View動畫的一些特殊的使用場景,屬性動畫以及使用動畫的注意事項。

(一)View動畫

        View動畫的作用對象是view,它支援四種動畫:平移,縮放,旋轉和透明度。

1.1.View動畫的種類

        view動畫的變換效果對應着Animation的四個子類,分别是TranslateAnimation,ScaleAnimation,RotateAnimation,AlphaAnimation,這四種動畫可以通過xml來定義,也可以代碼來實作。建議用XML自定義動畫:

《Android開發藝術探索》之動畫深入分析(八)

1.1.1.使用XML進行View動畫

       如果要使用View動畫,首先要建立動畫的XML檔案,View的動畫既可以是單一的動畫,也可以組合在一起而set标簽就是組合動畫,對應着AnimationSet。在res/anim目錄下。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:shareInterpolator="true">
    <!--set标簽标示動畫集合,對應AnimationSet類,可包含若幹個動畫,内部也可以嵌套其他動畫。-->
    <!--android:interpolator:表示動畫集合所使用的插值器,插值器影響動畫的速度,比如非勻速動畫就需要插值器來制作動畫的過程,這個屬性可以不指定,預設為加速減速插值器-->
    <!--android:shareInterpolator:表示集合中的動畫是否和集合共享同一個插值器,如果集合不指定插值器,那麼子動畫就需要單獨的去指定所需要的插值器了-->

    <!--透明度動畫,改變View的透明度,對應的是AlphaAnimation,fromAlpha表示透明度的起始值,比如0.1;toAlpha表示透明度結束值。-->
    <alpha
        android:duration="400"
        android:fromAlpha="0.1"
        android:toAlpha="1" />
    <!--縮放動畫,對應的ScaleAnimation,view具有放大,縮小的動畫效果-->
    <!--fromXScale、toXScale 水準方向縮放的起始值和結束值;pivotX、pivotY是縮放軸點(軸點位置适當)的x、y坐标,它會影響縮放的效果。-->
    <scale
        android:duration="300"
        android:fromXScale="0.5"
        android:fromYScale="0.5"
        android:pivotX="10"
        android:pivotY="10"
        android:toXScale="1.2"
        android:toYScale="1.2" />
    <!--平移動畫,水準和豎直方向完成平移;對應TranslateAnimation類,-->
    <!--fromXDelta、fromYDelta表示x、y的起始值;android:toXDelta、android:toYDelta标示x、y的結束值。-->
    <translate
        android:duration="200"
        android:fromXDelta="10"
        android:fromYDelta="10"
        android:toXDelta="20"
        android:toYDelta="20" />
    <!--旋轉動畫,對應RotateAnimation,它可以讓view旋轉。-->
    <!--fromDegrees、toDegrees 旋轉開始、結束的角度;pivotX、pivotY旋轉軸點的x、y-->
    <rotate
        android:duration="2000"
        android:fromDegrees="0"
        android:pivotX="10"
        android:pivotY="10"
        android:toDegrees="180" />
    <!--重點:android:duration表明動畫持續的時間;android:fillAfter動畫結束之後是否停留在結束的位置-->
</set>      
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_click = (Button) findViewById(R.id.btn_click);
        Animation animation = AnimationUtils.loadAnimation(this,R.anim.testset);
        btn_click.startAnimation(animation);
}      

1.1.2.使用代碼進行View動畫

       将一個Button的透明度在10s内由0變為1。

AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
        alphaAnimation.setDuration(10000);
        btn_click.setAnimation(alphaAnimation);      

1.2.自定義View動畫

        除了系統提供的四種動畫以外,我們還可以自定義View動畫,派生出來新動畫隻繼承animation這個抽象類,重寫它的initialize和applyTransformation方法。initialize完成初始化工作;applyTransformation進行相應的矩陣變化。很多時候需要采用Camera來簡化矩陣變換過程。較少使用。示例:圍繞y軸旋轉并沿着z軸平移實作一種類似于3D的效果。

        代碼示例:

public class Rotate3dAnimation extends Animation {
    private final float mFromDegrees;
    private final float mToDegrees;
    private final float mCenterX;
    private final float mCenterY;
    private final float mDepthZ;
    private final boolean mReverse;
    private Camera mCamera;
    public Rotate3dAnimation(float fromDegrees, float toDegrees,
                             float centerX, float centerY, float depthZ, boolean reverse) {
        mFromDegrees = fromDegrees;
        mToDegrees = toDegrees;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float fromDegrees = mFromDegrees;
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;
        final Matrix matrix = t.getMatrix();
        camera.save();
        if (mReverse) {
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        } else {
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        camera.rotateY(degrees);
        camera.getMatrix(matrix);
        camera.restore();
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
    }
}      

1.3.幀動畫

       幀動畫就是順序的播放一組圖檔,系統提供了一個AnimationDrawable來實作幀動畫,幀動畫的使用比較簡單。一直顔色在變化。幀動畫使用簡單,但容易引起OOM,避免過多尺寸較大圖檔。

       XML定義:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@color/colorAccent"
        android:duration="1000" />
    <item
        android:drawable="@color/colorPrimary"
        android:duration="1000" />
    <item
        android:drawable="@color/what"
        android:duration="1000" />
</animation-list>      

         代碼中定義:

btn_click.setBackgroundResource(R.drawable.animationdrawable01);
        AnimationDrawable drawable = (AnimationDrawable) btn_click.getBackground();
        drawable.start();      

(二)View動畫的特殊使用場景

          除了四種View方式外,View動畫可以在一些特殊場景下使用:ViewGroup控制子元素的出場效果,activity的切換動畫。

2.1.LayoutAnimation

       LayoutAnimation作用于ViewGroup,為ViewGroup指定一個動畫,這樣他的子元素出場的時候就會具有這種動畫了,這種效果常常在listview上,我們時常會看到一種特殊的listview,他的每一個item都有一個動畫,使用LayoutAnimation實作。LayoutAnimation是View動畫

2.1.1.為ViewGroup的子元素加上出場效果

       步驟一:定義LayoutAnimation;

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animationOrder="normal"
    android:animation="@anim/testlayoutanimation">
    <!--android:delay 子元素開始動畫的延遲,假設子元素入場動畫的周期為300ms,那麼0.5表示每一個子元素都需要延遲150ms才能播放入場動畫-->
    <!--android:animationOrder  表示子元素動畫的順序,有三種模式,normal,random,reverse,分别表示順序執行、表示随機、倒序播放。-->
    <!--android:animation 為子元素指定具體入場動畫-->
</layoutAnimation>      

       步驟二:為子元素指定的入場動畫;

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:zAdjustment="normal">
    <translate
        android:duration="100"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100"
        android:toYDelta="100" />
    <rotate
        android:duration="400"
        android:fromDegrees="0"
        android:toDegrees="90" />
</set>      

        步驟三:為ViewGroup指定layoutanimation屬性,對于listview來說,這樣item就具有出場動畫了。

ListView listView = (ListView) layout.findViewById(R.id.list);
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(0.5f);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        listView.setLayoutAnimation(controller);      

2.1.2.Activity的切換效果

       利用overridePendingTransition(int enterAnim, int exitAnim)這個方法,這個方法必須在startactivity或者finish之後調用才是有效的,裡面的兩個參數也很簡單,是進出的動畫。有點秀。

btn_click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,Main2Activity.class);
                startActivity(intent);
                overridePendingTransition(R.anim.testset01,R.anim.testset01);
            }
        });
 @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.testset01,R.anim.testset01);
}      

(三)屬性動畫

         屬性動畫是API11加入的特性,它對作用對象進行了擴充,可對任何對象甚至沒有對象做動畫。屬性動畫中有ValueAnimator、ObjectAnimator、AnimatorSet等。

3.1使用屬性動畫

        屬性動畫可對任意對象的屬性進行動畫而不僅僅是view,動畫預設的時間間隔是300ms,預設幀率是10ms/幀。能夠在一個時間間隔内完成對象從一個屬性值到另一個屬性值的改變。API11之前沒有屬性動畫,可使用相容庫譬如nineoldandroids使用屬性動畫。

        比較常用的屬性動畫類是:ValueAnimator、ObjectAnimator(繼承自ValueAnimator)、AnimatorSet(動畫集合)等。

3.1.1.使用代碼完成屬性動畫

        場景一:改變一個對象的translationY屬性,讓其沿着Y軸向上平移一個時間,預設時間完成。

        代碼示例一:

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

       場景二:改變一個對象的背景顔色值,譬如改變view的背景,下面的動畫是讓view的背景從0xffff8080到0xff8080ff,動畫會無限循環、反轉。

       代碼示例二:

ValueAnimator colorAnim = ObjectAnimator.ofInt(imageView,"backgroundColor",0xFFF8080,
0xFF8080FF);
colorAnim.setDuration(3000);
colorAnim.setEvaluator(new ArgbEvaluator());
colorAnim.setRepeatCount(ValueAnimator.INFINITE);
colorAnim.setRepeatCount(ValueAnimator.REVERSE);
colorAnim.start();      

       場景三:動畫集合,5s内對View的旋轉,平移,縮放和透明度進行了改變。代碼示例三:

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

3.1.2.使用XML完成屬性動畫

        屬性動畫除了代碼實作外,還可以通過XML實作。屬性動畫需要定義在res/animator目錄下。在XML中可以定義ValueAnimator、ObjectAnimator和AnimatorSet,分别與之對應的标簽是<animator>、<objectAnimator>、<set>。<set>的android:ordering屬性有兩個可選值:”together”和”sequentially”。前者表示動畫集合的子動畫同時播放;後者表示動畫集合按照前後順序依次播放。預設值為together。

        代碼示例:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:duration="300"
        android:propertyName="x"
        android:valueTo="200"
        android:valueType="intType" />
    <!--objectAnimator标簽中的各屬性:-->
    <!--android:propertyName:表示屬性動畫作用對象的屬性的名稱-->
    <!--android:duration:表示動畫的時長-->
    <!--android:valueFrom:表示屬性的起始值-->
    <!--android:valueTo:表示屬性的結束值-->
    <!--android:startOffset:表示動畫的延遲時間,當動畫開始後,需要延遲多少毫秒才會真正的播放-->
    <!--android:repeatCount:表示動畫的重複次數,預設為零,-1表示無限循環-->
    <!--android:repeatMode:表示動畫的重複模式,有一個restart(連續播放)和reverse(逆向播放)模式-->
    <!--android:valueType:表示propertyName有兩個屬性有int和float兩個可選項,分别表示屬性的類型,和浮點型,-->
    <!--另外,如果所制定的是顔色類型,那麼就不需要指定propertyName,系統會自動對顔色類型進行處理-->
    <objectAnimator
        android:duration="300"
        android:propertyName="y"
        android:valueTo="300"
        android:valueType="intType" />
    <!--<animator-->
    <!--android:duration="1000"-->
    <!--android:repeatCount="infinite"-->
    <!--android:repeatMode="restart"-->
    <!--android:startOffset="15"-->
    <!--android:valueFrom="0.5dp"-->
    <!--android:valueTo="1.0dp"-->
    <!--android:valueType="colorType" />-->
</set>      

       如何調用?

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(getApplicationContext(), R.anim.animatorxm);
  set.setTarget(imageView);
set.start();      

3.1.3.如何選取屬性動畫的方式?

        建議用代碼實作,一方面代碼會比較簡單,另一方面屬性的起始值有時無法提前确定,eg:一個view需要從左邊移動到右邊,但是如果是用XML實作,無法提前知道螢幕的寬度,也無法将屬性動畫定義在XML中。

3.2.了解插值器和估值器

       TimeInterpolator(時間插值器)的作用是根據時間流逝的百分比來計算出目前屬性值改變的百分比。系統預設的是LinearInterpolator(線性插值器,勻速動畫),AccelerateDecelerateInterpolator(加速減速插值器:兩頭慢中間快)和DecelerateInterpolator(減速插值器)。

       TypeEvaluator(類型估值算法),也叫估值器,它的作用是根據目前屬性變化的百分比來計算變化後的屬性值。系統預設了針對整型屬性IntEvaluator,浮點型FloatEvaluator,和color顔色值ArgbEvaluator。兩者結合能實作非勻速動畫。

《Android開發藝術探索》之動畫深入分析(八)

       動畫的預設重新整理率為10ms/幀,所有該動畫将分5幀進行,我們來考慮一下第三幀(x=20,t=20ms),當時間為20ms的時候,時間流逝百分比為20/40 = 0.5,那x改變了多少?插值器傳回0.5,估值算法會根據0.5 0 40傳回結果20。當然也可以自定義估值算法和插值器。

3.3.屬性動畫的監聽器

       屬性動畫提供了監聽器用于監聽動畫的播放過程,主要有兩個接口AnimationUpdateListener和AnimationListener。

3.3.1.AnimationListener

       AnimatorListener 監聽了動畫開始,結束,取消和重複,為了友善開發,系統提供了AnimatorListenerAdapter這個類,他是擴充卡,這樣我們就可以選擇實作下面的方法。

public static interface AnimatorListener {
         //監聽了動畫開始
        void onAnimationStart(Animator animation);
         //監聽了動畫結束
        void onAnimationEnd(Animator animation);
        //監聽了動畫取消
        void onAnimationCancel(Animator animation);
         //監聽了動畫重複
        void onAnimationRepeat(Animator animation);
    }      

3.3.2.AnimationUpdateListener

    會監聽整個動畫過程,每播放一幀,就會被調用一次下面的方法,可以利用此特性做事情。

public static interface AnimatorUpdateListener {
        void onAnimationUpdate(ValueAnimator animation);
}      

3.4.對任意屬性做動畫

       場景:給Buttion設定一個動畫,讓它的寬度從目前的變成500px,可以用view動畫來搞定,但存在拉伸後超出螢幕的風險,利用屬性動畫試試。

      解決方法:

      Click事件:ObjectAnimator.ofInt(btn_click02,"width",500).setDuration(500).start();

       如果想要一個動畫生效,必須同時滿足兩個條件:(1)object必須要提供set方法,如果動畫的時候沒有傳遞初始值,那麼我們還要提供get方法,因為系統要去取abc的屬性(如果這條不滿意,程式直接Crash);(2)object的set方法對該屬性所做的改變必須通過某種方法反映,比如帶來UI的改變。(如果這條不滿足,動畫無效果但是不會Crash)。

       如果動畫無效,那麼應該:(1)給你的對象增加set/get方法,前提是你有權限的話(往往不可行);(2)用一個類來包裝原始對象,間接提供get/set方法(使用ViewWrapper專門包裝View,對View屬性的width屬性做動畫,并修改内部寬度);(3)采用ValueAnimator,監聽動畫過程,自己去實作屬性變化(基本上是整形估值器IntEvaluator的内部實作)。

        第二種方法的代碼實作:

private void performAnimate_01() {
        ViewWrapper wrapper = new ViewWrapper(btn_click02);
        ObjectAnimator.ofInt(wrapper,"width",500).setDuration(500).start();
    }
class ViewWrapper {
    private View mTarget;
    public ViewWrapper(View mTarget){
        this.mTarget = mTarget;
    }
    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }
    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}      

        第三種方法的代碼實作:

performAnimator(btn_click02,btn_click02.getWidth(),500); 
   private void performAnimator(final View target, final int start, final int end) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            //持有一個IntEvaluator對象,友善下面估值的時候使用
            private IntEvaluator mEvaluator = new IntEvaluator();

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //獲得目前動畫的進度值,整形1-100之間
                int currentValue = (int) animation.getAnimatedValue();
                //獲得目前進度占整個動畫之間的比例,浮點0-1之間
                float fraction = animation.getAnimatedFraction();
                //直接使用整形估值器,通過比例計算寬度,然後再設定給按鈕
                target.getLayoutParams().width = mEvaluator.eval(fraction, start, end);
                target.requestLayout();
            }
        });
        valueAnimator.setDuration(5000).start();
}      

3.5.屬性動畫的工作原理

        以上面代碼ObjectAnimator.ofInt(btn_click02,"width",500).setDuration(500).start();為例。

        步驟一:先看ObjectAnimator的start方法。判斷是否有相同的動畫,是的話去掉,接下來調用了父類ValueAmiator的start方法;

        步驟二:父類ValueAnimator的start方法:屬性動畫運作在Lopper線程中,最終會調用AnimationHandler的start方法,它是一個runnable對象。然後調用ValueAnimator的doAnimationFrame方法。

        步驟三:ValueAnimator的doAnimationFrame方法:調用animationFrame方法,而animationFrame内部調用了animateValue。calculateValue方法計算每一幀動畫所對應的屬性值。屬性值的get和set方法是通過反射調用完成的。

(四)使用動畫的注意事項

      可能存在的問題:

       1.OOM問題(記憶體耗盡)

       幀動畫中圖檔過多過大的時候容易OOM了,實際開發盡量避免使用幀動畫;

       2.記憶體洩漏

       在屬性動畫中有一類無限循環的動畫,在activity退出後不停止的話,可能就會導緻Activity無法釋放而導緻的記憶體洩漏。

       3.相容性問題

       屬性動畫在3.0以下的系統上有缺陷,最好做好适配工作。

       4.View動畫的問題

       view動畫對view的影像做動畫,并不是真正的改變view的狀态,eg:有時候會動畫完成後view無法隐藏的現象,setVisibility(View.GONE)失效,記得調用clearAnimation清除動畫。

       5.不要使用px

       在進行動畫的過程,要盡量使用dp,使用px會導緻适配問題。注:px(像素,1px代表螢幕上一個實體的像素點):不被建議使用,因為同樣100px的圖檔,在不同手機上顯示的實際大小可能不同,而dp是像素密度。系數*dp長度獲得像素數,适配性較好。(文字的尺寸一律用sp機關,非文字的尺寸一律使用dp機關,px頂多用于畫一條切割線)

       6.動畫元素的互動

      将view移動後,3.0以前的系統,不管是view動畫還是屬性動畫,新位置都無法調用單機事件,同時老位置卻可以,從3.0之後,屬性動畫單擊事件發生于移動後的位置,但是view動畫的點選事件仍然在原位置。

繼續閱讀