Android之動畫

Claire_ljy發表於2020-04-04

Android的動畫可以分為三種,View動畫、幀動畫、屬性動畫。View動畫通過對場景裡的物件不斷做影像變化(平移、縮放、旋轉、透明度)從而產生動畫效果,它是一種漸進式動畫,而且View動畫支援自定義。幀動畫可以理解為圖片切換動畫(如果圖片過多,會導致OOM)。屬性動畫為API11的新特性,屬性動畫通過通過動態地改變物件的屬性從而達到動畫效果。

  • View動畫

View動畫的種類:View動畫的四種變換效果對應著Animation的四個子類。TranslateAnimation,ScaleAnimation,RotateAnimation,AlphaAnimation。這四種動畫可以通過XML來定義,也可以通過程式碼動態建立。對於View動畫來說,建議XML來定義,這是因為XML格式的動畫可讀性好。

View動畫的四種變換
名稱 標籤 子類 效果
平移動畫 <translate> TranslateAnimation 移動View
縮放動畫 <scale> ScaleAnimation 放大或縮小View
旋轉動畫 <rotate> RotateAnimation 旋轉View
透明度動畫 <alpha> AlphaAnimation 改變View的透明度

 

 

 

 

 

 

 

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:shareInterpolator="true"
    android:duration="1000"
    android:fillAfter="false">

    <alpha android:fromAlpha="float"
        android:toAlpha="float" />
    <scale android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />

    <translate android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
</set>

View動畫可以是單個動畫,也可以是一系列動畫組成。<set>表示動畫集合,對應AnimationSet類。

android:interpolator:表示動畫集合所使用的插值器,插值器影響動畫的速度,比如非勻速動畫就需要通過插值器來控制動畫的播放速度。

android:shareInterpolator:表示集合中的動畫是否共享同一個插值器。如果不指定插值器,那麼子動畫就需要單獨指定所需要的插值器或者使用預設值。

AlphaAnimation anim = (AlphaAnimation) AnimationUtils.loadAnimation(this, R.anim.anim_test);
TextView tv = findViewById(R.id.tv);
tv.startAnimation(anim);

除了XML定義動畫之外,還可以使用程式碼建立動畫。

AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(300);
TextView tv = findViewById(R.id.tv);
tv.startAnimation(alphaAnimation);

 

 注意:通過Animation的setAnimationListener的方法可以給View動畫新增過程監聽。如適用於帶動畫的啟動頁,監聽動畫完成後進入MainActivity。

anim.setAnimationListener(new Animation.AnimationListener() {
      @Override
      public void onAnimationStart(Animation animation) {}
      @Override
      public void onAnimationEnd(Animation animation) {}
      @Override
      public void onAnimationRepeat(Animation animation) {}
});

 

自定義View動畫:

自定義View動畫是一件簡單而複雜的事情。說簡單是:因為派生一種新動畫只需要整合Animation這個抽象類,然後重寫它的initialize和applyTransformation方法。在initialize中進行初始化工作,在applyTransformation中進行相應的矩陣變換即可。很多時候需要Camera來簡化矩陣變換過程。說複雜,是因為需要運用到矩陣變換的過程,使用到數學上的概念。之後再做相關的分析講解。

 

  • 幀動畫

幀動畫順序播放一組預先定義好的圖片,類似電影播放。不同於View動畫,系統提供另外一個類AnimationDrawable來使用幀動畫。幀動畫的使用比較簡單,先使用XML來定義一個AnimationDrawable。在drawable目錄下建立:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/img1" android:duration="500"/>
    <item android:drawable="@drawable/img2" android:duration="500"/>
    <item android:drawable="@drawable/img3" android:duration="500"/>
</animation-list>

將上述的Drawable作為View的背景並通過Drawable來播放動畫。

TextView tv = findViewById(R.id.tv);
tv.startAnimation(alphaAnimation);
AnimationDrawable animationDrawable = (AnimationDrawable) tv.getBackground();
animationDrawable.start();

注意幀動畫比較容易引起OOM,所以避免使用尺寸較大的圖片。

 

  • View動畫的特殊場景使用

在一些特殊場景下,如ViewGroup中控制子元素的出場效果,在Activity中實現不同Activity之間的切換效果。

 

LayoutAnimation作用於ViewGroup,作為ViewGroup指定一個動畫,這樣當它的子元素出場時都具有這樣的效果。常見的ListView的Item的出場以一定的動畫效果出現。

定義LayoutAnimation如下:

//res/anim/anim_layout.xml
<?
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/anim_item" />

delay表示子元素開始動畫的時間延遲。比如說元素入場動畫週期為300ms,那麼0.5表示每個子元素都需要延遲150ms才能播放入場動畫。第一個子元素延遲150ms開始播放入場動畫,第二個子元素延遲300m開始播放入場,以此類推。

animationOrder:子元素動畫順序,normal, reverse和random。

android:animation: 為元素指定具體的入場動畫。

為ViewGroup指定android:layoutAnimation屬性,對於ListView而言,指定之後就是Item的出場動畫了。除了XML中指定LayoutAnimation之外,還可以通過LayoutAnimationController來實現,具體如下:

ListView listView = findViewById(R.id.list_view);
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);

 

 

Activity的切換效果:

Activity有預設的切換效果,但是這個效果是可以自定義的,主要用到overridePendingTransition(int enterAnim, int exitAnim)這個方法,這個必須在startActivity(intent)和finish()之後被呼叫才能生效,它的引數含義如下:

enterAnim - Activity被開啟時,所需要的動畫資源id

exitAnim - Activity被暫停時,所需要的動畫資源id

當啟動一個Activity時,可以按照如下方式為其新增自定義的切換效果:

Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.anim.exitAnim);

 

當Activityt退出時,也可以為其指定自定義的切換效果:

@Override
public void finish() {
     super.finish();
     overridePendingTransition(R.anim.enter_anim, R.anim.exitAnim);
}

 

Fragment也可以新增切換效果,可以通過FragmentTransaction中的setCustomAnimations()方法來新增切換動畫,這個切換動畫需要時View動畫。還有其他的方法為Activity和Fragment新增切換動畫,但是需要考略相容為題。

 

  • 屬性動畫

屬性動畫是API11新加入的特性,和View動畫不同,它對作用的物件進行擴充套件,屬性動畫可以對任何物件做動畫,甚至還可以沒有物件。屬性動畫可以對任意物件的屬性進行動畫而不僅僅是View。屬性動畫中有ValueAnimator, ObjectAnimator和AnimatorSet等概念。為相容之前的版本,採用nineoldandroids源動畫庫。

ObjectAnimator繼承自ValueAnimator,AnimatorSet是動畫集合。屬性動畫需要定義在res/animator/目錄下。

  1. 改變一個物件myObject的translationY屬性,讓其沿著Y軸向上移動一段距離,在預設的時間完成。
    1. ObjectAnimator.ofFloat(tv, "translationY", -myObject.getHeight())
                      .start();
  2. 改變一個物件背景色屬性,典型的改變View背景,下面的動畫可以讓背景色在3s內實現從0xFFFF8080到0xFF8080FF的漸變,動畫會無限迴圈而且會有反轉的效果。
    1. ValueAnimator colorAnim = ObjectAnimator.ofInt(myObject, "backgroundColor", 0xFFFF8080, 0xFF8080FF);
              colorAnim.setDuration(3000);
              colorAnim.setEvaluator(new ArgbEvaluator());
              colorAnim.setRepeatCount(ValueAnimator.INFINITE);
              colorAnim.setRepeatMode(ValueAnimator.REVERSE);
              colorAnim.start();
  3. 動畫集合,5秒內對View的旋轉,平移,縮放和透明度都進行改變。
    1. AnimatorSet set = new AnimatorSet();
      set.playTogether(
               ObjectAnimator.ofFloat(view, "rotationX", 0, 360),
               ObjectAnimator.ofFloat(view, "rotationY", 0, 180),
               ObjectAnimator.ofFloat(view, "rotation", 0, -90),
               ObjectAnimator.ofFloat(view, "translationX", 0, 100),
               ObjectAnimator.ofFloat(view, "translationY", 0, 100),
               ObjectAnimator.ofFloat(view, "scaleX", 1, 1.5f),
               ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5f),
               ObjectAnimator.ofFloat(view, "alpha", 1, 0.5f, 1)
      );
      set.setDuration(5 * 1000).start();

屬性動畫除了程式碼實現之外,還可以通過XML來定義。

eg.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator android:propertyName="x"
        android:duration="300"
        android:valueTo="300"
        android:valueType="intType" />
    
    <objectAnimator android:propertyName="y"
        android:duration="300"
        android:valueTo="300"
        android:valueType="intType"/>
</set>

載入上面的屬性動畫:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.property_animator);
set.setTarget(view);
set.start();

建議:屬性動畫採用程式碼來實現,這是因為程式碼的方式會比較簡單,更重要的是,在一個屬性起始值無法確定的情況下,將無法將屬性定義在xml檔案中。

  • 插值器和估值器

 TimeInterpolater:時間插值器,它的作用是根據時間流逝的百分比來計算出當前屬性改變的百分比,系統預置的有LinearInterpolator(線性插值器:勻速動畫)、AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢,中間快),DeceleratrInterpolator(減速插值器:動畫越來越慢)等。

TypeEvaluator:型別估值演算法,也叫做估值器。它的作用是根據當前屬性改變的百分比來計算改變後的屬性值,系統預置的有IntEvaluator(針對整型屬性),FloatEvaluator(針對浮點型屬性),ArgbEvaluator(針對Color屬性)。

 

線性差值器,屬於勻速動畫,其原始碼為:

/**
 * An interpolator where the rate of change is constant
 */
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input; /** 輸入值和返回值一樣 */
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

那到底輸入數值是什麼?這需要估值演算法來確定,如整型估值器的原始碼為:

public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>int</code> or
     *                   <code>Integer</code>
     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

evaluate方法的三個引數分別是估值小數,起始值,結束值。例子:勻速動畫,物件從0位置到100,當估值小數位0.5的時候,根據演算法result = 0 + 0.5(100 - 0) = 50。

屬性動畫要求物件有該屬性的set方法和get方法(可選)。除了系統預置的插值器和估值演算法外,我們還可以自定義。自定義差值演算法需要實現Interpolator或TimeInterpolator;自定義估值演算法需要實現TypeEvaluator。

屬性動畫的監聽:

有兩個常用的屬性動畫監聽:AnimatorListener, AnimatorUpdateListener

ObjectAnimator animator = new ObjectAnimator();
animator.addListener(new Animator.AnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {}

        @Override
        public void onAnimationEnd(Animator animation) {}

        @Override
        public void onAnimationCancel(Animator animation) {}

        @Override
        public void onAnimationRepeat(Animator animation) {}
});

從AnimatorListener的定義來看,它可以監聽動畫的開始,結束,取消和重複動畫。

public static interface AnimatorUpdateListener {
    /**
     * <p>Notifies the occurrence of another frame of the animation.</p>
     *
     * @param animation The animation which was repeated.
     */
    void onAnimationUpdate(ValueAnimator animation);

}

AnimatorUpdateListener比較特殊,它會監聽整個動畫過程,動畫是由許多幀組成的,每播放一幀onAnimationUpdate()就會被呼叫一次,利用這個特徵,我們可以做一些特殊事情。

 

  • 對任意屬性做動畫

 例如TextView的寬度動畫變為800的動畫,當layout_width的屬性為wrap_content時,是有動畫的,但是如果設定為固定值之後,下面的屬性動畫就沒有效果了。

//高度從原來的預設值變為800
ObjectAnimator objectAnimator = ObjectAnimator.ofInt(textView, "width", 800);
objectAnimator.setDuration(500);
objectAnimator.start();

 

 這是因為:

public void setWidth(int pixels) {
    mMaxWidth = mMinWidth = pixels;
    mMaxWidthMode = mMinWidthMode = PIXELS;

    requestLayout();
    invalidate();
}
 public final int getWidth() {
       return mRight - mLeft;
 }

setWidth不是所謂意義上的寬度設定,所以就不會產生動畫。對任意屬性做動畫,一般需要有set和get方法,設定流程如下:

 如果沒有屬性設定獲取許可權,可以定義一個包裹類進行封裝,實現set和get方法;如:

public static class ViewWrapper{

        private View mTarget;

        public ViewWrapper(View target){
            mTarget = target;
        }

        public void setWidth(int width){
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }

        public int getWidth(){
            return mTarget.getLayoutParams().width;
        }
}
ViewWrapper viewWrapper = new ViewWrapper(view);
ObjectAnimator widthAnimator = ObjectAnimator.ofInt(viewWrapper, "width", btn.getWidth(), 800)
        .setDuration(500);
widthAnimator.start();

這樣,上面的TextView就可以實現寬度的動畫效果了。

自定義View設定相關屬性,進行動畫案例:

/**
 * 自定義圓環
 * 對屬性progress設定set和get方法
 * 就可以對CircleProgress的progress進行屬性動畫
 */
public class CircleProgress extends View{

    private Paint mPaint;
    private RectF rectF;
    private int progress = 270;
    int width, height;

    public CircleProgress(Context context) {
        super(context);
        init();
    }

    public CircleProgress(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CircleProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(20);
        mPaint.setColor(Color.RED);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        }else{
            width = 100; //設定為預設值
        }

        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        }else{
            height = 100; //設定為預設值
        }
        rectF = new RectF(20, 20, width-20, height-20);
        setMeasuredDimension(Math.min(width, height), Math.min(width, height)); //矩形
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawArc(rectF, -90, progress, false, mPaint);
        invalidate();
    }

    public void setProgress(int progress){
        this.progress = progress;
    }

    public int getProgress(){
        return progress;
    }
}

 

轉載於:https://www.cnblogs.com/denluoyia/p/8978045.html

相關文章