1. 程式人生 > >Android策略設計模式進階

Android策略設計模式進階

地方 on() raw cat off 標註 lis alt extend

策略模式定義了一系列的算法,並將每一個算法封裝起來,而且使它們還可以相互替換。策略模式讓算法獨立於使用它的客戶而獨立變化。Android中最典型的的策略設計模式是動畫插值器的使用,具體怎麽使用的,將是本文所重點所寫的內容。

1、UML類圖

技術分享圖片

  • Context:用來操作策略的上下文環境。
  • Strategy : 策略的抽象。
  • ConcreteStrategyA、ConcreteStrategyB : 具體的策略實現。

2、Android源碼中的模式實現

日常的Android開發中經常會用到動畫,Android中最簡單的動畫就是Tween Animation了,當然幀動畫和屬性動畫也挺方便的,但是基本原理都類似,畢竟動畫的本質都是一幀一幀的展現給用戶的,只不要當fps小於60的時候,人眼基本看不出間隔,也就成了所謂的流暢動畫。(註:屬性動畫是3.0以後才有的,低版本可采用NineOldAndroids來兼容。而動畫的動態效果往往也取決於插值器Interpolator不同,我們只需要對Animation對象設置不同的Interpolator就可以實現不同的效果,這是怎麽實現的呢?

首先要想知道動畫的執行流程,還是得從View入手,因為Android中主要針對的操作對象還是View,所以我們首先到View中查找,我們找到了View.startAnimation(Animation animation)這個方法。

public void startAnimation(Animation animation) {
 ? ?//初始化動畫開始時間
 ? ?animation.setStartTime(Animation.START_ON_FIRST_FRAME);
 ? ?//對View設置動畫
 ? ?setAnimation(animation); 
 ? ?//刷新父類緩存
 ? ?invalidateParentCaches();
 ? ?//刷新View本身及子View
 ? ?invalidate(true);
}

考慮到View一般不會單獨存在,都是存在於某個ViewGroup中,所以google使用動畫繪制的地方選擇了在ViewGroup中的drawChild(Canvas canvas, View child, long drawingTime)方法中進行調用子View的繪制。

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
 ? ?return child.draw(canvas, this, drawingTime);
}

再看下View中的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法中是如何調用使用Animation的

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
 ? ?//...

 ? ?//查看是否需要清除動畫信息
 ? ?final int flags = parent.mGroupFlags;
 ? ?if ((flags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) == ViewGroup.FLAG_CLEAR_TRANSFORMATION) {
 ? ? ? ?parent.getChildTransformation().clear();
 ? ? ? ?parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION;
 ? ?}

 ? ?//獲取設置的動畫信息
 ? ?final Animation a = getAnimation();
 ? ?if (a != null) {
 ? ? ? ?//繪制動畫
 ? ? ? ?more = drawAnimation(parent, drawingTime, a, scalingRequired);
 ? ? ? ?concatMatrix = a.willChangeTransformationMatrix();
 ? ? ? ?if (concatMatrix) {
 ? ? ? ? ? ?mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
 ? ? ? ?}
 ? ? ? ?transformToApply = parent.getChildTransformation();
 ? ?} else {
 ? ? ? ?//...
 ? ?}
}

可以看出在父類調用View的draw方法中,會先判斷是否設置了清除到需要做該表的標記,然後再獲取設置的動畫的信息,如果設置了動畫,就會調用View中的drawAnimation方法,具體如下:

private boolean drawAnimation(ViewGroup parent, long drawingTime,
 ? ? ? ?Animation a, boolean scalingRequired) {

 ? ?Transformation invalidationTransform;
 ? ?final int flags = parent.mGroupFlags;
 ? ?//判斷動畫是否已經初始化過
 ? ?final boolean initialized = a.isInitialized();
 ? ?if (!initialized) {
 ? ? ? ?a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
 ? ? ? ?a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
 ? ? ? ?if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
 ? ? ? ?onAnimationStart();//動畫開始監聽
 ? ?}

 ? ?//獲取Transformation對象,存儲動畫的信息
 ? ?final Transformation t = parent.getChildTransformation();
 ? ?//調用了Animation的getTransformation()方法,這裏就是通過計算獲取動畫的相關值
 ? ?boolean more = a.getTransformation(drawingTime, t, 1f);

 ? ?//代碼省略。。。

 ? ?if (more) {
 ? ? ? ?//根據具體實現,判斷當前動畫類型是否需要進行調整位置大小,然後刷新不同的區域
 ? ? ? ?if (!a.willChangeBounds()) {
 ? ? ? ? ? ?//...

 ? ? ? ?}else{
 ? ? ? ? ? ?//...重新重繪的區域、重新計算有效區域、更新這塊區域
 ? ? ? ?}
 ? ?}
 ? ?return more;
}

其中主要的操作是動畫始化、動畫操作、界面刷新。動畫的具體實現是調用了Animation中的getTransformation(long currentTime, Transformation outTransformation,float scale)方法。

public boolean getTransformation(long currentTime, Transformation outTransformation,
 ? ? ? ?float scale) {
 ? ?mScaleFactor = scale;
 ? ?return getTransformation(currentTime, outTransformation);
}

在上面的方法中主要是獲取縮放系數和調用Animation.getTransformation(long currentTime, Transformation outTransformation)來計算和應用動畫效果。

public boolean getTransformation(long currentTime, Transformation outTransformation) {
    //代碼省略。。。
    float normalizedTime;
    if (duration != 0) {
        //1、計算當前的時間的流逝百分比
        normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
                (float) duration;
    } else {
        // time is a step-change with a zero duration
        normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
    }

    //動畫是否已經完成
    final boolean expired = normalizedTime >= 1.0f || isCanceled();
    mMore = !expired;

    //代碼省略。。。

    if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        //代碼省略。。。

        //2、通過插值器獲取動畫的執行百分比
        final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
        //3、應用動畫效果
        applyTransformation(interpolatedTime, outTransformation);
    }

    //4、如果動畫指定完畢,那麽觸發動畫完成的回調或者指定重復動畫等操作

    if (!mMore && mOneMoreTime) {
        mOneMoreTime = false;
        return true;
    }

    return mMore;
}

?其中最重要的代碼通過紅筆標註出來了。

很容易發現Android系統中在處理動畫的時候會調用插值器中的getInterpolation(float input)方法來獲取當前的時間點,依次來計算當前變化的情況。這就不得不說到Android中的插值器Interpolator,它的作用是根據時間流逝的百分比來計算出當前屬性值改變的百分比。看到這裏,希望你已經理解了插值器和估值器的區別。之前有一個總結:插值器會傳入一個參數,這個參數就是一個時間進度值,也就是所謂的當前時間的流逝百分比。它相當於時間的概念,通過setDuration()制定了動畫的時長,在這個時間範圍內,動畫進度是一點點增加的,相當於一首歌,它的進度從0到1意思一樣。插值器需要根據這個值計算返回動畫的數值進度,這個值可以根據插值器的不同來設置不同的算法,最終這個值可以在監聽回調中拿到。

系統預置的有LinearInterpolator(線性插值器:勻速動畫)、AccelerateDecelerateInterpolator(加速減速插值器:動畫兩頭慢中間快)和DecelerateInterpolator(減速插值器:動畫越來越慢)等,如圖:

技術分享圖片

由於初期比較舊的版本采用的插值器是TimeInterpolator抽象,google采用了多加一層接口繼承來實現兼容也不足為怪了。很顯然策略模式在這裏作了很好的實現,Interpolator就是處理動畫時間的抽象,LinearInterpolator、CycleInterpolator等插值器就是具體的實現策略。插值器與Animation的關系圖如下:

技術分享圖片

這裏以LinearInterpolator、AccelerateInterpolator和CycleInterpolator為例:

LinearInterpolator:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
 ? ?public float getInterpolation(float input) {
 ? ? ? ?return input;
 ? ?}
}

CycleInterpolator:

public float getInterpolation(float input) {
 ? ?return (float)(Math.sin(2 * mCycles * Math.PI * input));
}

可以看出LinearInterpolator中計算當前時間的方法是做線性運算,也就是返回input*1,所以動畫會成直線勻速播放出來,而CycleInterpolator是按照正弦運算,所以動畫會正反方向跑一次,其它插值器依次類推。不同的插值器的計算方法都有所差別,用戶設置插值器以實現動畫速率的算法替換。

我們再來看看加速插值器的代碼:

public AccelerateInterpolator(float factor) {
 ? ?mFactor = factor;
 ? ?mDoubleFactor = 2 * mFactor;
}

public AccelerateInterpolator(Context context, AttributeSet attrs) {
 ? ?this(context.getResources(), context.getTheme(), attrs);
}

public float getInterpolation(float input) {
 ? ?if (mFactor == 1.0f) {//默認1.0f,鎖著事件的推移,變化範圍增大
 ? ? ? ?return input * input;
 ? ?} else {//用戶自己設置了值,根據這個值與1.0最大值乘積返回回去
 ? ? ? ?return (float)Math.pow(input, mDoubleFactor);
 ? ?}
}

我們看到,默認情況下,AccelerateInterpolator?的getInterpolation?方法中會對input進行乘方運算,這個input就是流逝時間百分比(時間進度值)。input取值範圍為0.0f~1.0f,當input逐漸增大時,input*input的變化範圍越來越大,使得動畫屬性值在同一時間段內的變化範圍更大,從而實現了加速動畫的效果。例如,動畫指定時間為1000ms,使用的是AccelerateInterpolator??插值器,在動畫指定了100ms時百分比為0.1,此時通過插值器計算得到的動畫數值進度值為0.01,;又經過100ms,此時的百分比為0.2,經過插值器計算變為0.04,;執行到0..ms,計算得到0.09....可以看到,在相同的100ms內百分比變化頻率逐漸增大。100~200ms變化值0.03,200~300ms變化值為0.05,這樣在同一個時間段內百分比差距越來越大,也就形成了加速的效果。

在調用了插值器的getInterpolation?方法後,會繼續調用動畫類的applyTransformation方法將屬性應用到對應的對象中。在Animation中是空實現,這裏選擇TranslateAnimation來看看它的具體實現:

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
 ? ?float dx = mFromXDelta;
 ? ?float dy = mFromYDelta;
 ? ?if (mFromXDelta != mToXDelta) {
 ? ? ? ?dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
 ? ?}
 ? ?if (mFromYDelta != mToYDelta) {
 ? ? ? ?dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
 ? ?}
 ? ?t.getMatrix().setTranslate(dx, dy);
}

當執行完applyTransformation後,View就發生了改變,不斷重復這個過程,動畫就隨之產生了。註意這裏是Matrix類的setTranslate方法,並沒有真正的修改View的屬性,這裏也是View動畫跟屬性動畫本質區別的地方了(個人這麽理解的,如果有誤還望指正)

Android策略設計模式進階