關於Android中動畫探究(一)——視覺動畫

視圖動畫(補間)

以下爲Android常用的視圖動畫類,xml動畫這裏不做詳解。

基本動畫

  • ScaleAnimation(縮放動畫)可變化控件的大小

        /**
         * Scale動畫裏x、Y指這個控件的寬高百分比,取值0~1
         * 
         * @param fromX 動畫開始的X。
         * @param toX 動畫結束的X。
         * @param fromY 動畫開始的y。
         * @param toY 動畫結束的y。
         *
         * @param 動畫開始時X座標類型,可以理解爲從控件的哪個位置開始,下方動畫同值。
    	 *		  取值範圍爲ABSOLUTE(絕對位置)、RELATIVE_TO_SELF(以自身寬或高爲參考)、
    	 *		  RELATIVE_TO_PARENT(以父控件寬或高爲參考)。
         * @param pivotXValue 取值0~1(1 is 100%) 
         * @param pivotYType 動畫開始時座標類型
         *        取值範圍爲ABSOLUTE(絕對位置)、RELATIVE_TO_SELF(以自身寬或高爲參*考)、
         *        RELATIVE_TO_PARENT(以父控件寬或高爲參考)。
         * @param pivotYValue 取值0~1(1 is 100%) 
         *        下面代碼動畫表示:從控件的左上角位置開始,放大1.5倍
         */
            val animation2 = ScaleAnimation(1f, 1.5f, 1f, 1.5f, ScaleAnimation.RELATIVE_TO_SELF, 0f, ScaleAnimation.RELATIVE_TO_SELF, 0f)
    animation2.duration = 700
    
  • RotateAnimation

      /**
     	 * 旋轉動畫
         * @param fromDegrees 開始前角度 0代表當前無角度
         * @param toDegrees   動畫結束角度 
         * @param pivotXType  
         * @param pivotXValue 
         * @param pivotYType 
         * @param pivotYValue
         *        下面代碼動畫表示:從控件的中心旋轉360°
         */
    val animation3 = RotateAnimation(0f, 360f, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f)
    
  • TranslateAnimation

    /**
     * 平移動畫
     * @param fromXDelta 動畫前X座標
     * @param toXDelta   動畫結束X座標
     * @param fromYDelta 動畫前Y
     * @param toYDelta   動畫結束Y座標
     *        下面代碼動畫表示:從當前控件的起始位置0,向上Y移動100像素
     */
     var animation1 = TranslateAnimation(0f, 0f, 0f, -100f)
    
  • AlphaAnimation

      /**
         * 透明度動畫
         * @param fromAlpha 動畫前View的通明度
         * @param toAlpha 動畫結束時View的通明度
         */
    val animation4 = AlphaAnimation(1f, 0.1f)
    

進階用法

AnimationSet組合動畫,對View運行組合動畫,這裏需要注意AnimationSet中添加的動畫是一起執行的,不能設定動畫的先後執行順序,同樣也不能在動畫的過程中進行操作。

/**
     * AnimationSet 有兩個構造函數
     * AnimationSet(Context context, AttributeSet attrs) // 傳入一組動畫屬性attr(執行時間等)
     * AnimationSet(boolean shareInterpolator) // 是否共用插值器
     * 注意事項:
     * 1.AnimationSet設定duration值(執行時長),會使集合中animation duration屬性值失效
     * 2.AnimationSet設定repeatCount值(重複次數)無效,只會取子動畫設定值
     * 3.AnimationSet設定repeatMode值(REVERSE從結束位置執行反動畫,RESTART重新執行動畫),會使子動畫repeatMode無效
     */

/**  AnimationSet源碼
*/
class AnimationSet extends Animation {
    
     @Override
    public long getDuration() {
        final ArrayList<Animation> animations = mAnimations;
        final int count = animations.size();
        long duration = 0;
        boolean durationSet = (mFlags & PROPERTY_DURATION_MASK) == PROPERTY_DURATION_MASK;
        if (durationSet) {  //如果AnimationSet有設置duration,則取AnimationSet的duration
            duration = mDuration;
        } else { // 取子動畫中最大的duration
            for (int i = 0; i < count; i++) {
                duration = Math.max(duration, animations.get(i).getDuration());
            }
        }
        return duration;
    }
}

//使用方式
val animationTest = AnimationSet(true)    
animationTest.addAnimation(animation1)
animationTest.addAnimation(animation2)
animationTest.addAnimation(animation3)
animationTest.addAnimation(animation4)
animationTest.duration = 900
animationTest.interpolator = LinearInterpolator() //線性插值器

視圖動畫原理解析

    /**
     * view.startAnimation(animation) view與animation產生聯繫 
     * 初始化動畫的啓動設定爲第一禎,調用reset方法,也就是說不管動畫是否在執行過程,再次執行動畫依然回到第一	 * 幀。接着invalidate開始重繪,UI重繪機制這裏不做詳解,我們直接找到getAnimation()調用的地方		 * view.draw(Canvas canvas, ViewGroup parent, long drawingTime)
     */
    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

	public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        if (animation != null) {
            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }
	

/**View類*/
@UiThread 
public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
	 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
        // ...省略代碼
        final Animation a = getAnimation();
        if (a != null) {
            //處理視圖活動Animation
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
        }
       
     }

	 private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        //.. 
        final Transformation t = parent.getChildTransformation();
        boolean more = a.getTransformation(drawingTime, t, 1f);
        if (more) {
            // 判斷動畫是否過期,如果沒有過期,則去執行parent的invalidate,會重新執行child重繪
            // 繪製下一幀動畫,16ms一幀
        	parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));     
        }
     }
}


/**Animatio類*/
public abstract class Animation implements Cloneable {
  public boolean getTransformation(long currentTime, Transformation outTransformation) {
        if (mStartTime == -1) {
            mStartTime = currentTime;
        }

        final long startOffset = getStartOffset();
        final long duration = mDuration;
        float normalizedTime;  
        if (duration != 0) {
            // 先初始化動畫的啓動進度,比如1個4s的動畫,計算現在動畫進度百分比
            normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                    (float) duration;
        } else {
            normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
        }
      	//判斷動畫是否過期
        final boolean expired = normalizedTime >= 1.0f || isCanceled(); 
        mMore = !expired;
        if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
        if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
            if (!mStarted) {
                fireAnimationStart();
                mStarted = true;
                if (NoImagePreloadHolder.USE_CLOSEGUARD) {
                    guard.open("cancel or detach or getTransformation");
                }
            }
            if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
            if (mCycleFlip) {
                normalizedTime = 1.0f - normalizedTime;
            }

            final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); // 通過插值器計算出平穩值
            //執行動畫,調用子類ScaleAnimation、RotateAnimation的實現方法,這裏說明我們也可以自定義Animation,通過重寫applyTransformation方法,執行自己的動畫邏輯
            applyTransformation(interpolatedTime, outTransformation); 
        }
        if (expired) {
            if (mRepeatCount == mRepeated || isCanceled()) {
                if (!mEnded) {
                	// ... 動畫結束
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

                if (mRepeatMode == REVERSE) {
                    mCycleFlip = !mCycleFlip;
                }

                mStartTime = -1;
                mMore = true;
				// 判斷動畫RepeatCount,是否重複執行
                fireAnimationRepeat();
            }
        }
        return mMore;
    }
}

視圖動畫的侷限性

  1. 只能作用於view,但有時需求不是對於整個view的,而只是對view的某個屬性的,例如顏色的變化,也無法對非View的對象進行動畫處理。
  2. 只改變了view的視覺效果而已,修改了視圖繪製的地方,例如控件的點擊,還是動畫前控件的位置。
  3. 動畫效果固定,動畫類型只有四種,縮放,平移,旋轉,透明度的基本動畫,無法對其他屬性進行操作。
  4. 動畫雖然可以添加監聽,但是動畫開始後無法對動畫的執行過程進行控制。

視圖動畫的優點

  1. 使用方便,能滿足基礎動畫效果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章