ListView之多种类型Item
2016-07-01 17:10
435 查看
一、概述
一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。
拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。
本文重点介绍后者,也就是多类型item的listview的实现思路和方法,比如实现一个这样的聊天会话页面:
View Code
3.2 重头戏在于Adapter的处理
可以看到, int getViewTypeCount(); 和 int getItemViewType(int position); 的处理是非常清晰的。
需要注意的在于,ViewType必须在 [0, getViewTypeCount() - 1] 范围内。
3.3 ViewHolder为何能正确的工作
回顾一下单一类型的listview,其ViewHolder的工作机制在于系统会将滑出屏幕的item的view回收起来,并作为getView的第二个参数 convertView 传入。
那么,在多种类型的listview中,滑出屏幕的view与即将滑入屏幕的view类型很可能是不同的,那这么直接用不就挂了吗?
其实不然,android针对多种类型item的情况已经做好处理了,如果getView传入的 convertView 不为null,那它一定与当前item的view类型是匹配的。
所以,在3.2节中对ViewHolder的处理方式与单类型的listview并没有本质区别,却也能正常的工作。
[转载请保留本文地址:/article/11872795.html]
四、demo工程
保存下面的图片,扩展名改成 .zip 即可
[转载请保留本文地址:/article/11872795.html]
五、番外篇 —— ListView回收机制简要剖析
在3.3节中简单介绍了android系统会处理好多类型item的回收和重用,那具体是怎么实现的呢?
下面简要剖析一下支持多种类型item的listview中,View回收的工作机制。
5.1 View回收站的初始化
ListView的父类AbsListView中定义了一个内部类RecycleBin,这个类维护了listview滑动过程中,view的回收和重用。
在ListView的 setAdapter 方法中,会通过调用 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()) 来初始化RecycleBin。
让我们看下RecycleBin中对应都做了什么:
看源码,说白了就是创建了一个大小为 getViewTypeCount() 的数组 mScrapViews ,从而为每种类型的view维护了一个回收站,此外每种类型的回收站自身又是一个View数组。
这也就解释了为什么ViewType必须在 [0, getViewTypeCount() - 1] 范围内。
5.2 View回收站的构建和维护
AbsListView在滑动时,会调用 trackMotionScroll 方法:
在 trackMotionScroll 方法中,会根据不同的滑动方向,调用 addScrapView ,将滑出屏幕的view加到RecycleBin中:
在 addScrapView 方法中,被回收的view会根据其类型加入 mScrapViews 中。
特别的,如果这个view处于TransientState(瞬态,view正在播放动画或其他情况),则会被存入 mTransientStateViewsById 、 mTransientStateViews 。
5.3 从View回收站获取View
Adapter的getView方法在AbsListView的 obtainView 中被调用:
可以看到,对于不处于TransientState的View,将会尝试通过 getScrapView 方法获取回收的View,如果有,就会作为参数传入Adatper的getView方法中。
而 getScrapView 方法,其实就是先调用Adapter的 getItemViewType 方法取position对应的view类型,然后从 mScrapViews 中根据类型取view。
至此,我们简要了解了多类型的listview中,是如何在滑动屏幕时回收view并进行重用的。
而如何维护每个类型item对应的View数组,以及TransientState的维护,本篇文章就不做详细介绍了,有兴趣的读者可以着重研究一下AbsListView的源码。
[转载请保留本文地址:/article/11872795.html]
一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。
拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。
本文重点介绍后者,也就是多类型item的listview的实现思路和方法,比如实现一个这样的聊天会话页面:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#EEEEEE" android:orientation="vertical" tools:context="${relativePackage}.${activityClass}" > <TextView android:id="@+id/listview_multi_type_item_txt_recv_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:maxWidth="250dp" android:layout_gravity="left" android:layout_margin="4dp" android:paddingTop="5dp" android:paddingBottom="5dp" android:paddingRight="5dp" android:paddingLeft="10dp" android:background="@drawable/listview_multi_type_item_txt_recv_bg" android:textColor="@android:color/black" android:textSize="13sp" android:text="接收的消息" android:autoLink="web" /> </LinearLayout>
View Code
3.2 重头戏在于Adapter的处理
private class MultiTypeAdapter extends BaseAdapter { private LayoutInflater mInflater; private List<JsonListData.Message> mMessages; private SimpleDateFormat mSdfDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault()); public MultiTypeAdapter(Context context, List<JsonListData.Message> messages) { mInflater = LayoutInflater.from(context); mMessages = messages; } private class DateViewHolder { public DateViewHolder(View viewRoot) { date = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_date_txt); } public TextView date; } private class TxtSentViewHolder { public TxtSentViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_sent_txt); } public TextView txt; } private class TxtRecvViewHolder { public TxtRecvViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_recv_txt); } public TextView txt; } @Override public int getViewTypeCount() { return JsonListData.Message.TYPE_COUNT; } @Override public int getItemViewType(int position) { return getItem(position).type; } @Override public int getCount() { return mMessages.size(); } @Override public JsonListData.Message getItem(int position) { return mMessages.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { switch (getItemViewType(position)) { case JsonListData.Message.TYPE_DATE: return handleGetDateView(position, convertView, parent); case JsonListData.Message.TYPE_TXT_SENT: return handleGetTxtSentView(position, convertView, parent); case JsonListData.Message.TYPE_TXT_RECV: return handleGetTxtRecvView(position, convertView, parent); default: return null; } } private View handleGetDateView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.listview_multi_type_item_date, parent, false); convertView.setTag(new DateViewHolder(convertView)); } if (convertView != null && convertView.getTag() instanceof DateViewHolder) { final DateViewHolder holder = (DateViewHolder)convertView.getTag(); holder.date.setText(formatTime(getItem(position).time)); } return convertView; } private View handleGetTxtSentView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_sent, parent, false); convertView.setTag(new TxtSentViewHolder(convertView)); } if (convertView != null && convertView.getTag() instanceof TxtSentViewHolder) { final TxtSentViewHolder holder = (TxtSentViewHolder)convertView.getTag(); holder.txt.setText(getItem(position).txt); } return convertView; } private View handleGetTxtRecvView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_recv, parent, false); convertView.setTag(new TxtRecvViewHolder(convertView)); } if (convertView != null && convertView.getTag() instanceof TxtRecvViewHolder) { final TxtRecvViewHolder holder = (TxtRecvViewHolder)convertView.getTag(); holder.txt.setText(getItem(position).txt); } return convertView; } private String formatTime(long time) { return mSdfDate.format(new Date(time * 1000)); } }
可以看到, int getViewTypeCount(); 和 int getItemViewType(int position); 的处理是非常清晰的。
需要注意的在于,ViewType必须在 [0, getViewTypeCount() - 1] 范围内。
3.3 ViewHolder为何能正确的工作
回顾一下单一类型的listview,其ViewHolder的工作机制在于系统会将滑出屏幕的item的view回收起来,并作为getView的第二个参数 convertView 传入。
那么,在多种类型的listview中,滑出屏幕的view与即将滑入屏幕的view类型很可能是不同的,那这么直接用不就挂了吗?
其实不然,android针对多种类型item的情况已经做好处理了,如果getView传入的 convertView 不为null,那它一定与当前item的view类型是匹配的。
所以,在3.2节中对ViewHolder的处理方式与单类型的listview并没有本质区别,却也能正常的工作。
[转载请保留本文地址:/article/11872795.html]
四、demo工程
保存下面的图片,扩展名改成 .zip 即可
[转载请保留本文地址:/article/11872795.html]
五、番外篇 —— ListView回收机制简要剖析
在3.3节中简单介绍了android系统会处理好多类型item的回收和重用,那具体是怎么实现的呢?
下面简要剖析一下支持多种类型item的listview中,View回收的工作机制。
5.1 View回收站的初始化
ListView的父类AbsListView中定义了一个内部类RecycleBin,这个类维护了listview滑动过程中,view的回收和重用。
在ListView的 setAdapter 方法中,会通过调用 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()) 来初始化RecycleBin。
让我们看下RecycleBin中对应都做了什么:
public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } //noinspection unchecked ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList<View>(); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; }
看源码,说白了就是创建了一个大小为 getViewTypeCount() 的数组 mScrapViews ,从而为每种类型的view维护了一个回收站,此外每种类型的回收站自身又是一个View数组。
这也就解释了为什么ViewType必须在 [0, getViewTypeCount() - 1] 范围内。
5.2 View回收站的构建和维护
AbsListView在滑动时,会调用 trackMotionScroll 方法:
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { //... final boolean down = incrementalDeltaY < 0; //... if (down) { int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } mRecycler.addScrapView(child, position); } } } } else { int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } mRecycler.addScrapView(child, position); } } } } //... }
在 trackMotionScroll 方法中,会根据不同的滑动方向,调用 addScrapView ,将滑出屏幕的view加到RecycleBin中:
void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { return; } lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(position, scrap); } else { // Otherwise, we'll have to remove the view and start over. if (mSkippedScrap == null) { mSkippedScrap = new ArrayList<View>(); } mSkippedScrap.add(scrap); } } else { if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } // Clear any system-managed transient state. if (scrap.isAccessibilityFocused()) { scrap.clearAccessibilityFocus(); } scrap.setAccessibilityDelegate(null); if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }
在 addScrapView 方法中,被回收的view会根据其类型加入 mScrapViews 中。
特别的,如果这个view处于TransientState(瞬态,view正在播放动画或其他情况),则会被存入 mTransientStateViewsById 、 mTransientStateViews 。
5.3 从View回收站获取View
Adapter的getView方法在AbsListView的 obtainView 中被调用:
View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; View scrapView; scrapView = mRecycler.getTransientStateView(position); if (scrapView == null) { scrapView = mRecycler.getScrapView(position); } View child; if (scrapView != null) { child = mAdapter.getView(position, scrapView, this); if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (child != scrapView) { mRecycler.addScrapView(scrapView, position); if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } else { isScrap[0] = true; // Clear any system-managed transient state so that we can // recycle this view and bind it to different data. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } child.dispatchFinishTemporaryDetach(); } } else { child = mAdapter.getView(position, null, this); if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } } //... return child; }
可以看到,对于不处于TransientState的View,将会尝试通过 getScrapView 方法获取回收的View,如果有,就会作为参数传入Adatper的getView方法中。
而 getScrapView 方法,其实就是先调用Adapter的 getItemViewType 方法取position对应的view类型,然后从 mScrapViews 中根据类型取view。
View getScrapView(int position) { if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); } else { int whichScrap = mAdapter.getItemViewType(position); if (whichScrap >= 0 && whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } } return null; }
至此,我们简要了解了多类型的listview中,是如何在滑动屏幕时回收view并进行重用的。
而如何维护每个类型item对应的View数组,以及TransientState的维护,本篇文章就不做详细介绍了,有兴趣的读者可以着重研究一下AbsListView的源码。
[转载请保留本文地址:/article/11872795.html]
相关文章推荐
- js页面跳转的方法
- 基于RecyclerView的封装,仿qq侧拉删除效果,实现下拉刷新,上拉加载更多,添加header,添加footer
- Android屏幕知识点
- Java解析Excel(JXL,仅支持Excel2003)
- Poj 1456 Supermarket
- xshell远程终端实用设置
- java一次多选的多图片异步上传
- iOS开发——单元测试
- 编写一个程序实现链式栈的各种基本运算(假设顺序表的元数基本类型为Char)
- Previous operation has not finished,SVN cleanup出错解决办法
- 电脑装系统
- java设计模式:观察者模式
- JOGL FirstDemo
- 操作服务器常用命令
- RecyclerView显示不同的类型的item
- 数据库错误5123修复
- mysql数据库大数据量的查询优化和分页测试
- c#向SQL Server中存储图片并且再从数据库中读取图片
- mysql 5.7.13 winx32 安装步骤
- 问题集