Android源码中的适配器模式
来源:互联网 发布:火炬之光2mod汉化 mac 编辑:程序博客网 时间:2024/05/22 08:16
在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 ? "<unknown type>" : 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。
- Android源码中的适配器模式
- Android源码中的适配器模式
- Android中的适配器模式
- android中的适配器模式
- Android中的适配器模式
- Android中的设计模式-适配器模式
- Android中的设计模式--适配器模式
- Android源码之ListView的适配器模式
- Android源码之ListView的适配器模式
- Android源码适配器模式---Activity类结构
- Android源码之ListView的适配器模式
- 适配器模式在Android中的应用
- 适配器模式与Android中的Adapter
- 适配器模式在Android开发中的应用
- 适配器模式在Android开发中的应用
- Android设计模式系列(9)--SDK源码之适配器模式
- Android设计模式系列(9)--SDK源码之适配器模式
- Android设计模式源码解析之适配器(Adapter)模式
- MySQL重置root用户密码的方法
- # 在Linux下的was服务器上部署web应用遇到问题
- Redis + Jedis + Spring整合遇到的异常
- Centos6.7安装Apache2.4+Mysql5.5+Apache2.4
- UML类图几种关系的总结
- Android源码中的适配器模式
- [编程规范]二、声明、定义、初始化
- 语音信号短时域分析之预处理(三)
- Linux的共享库so
- jQuery中this与$(this)的区别总结
- 日常生活
- 使用Linux(CentOS)搭建SVN服务器全攻略
- 操作系统知识点整理
- Hbuilder MUI用原生js添加或移除class属性