💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
>[info] **注意**:文章中涉及到的源码, 是以Android4.0 (API 14)源码为基础的。貌似Android4.4 (API19及其以上版本)源码会有细微变更,所以具体到某一个版本具体分析。 > 另外,RecyclerView 是Android 5.0(API21)才在V7包中引入的。 >[warning] **声明**:这里的Adapter并不是经典的适配器模式,但是却是对象适配器模式的优秀示例,也很好的体现了面向对象的一些基本原则。这里的Target角色和Adapter角色融合在一起,Adapter中的方法就是目标方法;而Adaptee角色就是ListView的数据集与Item View,Adapter代理数据集,从而获取到数据集的个数、元素。 >这里的Target角色就是View, Adapter角色就是将Item View输出为View抽象的角色, Adaptee就是需要被处理的Item View。 > 通过增加Adapter一层来将Item View的操作抽象起来,ListView等集合视图通过Adapter对象获得Item的个数、数据元素、Item View等,从而达到适配各种数据、各种Item视图的效果。因为Item View和数据类型千变万化,Android的架构师们将这些变化的部分交给用户来处理,通过getCount、getItem、getView等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。 > 虽然有些脱离Adapter模式将不兼容的接口转换为可用接口的使用场景,但也是Adapter模式的一种变种实现。 [ListView](https://www.androidos.net.cn/android/4.0.2_r1/xref/frameworks/base/core/java/android/widget/ListView.java)作为最重要的控件,它需要能够显示各式各样的视图( Item View ),每个人需要的显示效果各不相同,显示的数据类型、数量等也千变万化,那么如何应对这种变化成为架构师需要考虑的最重要特性之一。 ![](https://box.kancloud.cn/f5f6ebcd1d5af8d013a866c33df1be08_389x602.jpg) 从上图可知ListView是AbsListView的子类,AbsListView是AdapterView的子类实现。 Android 的做法是**增加一个Adapter层来隔离变化**,**将ListView需要的关于Item View接口抽象到Adapter对象中,并且在ListView内部调用Adapter这些接口完成布局等操作。这样只要用户实现了Adapter的接口,并且将该Adapter设置给ListView, ListView就可以按照用户设定的Ul 效果、数量、数据来显示每一项数据**。ListView最重要的问题是要解决每一项Item视图的输出, **Item View千变万化,但终究它都是View类型,Adapter统一将ItemView输出为View ,这样就很好地应对了Item View的可变性**。这虽然有些脱离Adapter模式将不兼容的接口转换为可用接口的使用场景,但也是Adapter模式的一种变种实现。 #### **ListView是如何通过Adapter 模式来实现应对千变万化的UI效果呢?** 发现在ListView中并没有Adapter相关的成员变量,其实Adapter在ListView 的父类[AbsListView](https://www.androidos.net.cn/android/4.0.2_r1/xref/frameworks/base/core/java/android/widget/AbsListView.java)中, AbsListView 是一个列表控件的抽象,我们看一看这个类。 **AbsListView.java** ~~~ package android.widget; public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher, ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener, ViewTreeObserver.OnTouchModeChangeListener, RemoteViewsAdapter.RemoteAdapterConnectionCallback { @SuppressWarnings("UnusedDeclaration") private static final String TAG = "AbsListView"; .............. ListAdapter mAdapter; .............. //子类需要覆写layoutChildren()函数来布局childview,也就是ItemView @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(); } // 布局 Child View 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); } } .............. // 关联到Window时调用世获取调用Adapter中的getCount方法等 @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; // 获取Item的数量,调用的是mAdapter的getCount方法 mItemCount = mAdapter.getCount(); } } ................ } ~~~ AbsListView 定义了**集合视图的逻辑框架**,比如**Adapter模式的应用、复用Item View 的逻辑、布局子视图的逻辑等**,子类只需要覆写特定的方法即可实现集合视图的功能。 首先**在AbsListView类型的View 中添加窗口( onAttachedToWindow 函数)时会调用Adapter 中的getCount函数获取到元素的个数,然后在onLayout函数中调用layoutChilden函数对所有子元素进行布局**。AbsListView中并没有实现layoutChilden 这个函数,具体的实现在子类中,这里我们分析ListView 中的实现,具体代码如下。 **ListView:layoutChildren()** ~~~ @Override protected void layoutChildren() { final boolean blockLayoutRequests = mBlockLayoutRequests; if (blockLayoutRequests) { return; } mBlockLayoutRequests = true; try { super.layoutChildren(); invalidate(); ........... //根据布局模式来布局ItemView 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: 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; } ............ } } ~~~ ListView 覆写了AbsListView 中的layoutChilden 函数,在该函数中**根据布局模式来布局Item View**,例如, **默认情况是从上到下开始布局, 但是,也有从下到上开始布局的**,例如QQ 聊天窗口的气泡布局,最新的消息就会布局到窗口的最底部。我们看看这两种实现。 **ListView:fillDown()** ~~~ //从上到下填充Item View [只是其中一种填充方式] 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) { boolean selected = pos == mSelectedPosition; //通过makeAndAddView 获取Item View 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; } ~~~ **ListView:fillUp()** ~~~ // 从下到上的布局 private View fillUp(int pos, int nextBottom) { View selectedView = null; int end = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end = mListPadding.top; } while (nextBottom > end && pos >= 0) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected); nextBottom = child.getTop() - mDividerHeight; if (selected) { selectedView = child; } pos--; } mFirstPosition = pos + 1; setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } ~~~ **在每一种布局方式的函数中都会从makeAndAddView 函数获取一个View , 这个View 就是ListView 的每一项的视图**,这里有一个**pos 参数**,也就是**对应这个View 是ListView 中的第几项**。我们看看makeAndAddView 中的实现。 **ListView:makeAndAddView()** ~~~ // 添加Item View 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; } } // ① 获取一个item View child = obtainView(position, mIsScrap); // ② 将item view 设置到对应的地方 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; } ~~~ 在makeAndAddView 函数中主要分为**两个步骤**,**第一是根据position 获取一个item View ,然后将这个View 布局到特定的位置。获取一个item View 调用的是obatinView 函数**, 这个函数在AbsListView 中。核心代码如下。 **AbsListView:obtainView()** ~~~ //获取一个Item View , 该函数在AbsListView中 View obtainView(int position, boolean[] isScrap) { isScrap[0] = false; View scrapView; // l.从缓存的Item View中获取,ListView的复用机制就在这里 scrapView = mRecycler.getScrapView(position); View child; if (scrapView != null) { if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP, position, -1); } // 2.注意,这里将scrapView设置给了Adapter的getView 函数 child = mAdapter.getView(position, scrapView, this); .............. } else { // 3.没有缓存View 的情况下getView的第二个参数为null child = mAdapter.getView(position, null, this); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (ViewDebug.TRACE_RECYCLER) { ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW, position, getChildCount()); } } return child; } ~~~ **obatinView 函数定义了列表控件的Item View 的复用逻辑**,**首先会从RecyclerBin 中获取一个缓存的View,如果有缓存则将这个缓存的View 传递到Adapter 的getView 第二个参数中**,这也就是我们**对Adapter 的最常见的优化方式**,即**判断getView 的convertView 是否为空,如果为空则从xml中创建视图,否则使用缓存的View。这样避免了每次都从xml 加载布局的消耗**,能够显著提升ListView 等列表控件的效率。通常我们自己的Adapter的实现如下。 ~~~ //解析、设置、缓存convertView 以及相关内容 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; //convertView为空,表示没有缓存,此时需要从xml 加载 if (convertView == null) { holder= new ViewHolder(); convertView = mInflater.inflate(R.layout.my_listview_item, null); //获取title holder.title= (TextView)convertView.findViewByid(R.id.title); convertView.setTag(holder); } else { // convertView不为空,这里的convertView就是从RecyclerBin取出的缓存视图 holder= (ViewHolder)convertView.getTag(); //数据绑定 return convertView; } ~~~ 通过这种缓存机制,即使有成千上万的数据项,ListView也能够流畅运行,因此,**只有填满一屏所需的Item View 存在内存中**。**ListView 根据Adapter 设置的数据项数量循环调用getView 方法获取视图(Item View),第一次加载填满屏幕的数据项时(没有缓存)getView的第二个参数convertView 都为空,此时每次都需要从xml中加载布局文件,填充数据之后返回给ListView 。当整屏的数据项加载完毕之后用户向下滚动屏幕,此时item1滚出屏幕,并且一个新的项目(ItemView)从屏幕低端上来时, ListView 再请求一个视图,此时item 1被缓存起来,在下一项数据加载时传递给getView 的第二个参数convertView ,因此, convertView此时不是空值,它的值是item1。此时只需设定新的数据然后返回convertView,这样就避免了每次都从xml加载、初始化视图,减少了时间、性能上的消耗**。原理如图所示。 :-: ![ListView缓存机制](https://box.kancloud.cn/ee6565519191fd5e89b2b78b4664b977_1101x667.jpg) ListView缓存机制 了解了它的工作原理后,就可以重复利用ListView 的Item View,只要convertView不为空就直接使用,改变它绑定的数据就行了。当然,由于视图被缓存了,视图中的数据也会被缓存,因此,你需要在每次获取到了Item view时对每个数据项重新赋值,否则会出现数据错误的现象。 #### **总结一下这个过程** ListView 等集合控件通过Adapter 来获取Item View的数量、布局、数据等,在这里最为**重要的就是getView函数**,这个函数**返回一个View类型的对象,也就是Item View**。 由于它**返回的是一个View 抽象**,而千变万化的UI视图都是View 的子类,**通过依赖抽象这个简单的原则和Adaper模式就将Item View的变化隔离了,保证了AbsListView 类族的高度可定制化。在获取了View之后,将这些View 通过特定的布局方式设置到对应的位置上,再加上Item View的复用机制,整个ListView就运转起来了。** 当然,**这里的Adapter并不是经典的适配器模式,却是对象适配器模式的优秀示例**,也很好地体现了面向对象的一些基本原则。这里的**Target角色就是View, Adapter角色就是将Item View输出为View抽象的角色, Adaptee就是需要被处理的Item View。通过增加Adapter一层来将Item View的操作抽象起来,ListView 等集合视图通过Adapter 对象获得Item的个数、数据、Item View 等,从而达到适配各种数据、各种Item视图的效果**。 因为Item View和数据类型千变万化, Android 的架构师们**将这些变化的部分交给用户来处理,通过getCount、getItem、getView 等几个方法抽象出来,也就是将Item View的构造过程交给用户来处理,灵活地运用了适配器模式,达到了无限适配、拥抱变化的目的。** #### **深度扩展之[RecyclerView](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java)** 虽然ListView已经足够强大,但还是有很多问题,比如:每次都需要创建一个ViewHolder、手动判断是否有缓存View等。对于用户而言,这些都可改进,因此出现了**RecyclerView**,顾名思义,这个View**代表的就是一个可循环使用的视图集合控件,它定义了ViewHolder类型标准,封装了View缓存判断机制,更强大的是它可以将一个RecyclerView显示为不同的样式,例如ListView、GridView形式,瀑布流形式。** :-: ![](https://box.kancloud.cn/355dff7bc7b6b1ae670b0a12d0cf80dc_370x603.jpg) RecyclerView列表视图 除了类似ListView的Adapter之外, RecyclerView还有一个**额外的设置就是设置布局方式**,这也是RecyclerView 能够在布局上比ListView更为灵活的原因。因为它**将布局通过桥接、组合的形式交由LayoutManager实现**,而不是像ListView 、GridView这些类自己实现,因为这会导致一个类只能有一种布局方式,当用户需要实现其他布局效果时就需要替换整个实现类。而**通过桥接模式和组合模式,将布局这个职责分离开来,使得一个RecyclerView与不同的LayoutManager搭配就能做成各种布局效果,这也是桥接模式的经典应用。** ~~~ //1.获取控件 RecyclerView mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview); //2.设置布局方式为线性布局 mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); mRecyclerView.setHasFixedSize(true); List<Article> articles= new ArrayList<Article>(); //3.设置Adapter ArticleAdapter mAdapter = r1ew ArticleAdapter (articles); mRecyclerView.setAdapter(mAdapter); ~~~ 上述代码描述的是一个普通的初始化过程,唯一与ListView 不太相同的是第二步中设置了**LayoutManager**,这个LayoutManager的职责就是**负责RecyclerView元素的布局。默认的实现有LinearLayoutManager 、GridLayoutManager、StaggeredGridLayoutManager,分别是线性布局、网格布局和瀑布流布局**。这些布局不仅可以竖向布局,还支持横向布局,这几个布局已经能够基本满足我们的需求了,可见RecylerView 的高度定制化能力。 RecyclerView是在Android 5.0(API 21)才引入的V7包中的一个控件,可以查看官方的Android 5.0变更,也可以查看[这里整理的译文](https://www.kancloud.cn/alex_wsc/android_google/488254),从ViewGroup这张层次结构图,可以看出RecyclerView继承于ViewGroup。 下面看看文章Adapter 的实现。(**Adapter是指这个`RecyclerView.Adapter<VH extends ViewHolder>`**) ~~~ public class ArticleAdapter extends Adapter<ViewHolder> { List<Article> mArticles;//文章数据 OnItemClickListener<Article> mClickListener;//每一项的点击事件 public ArticleAdapter (List<Article> dataset) { mArticles = dataset; } //绑定每一项的数据 @Override public void onBindViewHolder (ViewHolder viewHolder, int position) { if(viewHolder instanceof ArticleViewHolder) { bindViewForArticle (viewHolder,position); } } protected void bindViewForArticle (ViewHolder viewHolder,int position) { ArticleViewHolder articleViewHolder = (ArticleViewHolder) viewHolder; fina1 Article article = getItem(position); articleViewHolder.titleTv.setText(article.title); articleViewHolcter.publishTimeTv.setText(article.publishTime); articleViewHolder.authorTv.setText(article.author); //设置点击事件 articleViewHolder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick (View v) { if (mClickListener != null ) { mClickListener.onClick(article); } } }); } @Override public int getitemCount () { return mArticles.size(); } //创建ViewHolder @Override public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup viewGroup, int viewType) { return createArticleViewHolder(viewGroup); } protected ViewHolder createArticleViewHolder (ViewGroup viewGroup) { View itemView = Layoutinflater.from(viewGroup.getContext()).inflate( R.layout.recyclerview_article_item,viewGroup , false ); return new ArticleViewHolder(itemView); } protected Article getItem(int position) { return mArticles.get(position); } public void setOnItemClickListener (OnItemClickListener<Article> mClickListener) { this.mClickListener = mClickListener; } //ViewHolder ,负责保持Item View static class ArticleViewHolder extends RecyclerView.ViewHolder { public TextView titleTv; public TextView publishTimeTv; public TextView authorTv; public ArticleViewHolder (View itemView) { super (itemView); titleTv = (TextView) itemView.findViewById(R.id.article_title_tv); publishTimeTv = (TextView) itemView.findViewById(R.id.article_time_tv); authorTv = (TextView) itemView.findViewById(R.id.article_author_tv); } } } ~~~ 在RecyclerView 中没有了getView函数,取而代之的是**onBindViewHolder和onCreateViewHolder,这两个函数(在RecyclerView的内部类`Adapter<VH extends ViewHolder>`中)就相当于getView功能**。 **RecyclerView.Adapter** ~~~ public class RecyclerView extends ViewGroup { ......... public static abstract class Adapter<VH extends ViewHolder> { ........... public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); public abstract void onBindViewHolder(VH holder, int position); .......... } ........... } ~~~ **首先会从onCreateViewHolder中获取ViewHolder对象,ViewHolder 中又含有Item View 的引用,获取到ViewHolder 之后又会通过onBindViewHolder 函数来绑定数据**,这两个过程**实际上是将getView函数分解开来,使得加载视图和绑定数据分离开来**。在**RecyclerView中的操作单位也不再是View,而是ViewHolder**。 Android中定义了一个[Adapter基类](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java),该类的一个泛型参数(**VH**)就是ViewHolder,**用户需要继承ViewHolder实现自己的ViewHolder,对于item view 的操作都从这个ViewMolder对象进行。通过onBindViewHolder和onCreateViewHolder两个函数,Android屏蔽了对Item View缓存的手动判断,将这部分逻辑封装在RecyclerView中,这样就不需要像ListView中的Adapter的getView函数那样对convertView进行判空等操作,简化的逻辑,隐藏了具体实现**。 **最后将数据设置给Adapter,再将Adapter设置给RecyclerView, RecyclerView再根据用户设置的LayoutManager 将每个数据项布局到对应的位置,这样一来整个列表控件就显示在我们眼前了**。 **总结**:Android通过onBindViewHolder和onCreateViewHolder两个函数屏蔽了一些固定的逻辑,使得Adapter的使用更为简单,又通过LayoutManager实现具体的布局,使得RecyclerView具有更强大的定制能力,这就是RecyclerView比ListView更强大的原因所在。 如果我们看腻了上图所示的单向列表效果,想要换成每行3 篇文章的网格形式的布局,此时只需要将LieanrLayoutManager 替换为一个GridLayouManager 即可。具体代码如下。 ~~~ //1.获取控件,代码省略 //2.设置布局方式为网格布局 mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3)); mRecyclerView.setHasFixedSize(true); List<Article> articles = new ArrayList<Article>(); //3 . 设置Adapter ~~~ :-: ![](https://box.kancloud.cn/8b811ad7a73a62e208dc7907254f4b05_372x610.jpg) RecyclerView网格视图 又如果你觉得高度一样的网格布局还是不好看, 你想要高度不规则的瀑布流布局, 那么此时需要做的同样也很简单。修改LayoutManager为 StaggeredGridLayoutManager,然后在 Adapter 中为每个Item 设置一个高度即可。具体代码如下 ~~~ //1.获取控件,代码省略 //2.设置布局方式为瀑布流布局 mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3 ,StaggeredGridLayoutManager.VERTICAL)); mRecyclerView.setHasFixedSize(true); List<Article> articles = new ArrayList<Article>(); //3 . 设置Adapter ~~~ 还需要做的是在Adapter 中设置每个Item 的高度, 例如。 ~~~ @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { //代码省略 ArticleViewHolder articleViewHoider = (ArticleViewHolder) viewHolder; LayoutParams params = articleViewHolder.itemView.getLayoutParams() //随机生成一个高度 params.height = new Random().nextint(lO)*50 + 100; //设置属性 articleViewHolder.itemView.setLayoutParams(params); } ~~~ :-: ![](https://box.kancloud.cn/6bce256497adb064d11e8cbca45526bb_362x602.jpg) RecyclerView瀑布流格局 #### **RecyclerView存在的缺陷** * **缺陷一:没有设置处理条目Item 点击事件的Listener接口** RecyclerView 也有几个小问题需要注意, 第一个是它没有设置处理Item 点击事件的Listener 接口,例如ListView 可以通过setOnltemClickListener,但是, 在RecyclerView 中并没有这个接口。如果要给Item设置点击事件,那么需要通过Adapter 实现,通常的做法是定义一个点击接口,然后设置给Adapter,最后在这个View 的点击事件中触发这个类似于OnltemCiickListener 的回调函数。示例程序如下。 ~~~ public class ArticleAdapter extends Adapter<ViewHolder> { private OnItemClickListener<Article> ClickListener; @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { //代码省略 ArticleViewHolder articleViewHolder = (ArticleViewHolder) viewHolder; //设置itemView的点击事件,并且在里面调用OnItemClickListener的onClick方法 articleViewHolder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mClickListener != null) { mClickListener.onClick(article); } } }); public void setOnitemClickListener (OnItemClick.Listener<Article> listener) { mClickListener = listener; } //定义点击接口 public static interface OnitemClickListener<T> { public void onClick (T item); } } } ~~~ * **缺陷二:没有可以设置Header 和footer 的接口** 第二个问题是, RecyclerView 没有可以设置Header 和footer 的接口,当需要实现HeaderView时,需要根据viewType 进行分类处理。例如当position 为0 时,你把viewType设置为0 ,其他位置的Item 的viewType 则为1 ,也就是说第一个item 是Header,其他的是普通的Item 。因此,需要在position 为0 时返回HeaderView 的ViewHolder,而在其他位置时返回普通item 的ViewHolder。并且需要注意的是, item count 需要比数据集的数量多1 ,这个1 就是这个Header View ,另外,从 数据集取数据时则需要把position 减1 。因为第一个View 为Header, Header 通常情况下并不使用数据集中的数据,也就是说第一个item 不需要数据,需要数据的是第二个Item View,而此时它显示的数据应该是数据集中的第一个,因此取数据时需要把索引减1 。示例代码如下。 ~~~ public class ArticleWithHeaderAdapter extends Adapter<ViewHolder> { private static final int HEADER TYPE = 0 ; //字段省略 //分类进行设置 @Override public void onBindViewHolder(ViewHolder viewHolder, int position) { if (viewHolder instanceof ArticleViewHolder) { //设置普通的Item View } else if (viewHolder instanceof HeaderViewHolder) { //设置Header View } } //根据分类创建对应的ViewHolder @Override public RecyclerView.ViewHolder onCreateViewHolder (ViewGroup viewGroup, int viewType){ if (viewType == HEADER_TYPE) { return createHeaderViewHolder(viewGroup); } return createArticleViewHolder(viewGroup) ; } private HeaderViewHolder createHeaderViewHolder(ViewGroup viewGroup) { View headerView = Layoutinflater.from( viewGroup.getContext()).inflate(R.layout.auto_slider,viewGroup,false); return new HeaderViewHolder(headerView); } /** *多了一个Header,因此数量要加1 */ @Override public int getItemCount() { return mArticles ==null ? 1 : mArticles.size() + l ; } @Override protected Article getItem (int position) { //因为第一项视图是Header ,添加了一个Heade主,因此数据索引要减去1 return mArticles.get(position - l) ; } //设置viewType @Override public int getItemViewType(int position) { if (HEADER_TYPE == position) { return 0 ; } return 1 ; } static class ArticleViewHolder extends RecyclerView.ViewHolder { //字段省略 public HeaderViewHolder(View view) { super (view); //代码省略 } } static class HeaderViewHolder extends RecyclerView.ViewHolder { View headerView; public HeaderViewHolder(View view) { super(view); //代码省略 } } } ~~~ 上述代码中有一个**getItemViewType 函数**,在该函数中**把第一个item 的viewType 设置为0,其他位置的设置为1** 。**这样在onCreateViewHolder 函数中就可以根据不同的viewType ( onCreateViewHolder的第二个参数,不是position ,而是viewType )来创建不同的ViewHolder,不同的ViewHolder 含有不同的View,因此达到了不同显示效果的目的**。上文中的开发技术前线网站的第一项就是一个Header View,这个Header View是一个自动滚动的ViewPager,其他的都是普通的文本控件的组合视图。 #### **参考文章:** [Android源码之ListView的适配器模式](http://blog.csdn.net/bboyfeiyu/article/details/43950185)