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