Android源碼設(shè)計模式學(xué)習(xí)筆記-適配器模式

適配器模式在我們開發(fā)中使用率極高,從代碼中隨處可見的Adapter可以判斷出來。從最早的ListView,GridView到現(xiàn)在最新的RecyclerView都需要使用Adapter, 并且在開發(fā)過程中遇到的優(yōu)化問題,出錯概率較大的地方也基本都出自Adapter, 這也是一個讓人又愛又恨的角色.
說到底,適配器是將兩個不兼容的類融合在一起,它有點像粘合劑,將不同的東西通過一種轉(zhuǎn)換使得它們能夠協(xié)作起來。
這個模式的UML類圖如下.


image.png

適配器模式應(yīng)用的簡單示例

用電影接口做栗子,筆記本電腦的電源一般在5V電壓,但是在我們生活中的電線電壓一般是220V。這個時候出現(xiàn)了不匹配的狀況,在軟件開發(fā)中稱為接口不兼容,此時就需要適配器來進(jìn)行一個接口轉(zhuǎn)換. 我們可以加一個Adapter層來進(jìn)行接口轉(zhuǎn)換.

類適配器模式

在上述電源接口這個示例中,5V電壓就是Target接口,220V電壓就是Adaptee類,而將電壓從220v轉(zhuǎn)換到5v就是Adapter.
具體程序如下所示.

public interface FiveVolt {
    int getVolt5();
}
public class Volt220 {
    public int getVolt220(){
        return 220;
    }
}
public class VoltAdapter extends Volt220 implements FiveVolt {
    @Override
    public int getVolt5() {
        return 5;
    }
}
public class Test {
    public static void main(String[] args){
        VoltAdapter adapter = new VoltAdapter();
        System.out.println("輸出電壓 : "+adapter.getVolt5());
    }
}
對象適配器模式
public interface FiveVolt {
    int getVolt5();
}
public class Volt220 {
    public int getVolt220(){
        return 220;
    }
}
public class VoltAdapter implements FiveVolt{

    Volt220 mVolt220;

    public VoltAdapter(Volt220 mVolt220) {
        this.mVolt220 = mVolt220;
    }

    public int getVolt220(){
        return mVolt220.getVolt220();
    }

    @Override
    public int getVolt5() {
        return 5;
    }
}
public class Test {
    public static void main(String[] args){
        VoltAdapter adapter = new VoltAdapter(new Volt220());
        System.out.println("輸出電壓 : "+adapter.getVolt5());
    }
}

這種實現(xiàn)方式直接將要適配的對象傳遞到Adapter中,使用組合的形式實現(xiàn)接口兼容的效果。這比類適配器方式更為靈活。

Android源碼中的適配器模式-ListView

我們知道ListView作為最重要的控件,它需要顯示各式各樣的視圖,每個人需要顯示的效果不相同,顯示的數(shù)據(jù)類型,數(shù)量等也千變?nèi)f化,那么如何應(yīng)對這種變化成為架構(gòu)師要考慮的最重要特性之一.
Android的做法是增加一個Adapter層來隔離變化,將ListView需要的關(guān)于Item View接口抽象到Adapter對象中,并且在ListView內(nèi)部調(diào)Adapter這些接口完成布局等操作。這樣用戶只要實現(xiàn)了Adapter的接口,并且將Adapter設(shè)置給ListView, ListView就可以按照用戶設(shè)定的UI效果,數(shù)量,數(shù)據(jù)顯示每一項數(shù)據(jù).


image.png

我們發(fā)現(xiàn)在ListView中并沒有Adapter相關(guān)的成員變量,其實Adapter在ListView的父類AbsListView中.

public class ListView extends AbsListView {
           ListAdapter mAdapter;
}
//關(guān)聯(lián)到Window時調(diào)用,獲取調(diào)用Adapter中的getCount方法等
@Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //代碼省略
        //給適配器注冊一個觀察者
        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount;
            //獲取Item的數(shù)量,調(diào)用的是mAdapter的getCount方法
            mItemCount = mAdapter.getCount();
        }
    }
    //子類需要復(fù)寫layoutChildren()函數(shù)來布局child view, 也就是item view
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        mInLayout = true;
        final int childCount = getChildCount();
        if (changed) {
            for (int i = 0; i < childCount; i++) {
                getChildAt(i).forceLayout();
            }
            mRecycler.markChildrenDirty();
        }
        //布局Child View
        layoutChildren();
        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
        mInLayout = false;
    }

AbsListView定義了集合視圖的邏輯框架,比如Adapter模式的應(yīng)用,復(fù)用Item View的邏輯,布局子視圖的邏輯等,子類只需要復(fù)寫特定的方法即可實現(xiàn)集合視圖的功能。首先在AbsListView類型的View中添加窗口時會調(diào)用getCount函數(shù)獲取元素的個數(shù),然后在onLayout函數(shù)中調(diào)用layoutChilden函數(shù)對所有子元素進(jìn)行布局。layoutChilden實際是在ListView中實現(xiàn).

@Override
protected void layoutChildren() {
        //代碼省略
        switch (mLayoutMode) {
            //代碼省略
            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            default:
            //代碼省略
            break;
        }
}

ListView復(fù)寫了AbsListView中的layoutChilden函數(shù),在該函數(shù)中根據(jù)布局模式來布局Item View, 例如,默認(rèn)情況是從上到下開始布局,也有可能從下到上開始布局.

    //從下到上填充Item View[ 只是其中一種填充方式 ]
    private View fillDown(int pos, int nextTop) {
        View selectedView = null;

        int end = (mBottom - mTop);
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end -= mListPadding.bottom;
        }

        while (nextTop < end && pos < mItemCount) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

            nextTop = child.getBottom() + mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos++;
        }

        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }
//從下到上填充
private View fillUp(int pos, int nextBottom) {
        View selectedView = null;

        int end = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end = mListPadding.top;
        }

        while (nextBottom > end && pos >= 0) {
            // is this the selected item?
            boolean selected = pos == mSelectedPosition;
            View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
            nextBottom = child.getTop() - mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos--;
        }

        mFirstPosition = pos + 1;
        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

在每一種布局方式中都會從makeAndAddView函數(shù)獲取一個View, 這個View就是ListView的每一項的視圖,這里有一個pos函數(shù),也就是對應(yīng)這個View是ListView中的第幾項. 我們來看看makeAndAddView中的實現(xiàn)

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
    //代碼省略
    //獲取一個item View
    final View child = obtainView(position, mIsScrap);
    //將item View設(shè)置到對應(yīng)的地方
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
    return child;
}

makeAndAddView主要分兩個步驟,第一是根據(jù)position獲取一個item view, 然后將這個view布局到特定的位置。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容