Android動畫開發實踐

市面上的大多數應用,多多少少都會通過動畫,讓應用多一些靈動性和趣味性,並且在視圖之間的切換會顯得更加自然。例如許多應用都定制瞭自己的下拉刷新中的動畫,讓應用增色不少。Android動畫主要有:

View 動畫 屬性動畫 幀動畫


View動畫

View動畫是Android裡面常用的動畫方式。View動畫直接作用於View上面。View動畫有且隻有四種:平移、縮放、旋轉、透明度。平移就是左右上下位置移動,縮放就是大小的變換,旋轉就是視圖的2D旋轉,透明度就不用多說瞭,由於View動畫采用xml方式實現較多,所以這裡我們著重介紹下xml方式下的View動畫標簽。

動畫 標簽
平移動畫 TranslateAnimation
縮放動畫 ScaleAnimation
旋轉動畫 RotateAnimation
透明度動畫 AlphaAnimation
動畫集合 AnimationSet

 


    
       
    
    
   
    
      
    
    
        ...
    

插值器

我們可以看到這裡有一個 android:interpolator 屬性。這個在動畫中十分重要,interpolator 插值器,控制動畫是以一定的速度播放還是先快後慢等動畫播放效果。android內置瞭豐富的插值器,不同的插值器對整個動畫過程會產生不同的影響,默認的插值器是常量速度插值器,我們可以通過插值器讓動畫執行過程由慢到快的變換。下面是Android系統提供的部分常見插值器:

名稱 效果
AccelerateInterpolator 動畫由慢向快加速
DecelerateInterpolator 動畫由快到慢減速
AccelerateDecelerateInterpolator 動畫中間快兩端慢
AnticipateInterpolator 動畫反效果後再按照效果執行,例如動畫是向右平移100px,那麼會先向左平移一段事件再向右平移到終點
BounceInterpolator 動畫結束的時候會有一個彈跳的效果,自行腦補彈珠在地上彈起的效果
LinearInterpolator 常量速率執行動畫,默認
OvershootInterpolator 動畫結束時候,會反效果執行一段時間,例如向右平移100px,向右平移到終點,會反方向平移一定距離
CycleInterpolator 動畫按照一定形式循環播放一定的次數,例如向右平移100px,這時候會向右平移100px後,會反方向,向左平移100px,這樣重復一定次數
AnticipateOvershootInterpolator 組合瞭AnticipateInterpolator和OvershootInterpolator的效果

 

View動畫實現

View動畫有兩種實現方式,一種是硬編碼,一種是通過XML文件,建議使用XML方式。如果采用XML文件的話,那麼需要在/res文件夾下面新建anim文件夾,並且將XML動畫文件放在文件夾下面。

XML文件形式






    
    
//加載XML動畫並且播放
public void loadAnimation(){
   //加載動畫
   Animation animation = AnimationUtils.loadAnimation(this,R.anim.alpha_scale);
  //設定動畫播放時長
  animation.setDuration(3000);
  //通過View.startAnimation(Animation)執行動畫
  findViewById(R.id.iv).startAnimation(animation);
}

純硬編碼方式

 //純硬編碼方式
 public void loadAnimation2(){
         //動畫效果由透明動畫與縮放動畫組合成,所以需要用AnimationSet將他們裝起來
        AnimationSet animationSet = new AnimationSet(true);
        //創建透明度動畫
        AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
        //創建縮放動畫
        ScaleAnimation scaleAnimation = new ScaleAnimation(0f, 1f, 0f, 1f, 
         Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF,0.5f);
        animationSet.addAnimation(alphaAnimation);
        animationSet.addAnimation(scaleAnimation);
        animationSet.setDuration(3000);
        //通過View.startAnimation(Animation)播放動畫
        findViewById(R.id.iv).startAnimation(animationSet);
    }

View動畫常用方法

方法 描述
AnimationUtils.loadAnimation(int) 加載xml動畫
Animation.setDuration(long) 設置動畫播放時間
Animation.setRepeatCount(int) 設置動畫重復次數
Animation.setFillAfter(boolean) 設置動畫播放後是否停留在當前位置
Animation.start() 播放動畫
Animation.cancel() 停止動畫
View.startAnimation(Animation) 播放動畫

 

需要特別註意,View動畫的交互事件隻在動畫前的位置有效。例如我向右平移瞭100px,並停留在該位置,但是在該位置無法觸發觸摸等事件,因為View動畫的交互事件隻在原來的位置有效!


屬性動畫

屬性動畫是Android 從API 11(Android3.0)開始支持的動畫類型。雖然官方隻支持API11以上(包括),但是我們依舊可以使用NineOldAndroids在API11下面采用屬性動畫的用法(部分動畫效果實質還是View動畫)。屬性動畫十分強大,幾乎可以在任何對象上產生動畫效果。屬性動畫的原理是:在一定的動畫時長內,在不同的時間點(幀)下,不斷改變對象的屬性值,從而產生動畫效果。屬性動畫由三部分構成:動畫控制器、插值器、估值器。

屬性動畫控制器

屬性動畫控制器,控制著動畫的播放&停止,動畫的時間等等,並提供一系列的動畫回調。控制器是我們使用屬性動畫的入口。我們經常使用的是ObjectAnimator來實現動畫效果,如果需要更加靈活的控制動畫,那麼可以通過ValueAnimator實現。

標簽 描述
ValueAnimator 屬性動畫的基礎類,本身並不實現任何動畫效果,主要充當屬性動畫的控制器,控制播放、暫停、插值器的應用、時長設置、屬性動畫的回調方法等,如果我們需要通過它實現動畫效果的話,那麼需要配合回調方法手動更新對象的屬性值,下面將會給出例子
ObjectAnimator 繼承於ValueAnimator,實現屬性動畫的常用類。需要動畫目標對象提供Setter封裝方法。ObjectAnimator的實現原理是:當ValueAnimator根據插值器和估值器計算出下一個時間點需要的屬性值之後,ObjectAnimator會通過Java的反射機制,自動調用設置對象屬性的方法,以此達到動畫的效果。
AnimatorSet 屬性動畫集,將多個屬性動畫聚集起來,可以根據需求控制動畫的播放順序

屬性動畫插值器

這裡的插值器與上面View動畫的插值器是一致的,屬性動畫的原理是不停的改變屬性值,來實現動畫效果,這個屬性值的計算是由插值器與估值器共同計算出來的,而插值器是主要的影響因素。例如上面有一AccelerateInterpolator(動畫由慢到快)插值器,插值的增長速率會慢慢變快。以此才能達到由慢到快的動畫效果。下圖是AccelerateInterpolator插值器每兩個時間點的差值。
AccelerateInterpolatZ喎?/kf/ware/vc/vcrLl1rXG9w==” src=”/uploadfile/Collfiles/20160614/20160614093434113.png” title=”\” />
可以很清晰的看到,越往下,每兩個時間點的差值越大,也就是插值隨著時間加速增長,因此,才能讓動畫由慢到快。不同的插值器,在動畫開始到結束過程中,產生的插值數值也不一樣,需要註意的是,這裡的插值數值是隨著動畫時間變化的一個比例值,不是具體應該被設置的屬性值。

屬性動畫估值器

上面我們提到動畫播放過程中的屬性值由插值器和估值器共同計算,由於插值器隻得出瞭隨著時間變化而改變的一個插值比例值,所以我們還需要通過動畫估值器來計算出該時間點最終的屬性值。Android系統提供的默認估值器有:

估值器 描述
IntEvaluator 當作用屬性值是Int類型時候,可以使用該估值器計算最終屬性值
FloatEvaluator 當作用屬性值是Float類型時候,可以使用該估值器計算最終屬性值
ArgbEvaluator 當作用屬性值是顏色類型時候,可以使用該估值器計算最終屬性值
TypeEvaluator 如果當作用的屬性值系統並未提供相應的估值器,那麼就需要通過TypeEvaluator自定義估值器

 

ObjectAnimator 屬性動畫實例

我們比較常用ObjectAnimator實現屬性動畫,使用ObjectAnimator的時候,有一個前提條件,那就是需要目標對象提供作用屬性的Setter封裝方法。下面是Android 11(Android 3.0)之後。我們常用且View基類中提供瞭Setter方法的屬性。

屬性名稱 描述
alpha 透明度
scaleX X軸縮放值
scaleY Y軸縮放值
rotation 平面旋轉值
rotationX X軸3D旋轉
rotationY Y軸3D旋轉
pivotX 旋轉/縮放中心X軸坐標
pivotY 旋轉/縮放中心Y軸坐標
x X坐標
y Y坐標
translationX X軸 平移偏移量
translationY Y軸 平移偏移量
scrollX X軸 滾動偏移量
scrollX Y軸 滾動偏移量

利用ObjectAnimator實現上面View動畫從0到1透明並且逐漸放大的效果:

  private void playObjectPropertyAnimation() {
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        final AnimatorSet animatorSet = new AnimatorSet();
        //作用在ImageView的alpha、scaleX、scaleY屬性上
        ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(iv, "alpha", 0, 1);
        ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(iv, "scaleX", 0, 1);
        ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(iv, "scaleY", 0, 1);
        //這裡同一時間播放上面三個動畫
        animatorSet.playTogether(alphaAnimator, scaleXAnimator, scaleYAnimator);
        animatorSet.setDuration(3000);
        animatorSet.start();
   }

如果目標對象的作用屬性沒有提供Setter方法,那麼我們需要創建包裝類,提供相應的Setter方法。我們如果要將動畫作用於寬度&高度上,但是View對象並沒有提供寬度&高度的Setter方法,那麼我們需要創建相應的包裝類。

//View寬度包裝類
public class ViewWidthWrapper {

    private View v;
    public ViewWidthWrapper(View v){
        this.v = v;
    }
    //寬度Setter方法
    public void setWidth(int width){
        ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
        layoutParams.width = width;
        v.setLayoutParams(layoutParams);
        v.invalidate();
    }
    public int getWidth(){
        return v.getWidth();
    }
}
//播放寬度屬性
 private void playCustomPropertyAnimation() {
        final ImageView iv = (ImageView) findViewById(R.id.iv);
        //創建View寬度包裝類
        final ViewWidthWrapper widthWrapper = new ViewWidthWrapper(iv);
        iv.post(new Runnable() {
            @Override
            public void run() {
                //播放寬度屬性動畫
                final ObjectAnimator widthAnimator = ObjectAnimator.ofInt(widthWrapper,"width",widthWrapper.getWidth(),widthWrapper.getWidth()+100);
                widthAnimator.setDuration(3000);
                widthAnimator.start();
            }
        });

    }

ValueAnimator 屬性動畫實例

我們也可以利用ValueAnimator更加靈活的實現動畫,但是相對於ObjectAnimator會相對復雜一點。我們需要聯合ValueAnimator.AnimatorUpdateListener回調來實現屬性動畫。下面是用ValueAnimator實現上面ObjectAnimator實現的動畫效果。

public void loadValueAnimation(){
        final ImageView iv =  (ImageView)findViewById(R.id.iv);
        //估值器
        final FloatEvaluator floatEvaluator = new FloatEvaluator();
        //透明度動畫
        ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0,1);
        alphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //這裡我們需要自己計算最終屬性值
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                //自己更新作用屬性值
                iv.setAlpha(value);
            }
        });
        //X軸縮放動畫
        ValueAnimator scaleXAnimator = ValueAnimator.ofFloat(0,1);
        scaleXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                iv.setScaleX(value);
            }
        });
        //Y軸縮放值
        ValueAnimator scaleYAnimator = ValueAnimator.ofFloat(0,1);
        scaleXAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = floatEvaluator.evaluate(valueAnimator.getAnimatedFraction(),0,1);
                iv.setScaleY(value);
            }
        });
        final AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(alphaAnimator,scaleXAnimator,scaleYAnimator);
        animatorSet.setDuration(3000);
        iv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animatorSet.start();
            }
        });

    }

通過代碼我們可以看到,ValueAnimator隻是幫我們計算瞭插值並提供瞭一個插值變化的回調方法,我們需要自己手動在回調方法裡面,根據估值器計算最終屬性值,並且自己更新View的屬性值,最終才能實現動畫效果。相比較ObjectAnimator來說,實現同一個效果會相對麻煩,但是靈活性增高瞭。例如ValueAnimator不需要我們提供Setter方法。

屬性動畫常用方法

方法名稱 描述
ObjectAnimator.ofXX 屬性動畫的主要創建方法,根據作用屬性值來創建相應的屬性動畫,有ofInt,ofFloat等方法
ObjectAnimator.setDuration(int) 設置動畫播放時長
ObjectAnimator.start() 播放動畫
ObjectAnimator.cancel() 停止播放動畫,會暫停在當前這一幀
ObjectAnimator.end() 停止播放動畫集,直接跳到動畫最後一幀
ObjectAnimator.setRepeatCount(int) 設置動畫重復次數,ValueAnimator.INFINITE表示無限次數
ObjectAnimator.setRepeatMode(int) 設置動畫重復模式,有兩種,一種是ValueAnimator.RESTART[重新播放],一直是ValueAnimator.REVERSE[反序播放]
ObjectAnimator.setInterpolator(Interpolator) 設置插值器
ObjectAnimator.setEvaluator(TypeEvaluator) 設置估值器
AnimatorInflater.loadAnimator(Context,int) 加載Xml文件形式的屬性動畫
AnimatorSet.play(Animator) 將某個屬性動畫添加進入屬性動畫集,這個方法會返回一個Builder對象,通過這個Builder對象,我們可以簡單控制它與其他屬性動畫的播放順序,可以通過Builder.after(Animator),Builder.before(Animator),Builder.with(Animator)這三個方法來控制它與其他屬性動畫的播放順序
AnimatorSet.playSequentially(Animator…) 按照添加的順序播放屬性動畫集
AnimatorSet.playTogether(Animator…) 同一時間播放動畫集
AnimatorSet.setInterpolator(Interpolator) 設置插值器
AnimatorSet.setDuration(int) 設置動畫集的播放時長,動畫集設置的時長會覆蓋掉單個動畫設置的時長
AnimatorSet.start() 播放動畫集
AnimatorSet.cancel() 停止播放動畫集,會停止在當前這一幀
AnimatorSet.end() 停止播放動畫集,會跳到動畫最後一幀

幀動畫

幀動畫是直接由一副一副圖片組成,並且按照一定的順序和間隔進行播放,這就構成瞭幀動畫。Android幀動畫的實現十分簡單,通過AnimationDrawable就可以很簡單的實現幀動畫:




    
    
    
  private  void playFrameAnimation(){
        ImageView iv = (ImageView) findViewById(R.id.iv);
        //這裡跟我們設置其他圖片資源是一樣的
        iv.setImageResource(R.drawable.ani);
        //將ImageView圖片資源轉換成AnimationDrawable
        AnimationDrawable animationDrawable = (AnimationDrawable) iv.getDrawable();
        //播放幀動畫
        animationDrawable.start();
    }

其他方式實現動畫效果

除瞭用以上幾種Android提供的方式實現動畫之外,還可以通過自定義View實現動畫效果。
我們可以在onDraw()方法裡面,不停調用invaildate()來產生動畫效果,有一些動畫效果就是通過這種方式實現的。例如下面就是通過自定義View方式粗糙的仿荔枝FM加載稍等提示框的效果。
動畫效果

public class LizhiLoadingView extends View {

    private float lineOneStartY,lineOneEndY;
    private float lineSecondStartY,lineSecondEndY;
    private float lineThirdStartY,lineThirdEndY;
    private boolean onePlus;
    private Paint mPaint;

    public LizhiLoadingView(Context context) {
        super(context);
        initPaint();
    }

    public LizhiLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    public LizhiLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initPaint();
    }


    private void initPaint(){
        mPaint = new Paint();
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(20);
        mPaint.setStyle(Paint.Style.FILL);
        lineOneStartY = 0;
        lineOneEndY  = 100;
        lineSecondStartY = 10;
        lineSecondEndY = 80;
        lineThirdStartY = 0;
        lineThirdEndY = 100;
        onePlus = true;


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
       canvas.drawLine(80,lineOneStartY,80,lineOneEndY,mPaint);
       canvas.drawLine(150,lineSecondStartY,150,lineSecondEndY,mPaint);
        canvas.drawLine(220,lineThirdStartY,220,lineThirdEndY,mPaint);
        //下面改變繪畫參數
        if(onePlus){
            lineOneStartY++;
            lineThirdStartY++;
            lineSecondStartY --;
            lineOneEndY--;
            lineThirdEndY--;
            lineSecondEndY++;
        }else{
            lineOneStartY--;
            lineThirdStartY--;
            lineSecondStartY ++;
            lineOneEndY++;
            lineThirdEndY++;
            lineSecondEndY--;
        }
        if(lineOneStartY==25){
            onePlus = false;
        }else if(lineOneStartY ==0){
            onePlus = true;
        }
        //這裡導致onDraw()被不停調用
        invalidate();


    }


}

You May Also Like