Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android源碼中的適配器模式

Android源碼中的適配器模式

編輯:關於Android編程

在Android開發過程中,ListView的Adapter是我們最常見的類型之一,我們需要使用Adapter加載Item View的布局,並且進行數據綁定、緩存復用等操作。代碼大致如下:

ListView myListView =  (ListView)view.findViewById(R.id.id_list);

MyAdapter adapter = new MyAdapter();
myListView.setAdapter(adapter);

class MyAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return 6;
        }

        @Override
        public Object getItem(int position) {
            return null;
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {


            View view;

            MerchantSuccessViewHolder holder = null;
            if(convertView == null) {
                holder = new MerchantSuccessViewHolder();
                view = View.inflate(this,R.layout.item_view,null);
               
                holder.title = (TextView) view.findViewById(R.id.title);

                view.setTag(holder);
            } else {
                view = convertView;
                holder = (ViewHolder) view.getTag();
            }

           
            holder.title.setText("hello world");
                

            return view;
        }
    }

另外GridView的使用幾乎完全一樣。

ListView需要顯示各式各樣的視圖,每個人考慮的顯示效果各不相同,顯示的數據類型,數量也千變萬化,那麼如何應對這種變化,Android的架構師的做法就是采用適配器模式。

Android的做法是增加一個Adapter層來隔離變化,將ListView需要的關於Item View接口抽象到Adapter對象中,並在ListView內部調用Adapter這些接口完成布局等操作。這樣只要用戶實現了Adapter接口,並且該Adapter設置給ListView,ListView就可以按照用戶設定的UI效果、數量、數據來顯示每一項數據。ListView最重要的問題是要解決每一項Item視圖的輸出,ItemView千變萬化,但它終究都是View類型,Adapter統一將Item View輸出為view類型,這樣很好的應對了Item View的可變性。

那麼ListView是如何通過Adapter將千變萬化U效果設置給ListView的呢?

下面來跟蹤源碼一探究竟。

我們在ListView類中並沒有發生Adapter相關的成員變量,其實在ListView的父類AbsListView中,AbsListView是一個列表空間的抽象。源碼如下:

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        final ViewTreeObserver treeObserver = getViewTreeObserver();
        treeObserver.addOnTouchModeChangeListener(this);
        if (mTextFilterEnabled && mPopup != null && !mGlobalLayoutListenerAddedFilter) {
            treeObserver.addOnGlobalLayoutListener(this);
        }

        if (mAdapter != null && mDataSetObserver == null) {
            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            // Data may have changed while we were detached. Refresh.
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();//獲得Item的數量  這個方法需要我們重寫,交給程序猿

        }
    }

   @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();
        }

        layoutChildren();
        mInLayout = false;

        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;

        // TODO: Move somewhere sane. This doesn't belong in onLayout().
        if (mFastScroll != null) {
            mFastScroll.onItemCountChanged(getChildCount(), mItemCount);
        }
    }
當Activity被啟動時,它的布局中的ListView的onAttachToWindow方法就會被先調用,然後調用其onLayout方法。我們看到onAttachToWindow調用mAdapterd.getCount()方法,這時獲取到了Item View的數量。然後執行在onLayout方法時,會調用layoutChilren這個方法,具體的實現在子類中。在AbsListView是個空實現,ListView實現了這個方法,源碼如下:

 

 

   @Override
    protected void layoutChildren() {
        //省略一部分代碼

        try {
            super.layoutChildren();

            invalidate();

           
 //省略一部分代碼

            switch (mLayoutMode) {
            case LAYOUT_SET_SELECTION:
                if (newSel != null) {
                    sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
                } else {
                    sel = fillFromMiddle(childrenTop, childrenBottom);
                }
                break;
            case LAYOUT_SYNC:
                sel = fillSpecific(mSyncPosition, mSpecificTop);
                break;
            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_SPECIFIC:
                sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
                break;
            case LAYOUT_MOVE_SELECTION:
                sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
                break;
            default:
               //省略一部分代碼

                break;
            }

             //省略一部分代碼

    }

ListView重寫了父類的layoutChilden方法,該方法根據布局模式來布局模式來布局Item View,這裡討論最常用的從上到下布局模式。源碼如下:

 

 

    private View fillFromTop(int nextTop) {
        mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
        mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
        if (mFirstPosition < 0) {
            mFirstPosition = 0;
        }
        return fillDown(mFirstPosition, nextTop);
    }

fillFromTop又調用了fillDown方法

 

 

   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;
    }
可以看到上面代碼用到了mItemCount,沒錯這個變量已經在AbsListView的onAttachToWindow初始化過了。上面代碼的大概意思就是通過makeAndAddView方法獲取Item View,然後將mItemCount個Item View逐個往下布局,然後將高度累加。

 

然後繼續跟蹤,看makeAndAddView,其源碼如下:

 

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;


        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);

                return child;
            }
        }

        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);

        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

        return child;
    }

在makeAndAddView方法中,主要做了兩件事,

 

1)通過obtainView方法根據position獲取一個item View

2)通過setupChild方法將這個View布局到特定的位置。

這裡的setupChild方法主要是View的繪制相關操作,這裡不再贅述,主要是obtainView方法。下面來看看它是如何根據position獲得一個item View的。這個定義在AbsListView中。其主要源碼如下

 

final RecycleBin mRecycler = new RecycleBin();

    View obtainView(int position, boolean[] isScrap) {
        //省略一部分代碼

	//1、從緩存中的ItemView中獲取View  ListView的復用機制便在這裡了
        final View scrapView = mRecycler.getScrapView(position);
	
	//2、調用mAdapter.getView方法,注意將scrapView設置給getView方法的convertView參數
        final View child = mAdapter.getView(position, scrapView, this);

        //省略一部分代碼

        return child;
    }

 

可以看出,主要邏輯也就兩句代碼,obtainView方法定義了列表空間的Item View的復用邏輯,首先會從RecycleBin中獲取一個緩存的View,如果有緩存則將這個緩存的View傳遞給Adapter的getView方法的第二個參數convertView參數,這也就是我們隊Adapter的最常見的優化方式啊,即判斷getView的convertView是否為空,如果為空則從xml中創建一個新的View,否則使用緩存的View。這樣避免了每次都從xml加載布局的消耗,能顯著提升ListView等列表控件的效率,通常的實現如下。

 

 @Override
        public View getView(int position, View convertView, ViewGroup parent) {


            View view;

            MerchantSuccessViewHolder holder = null;
            if(convertView == null) {
                holder = new MerchantSuccessViewHolder();
                view = View.inflate(this,R.layout.item_view,null);
               
                holder.title = (TextView) view.findViewById(R.id.title);

                view.setTag(holder);
            } else {
                view = convertView;
                holder = (ViewHolder) view.getTag();
            }

           
            holder.title.setText("hello world");
                

            return view;
        }

通過這種緩存機制,即使有成千上萬的數據項,ListView也能夠流暢運行,因此,只有填滿一屏所需的Item View存在內存中。流程圖如圖所示:

 

\

最後,總結一下,不然前面一直看源碼都快忘了適配器模式了,ListView通過Adapter來獲取Item View的數量、布局、數量等,在這裡最為重要的就是getView方法,這個方法返回的是一個View的的對象,也就是Item View,由於它返回的是View,而千變萬化的UI視圖都是View的子類,通過依賴抽象這個簡單的原則和Adapter模式將Item View的變化隔離了,保證了ListView的高度定制化,在獲取了View之後,將這個View顯示在特定的position商,在家桑Item View的復用機制,整個ListView就運轉起來了。

雖然這裡的BaseAdapter不是經典的適配器模式,確實適配器模式很好的擴展。也很好的提現了面向對象的一些基本准則。用戶只要處理getCount、getItem、getView方法就可以了,達到了無線適配擁抱變化的目的。

 

 

最後再談一下SimpleAdapter,它們都是給ListView的設置Adapter,與BaseAdapter不同的是,SimpleAdapter是具體的Adapter,BaseAdapter是抽象類,這樣我們可以高度定制化,擴展。我們再集成BaseAdapter的時候,可以在getView方法中進行各種優化,緩存機制還有ViewHolder的應用。

 

但是在SimpleAdapter中,雖然有緩存復用,但是並沒有ViewHolder的概念,所以說優化效果不如我們自己定制的效果,下面貼上SimpleAdapter的getView方法。

 

    public View getView(int position, View convertView, ViewGroup parent) {
        return createViewFromResource(mInflater, position, convertView, parent, mResource);
    }

    private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
            ViewGroup parent, int resource) {
        View v;
        if (convertView == null) {
            v = inflater.inflate(resource, parent, false);
        } else {
            v = convertView;
        }

        bindView(position, v);

        return v;
    }


getView方法又會調用createViewFromResource方法,createViewFromResource中只是判斷了緩存,但是並沒有ViewHolder的優化,只是通過bindView方法將數據和Item View綁定而已。bindView方法源碼如下

 

 

   private void bindView(int position, View view) {
        final Map dataSet = mData.get(position);
        if (dataSet == null) {
            return;
        }

        final ViewBinder binder = mViewBinder;
        final String[] from = mFrom;
        final int[] to = mTo;
        final int count = to.length;

        for (int i = 0; i < count; i++) {
            final View v = view.findViewById(to[i]);
            if (v != null) {
                final Object data = dataSet.get(from[i]);
                String text = data == null ? "" : data.toString();
                if (text == null) {
                    text = "";
                }

                boolean bound = false;
                if (binder != null) {
                    bound = binder.setViewValue(v, data, text);
                }

                if (!bound) {
                    if (v instanceof Checkable) {
                        if (data instanceof Boolean) {
                            ((Checkable) v).setChecked((Boolean) data);
                        } else if (v instanceof TextView) {
                            // Note: keep the instanceof TextView check at the bottom of these
                            // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                            setViewText((TextView) v, text);
                        } else {
                            throw new IllegalStateException(v.getClass().getName() +
                                    " should be bound to a Boolean, not a " +
                                    (data == null ? "" : data.getClass()));
                        }
                    } else if (v instanceof TextView) {
                        // Note: keep the instanceof TextView check at the bottom of these
                        // ifs since a lot of views are TextViews (e.g. CheckBoxes).
                        setViewText((TextView) v, text);
                    } else if (v instanceof ImageView) {
                        if (data instanceof Integer) {
                            setViewImage((ImageView) v, (Integer) data);                            
                        } else {
                            setViewImage((ImageView) v, text);
                        }
                    } else {
                        throw new IllegalStateException(v.getClass().getName() + " is not a " +
                                " view that can be bounds by this SimpleAdapter");
                    }
                }
            }
        }
    }

可以看出,bindView方法不能處理ViewGroup,只能是一些簡單的TextView,ImageView等View。

 

所以,當數據很少,不會超過一屏,而且只能一些簡單的View的組合,可以考慮使用SimpleAdapter。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved