ListView适配器模式的应用
2017-12-24 22:00
295 查看
ListView适配器模式的应用
文章开头先贴下另外几篇设计模式的分析,感兴趣的可以看下:设计模式-简单说明
ListView观察者模式的应用
AsyncTask模板模式的应用
ListView桥接模式的应用
我们都知道ListView是用来展示列表的,然而列表在不同的使用场景中需要展示的样式,以及所提供的数据源都是不确定的,因为需求千变万化,Android团队在设计之初就考虑到了这一方面,所以适配器模式就理所当然的得到应用。我们简单说一下适配器模式的定义:源接口和目标接口不一致(这里的接口并不一定就是指interface),为了使两个不兼容的接口能够得到很好的对接,我们需要提供一个中间类,来将源接口进行转化成目标接口中需要的数据。
上面是一张简单的UML类图,说明了ListView和BaseAdapter的关系,而适配器模式的应用就在于Adapter的getView,将数据转化成View,然后在ListView中会将其添加到列表中。下面我们进入到源码分析一下适配器模式的具体实现,首先从Adapter的源码开始分析(作了适当的删减):
/** * An Adapter object acts as a bridge between an {@link AdapterView} and the * underlying data for that view. The Adapter provides access to the data items. * * Adapter的类声明,简单明了--> Adapter对象,充当AdapterView(ListView的间接父类)和数据视图之间的桥梁。提供了访问数据项的功能 */ public interface Adapter { int getCount(); Object getItem(int position); long getItemId(int position); View getView(int position, View convertView, ViewGroup parent); int getItemViewType(int position); int getViewTypeCount(); boolean isEmpty(); }
可以发现都是大家很熟悉的函数,而且Adapter的官方声明也说了,Adapter是AdapterView(ListView的父类)和数据视图之间的桥梁,接下来看下它的直接子类,我们以我们常用的Adapter->ListAdapter->BaseAdapter来分析,所以接下来就是ListAdapter的源码:
/** * ListView和数据之间的桥梁,任何需要显示的数据都将会在ListAdapter中进行转化。实际上是在我们实现的Adapter 的子类中进行转化的 */ public interface ListAdapter extends Adapter { /** * 是否所有的Item都是可点击和可选中的(分隔符是不满足条件的),只有都满足条件才返回true */ public boolean areAllItemsEnabled(); /** * 判断一个Item是否可点击、可选中,满足条件返回true */ boolean isEnabled(int position); }
发现ListAdapter也是接口,我们继续看它的直接子类BaseAdapter:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { public boolean areAllItemsEnabled() { return true; } public boolean isEnabled(int position) { return true; } public View getDropDownView(int position, View convertView, ViewGroup parent) { return getView(position, convertView, parent); } public int getItemViewType(int position) { return 0; } public int getViewTypeCount() { return 1; } public boolean isEmpty() { return getCount() == 0; } }
我们发现这是一个抽象类,它是一个公共基础类,实现了一些常用的公共逻辑,可以简单的理解作了一些常用的默认实现,如果你感兴趣你也可以自定义ListView适配器,直接实现ListAdapter。那么剩下没有实现的就是我们天天用的了,这里自定义一个MyAdapter,看看它有哪些逻辑需要实现:
class MyAdapter extends BaseAdapter{ @Override public int getCount() { return 0; } @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) { return null; } }
现在是不是倍感亲切,看着心里格外的舒坦呢。不要着急,这才刚刚开始呢,下面才是重点,Adapter我们已经看完了,大家现在肯定有一个疑问,Adapter返回的这些itemCount、View是怎么添加到ListView中的。我们从ListView的setAdapter为入口来分析:
@Override public void setAdapter(ListAdapter adapter) { ...一些初始化操作 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter); } else { //为成员变量mAdapter赋值 mAdapter = adapter; } ... // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter); if (mAdapter != null) { ... //调用mAdapter的getCount方法保存ListView的条目数 mItemCount = mAdapter.getCount(); ... } else { ... } //调用重新布局 requestLayout(); }
我们在以上的源码中注意到了我们想要的信息,首先它保存了我们设置的新的ListAdapter实例,然后调用其getCount()方法保存ListView的条目个数,最后调用了requestLayout来实现重新布局,我们看下具体的onLayout方法吧,这时候发现ListView并没有onLayout方法,那我们就去它的父类AbsListView中取寻找一下:
/** * Subclasses should NOT override this method but * {@link #layoutChildren()} instead. */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); ... //布局子View layoutChildren(); ... }
我们看见在AbsListView的onLayout的函数声明上表明,我们不该去重写onLayout,如果需要布局子View应该重写layoutChildren函数,在AbsListView的onLayout内部调用了layoutChildren,所以子类调用layoutChildren可以就可以实现布局子View,那我们就去看下ListView的layoutChildren吧,layoutChildren是很重要的,在ListView的条目复用机制时也会涉及到,不过我们现在并不是分析条目复用,所以只保留我们关心由BaseAdapter的getView返回的View是怎么添加到ListView中的:
@Override protected void layoutChildren() { ... //这里会根据不同的布局模式来添加子View到ListView中,默认走default 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: final int selectedPosition = reconcileSelectedPosition(); sel = fillSpecific(selectedPosition, mSpecificTop); /** * When ListView is resized, FocusSelector requests an async selection for the * previously focused item to make sure it is still visible. If the item is not * selectable, it won't regain focus so instead we call FocusSelector * to directly request focus on the view after it is visible. */ if (sel == null && mFocusSelector != null) { final Runnable focusRunnable = mFocusSelector .setupFocusIfValid(selectedPosition); if (focusRunnable != null) { post(focusRunnable); } } break; case LAYOUT_MOVE_SELECTION: sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom); break; default: //我们刚设置Adapter,ItemView都还没添加到ListView中,所以走的是上面的分支。并且没有设置填充模式的情况下,默认是从上往下填充,即fillFromTop if (childCount == 0) { if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); sel = fillFromTop(childrenTop); } else { final int position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } ... }
在layoutChildren中,会根据不同的模式来填充子View,默认走的default,我们刚设置Adapter,ItemView都还没添加到ListView中,所以走的是上面的分支。并且没有设置填充模式的情况下,默认是从上往下填充,即fillFromTop,我们看下它的源码:
/** * Fills the list from top to bottom, starting with mFirstPosition * * @param nextTop The location where the top of the first item should be * drawn * * @return The view that is currently selected */ 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,继续看看fillDown的源码:
private View fillDown(int pos, int nextTop) { View selectedView = null; //获取ListView的高度 int end = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } //此处是关键,做了死循环,直到下一个要添加的ItemView的top>ListView高度,或者条目已经填充完成(简单的说就是要吗填充满一屏就退出,要吗就是全部填充完,但是没填充满也退出) while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; //这里获取一个View View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); //将获取到的View的高度累加,用于判断是否添加满 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }
在fillDown中我们发现了一个View对象,我们就进入到makeAndAddView中看一下:
/** * Obtains the view and adds it to our list of children. * 获取View并添加到列表中 */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { if (!mDataChanged) { // Try to use an existing view for this position. //从缓存中获取View,有则直接使用,没有下面的obtainView会重新创建. final View activeView = mRecycler.getActiveView(position); if (activeView != null) { // Found it. We're reusing an existing child, so it just needs // to be positioned like a scrap view. //复用ItemView,所以放到特定位置即可 setupChild(activeView, position, y, flow, childrenLeft, selected, true); return activeView; } } // Make a new view for this position, or convert an unused view if // possible. //获取一个新的View对象 final View child = obtainView(position, mIsScrap); // This needs to be positioned and measured. //因为是新建ItemView,所以需要测量 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }
因为我们刚设置Adapter,所以是没得复用的,所以就走到了obtainView的逻辑,我们看下它的实现,该函数在AbsListView中:
/** * Gets a view and have it show the data associated with the specified * position. * 获取View,并绑定特定位置的数据 */ View obtainView(int position, boolean[] outMetadata) { ... final View child = mAdapter.getView(position, scrapView, this); ... return child; }
这里面代码不少,我们只关注核心,mAdapter.getView(),可以发现它调用了我们传递进来Adapter的实例的getView方法,并且将该View返回,所以说我们在ListView中的makeAndAddView函数使用的就是我们的Adapter实例的getView返回的View对象,既然已经返回,那么它是什么时候添加进去的呢,看下setupChild函数:
/** * Adds a view as a child and make sure it is measured (if necessary) and * positioned properly. * 添加子View */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean isAttachedToWindow) { ... //获取View的类型,以及是否可选中可点击 p.viewType = mAdapter.getItemViewType(position); p.isEnabled = mAdapter.isEnabled(position); ... if ((isAttachedToWindow && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) { //当后面复用回收的Item时会走这里 attachViewToParent(child, flowDown ? -1 : 0, p); // If the view was previously attached for a different position, // then manually jump the drawables. if (isAttachedToWindow && (((AbsListView.LayoutParams) child.getLayoutParams()).scrappedFromPosition) != position) { child.jumpDrawablesToCurrentState(); } } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } //将View添加到父容器中 addViewInLayout(child, flowDown ? -1 : 0, p, true); // add view in layout will reset the RTL properties. We have to re-resolve them child.resolveRtlPropertiesIfNeeded(); } ... }
我们发现setupChild函数的官方注释那里已经表明这是添加子View,具体的话这里有两个函数都可以添加子View,分别是
attachViewToParent和addViewInLayout,不过attachViewToParent是条目复用的时候使用的,我们第一次添加的时候调用的是addViewInLayout。
本人是新手,如果有什么错误或者意见请指出,谢谢大家抽出宝贵的时间阅读!!!
相关文章推荐
- PulltoRefreshListView的应用
- 暑期实验4__Intent、Bundle的使用以及ListView的应用
- 设计模式学习笔记--设计模式在Java I/O中的应用(装饰模式和适配器模式)
- Win8.1应用开发之适配器模式(C#实现)
- 详解Android应用中ListView列表选项栏的编写方法
- ListView Button ImageView 里应用selector选择器切换图片并保持住
- android应用开发全程实录-你有多熟悉listview? getView重写 inflate结合各种Adapter实例
- android listview局部刷新和模拟应用下载
- Java设计模式之《适配器模式》及应用场景
- ListView的应用
- Listview与adapter的关系及应用(超详细)
- android 笔记——listView应用
- 适配器模式在Web Service返回值中的应用
- ListView 和 GridView应用详解-----本文转自博客园
- android应用开发全程实录-你有多熟悉listview?(1) 推荐
- Android编程应用--仿QQ我的好友ExpandableListView应用
- ListView,GridView等列表控件中CountDownTimer的应用
- Android入门之ListView应用解析(二)
- android:duplicateParentState属性解释在listview中的应用
- C# 系统应用之ListView实现简单图片浏览器