Android策略設計模式進階

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

一、UML類圖

image

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

二、Android源碼中的模式實現

平常的Android開發中常常會用到動畫,Android中最簡單的動畫就是Tween Animation了,固然幀動畫和屬性動畫也挺方便的,可是基本原理都相似,畢竟動畫的本質都是一幀一幀的展示給用戶的,只不要當fps小於60的時候,人眼基本看不出間隔,也就成了所謂的流暢動畫。(注:屬性動畫是3.0之後纔有的,低版本可採用NineOldAndroids來兼容。而動畫的動態效果每每也取決於插值器Interpolator不一樣,咱們只須要對Animation對象設置不一樣的Interpolator就能夠實現不一樣的效果,這是怎麼實現的呢?canvas

首先要想知道動畫的執行流程,仍是得從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的bash

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方法,具體以下:app

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)方法。ide

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) {
        //一、計算當前的時間的流逝百分比
        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)) {
        //代碼省略。。。

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

    //四、若是動畫指定完畢,那麼觸發動畫完成的回調或者指定重複動畫等操做

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

    return mMore;
}
複製代碼

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

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

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

image

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

image

這裏以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動畫跟屬性動畫本質區別的地方了(我的這麼理解的,若是有誤還望指正)

相關文章
相關標籤/搜索