編輯:關於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; } }
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; } //省略一部分代碼 }
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); }
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; }
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通過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; }
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"); } } } } }
所以,當數據很少,不會超過一屏,而且只能一些簡單的View的組合,可以考慮使用SimpleAdapter。
本文實例講述了Android編程之線性布局LinearLayout用法。分享給大家供大家參考,具體如下:線性布局(LinearLayout)可以讓它的子元素垂直或水平的方
前言在Android開發中,消息推送功能的使用非常常見。為了降低開發成本,使用第三方推送是現今較為流行的解決方案。今天,我將手把手教大家如何在你的應用裡集成小米推送目錄1
Android中的數據存儲主要有以下幾種方式: 1、使用SharedPreferences:該存儲方式主要用於應用程序有少量的數據需要保存,而且這些數據的格
字體管家是手機安卓手機上面的一個更改手機字體的軟體,應該算是手機美化類別的。字體管家在手機在線字體的收集方面還算是比較全面的,雖不能算是百變字體,但分類足以