ListView之多种类型Item
Posted Snser
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ListView之多种类型Item相关的知识,希望对你有一定的参考价值。
一、概述
一般而言,listview每个item的样式是一样的,但也有很多应用场景下不同位置的item需要不同的样式。
拿微信举例,前者的代表作是消息列表,而后者的典型则是聊天会话界面。
本文重点介绍后者,也就是多类型item的listview的实现思路和方法,比如实现一个这样的聊天会话页面:
二、实现思路
2.1 第一种思路:用“一种类型”变相实现多种类型
这种思路其实与 ListView之点击展开菜单 这篇文章的原理一样,每个item的布局都包含所有类型的元素:
对于每个item,根据实际类型,控制“日期”、“发出的消息”、“接收的消息”这三部分的显示/隐藏即可。
这种思路的优势在于好理解,是单一类型的listview的扩展,却并不适合本文描述的应用场景。
因为每个item实际上只会显示“日期”、“发出的消息”、“接收的消息”中的一种,所以每个item都inflate出来一个“全家桶”layout再隐藏其中的两个,实在是一种资源浪费。
2.2 第二种思路:利用Adapter原生支持的多类型
其实 android.widget.Adapter 类已经原生支持了多种类型item的模式,并提供了 int getViewTypeCount(); 和 int getItemViewType(int position); 两个方法。
只不过在 android.widget.BaseAdapter 中对这两个方法进行了如下的默认实现:
1 public int getViewTypeCount() { 2 return 1; 3 } 4 5 public int getItemViewType(int position) { 6 return 0; 7 }
那我们要做的就是根据实际的数据,对这两个方法进行正确的返回。
本文采用第二种思路实现多种类型item的listview。
[转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html]
三、开始干活
3.1 首先准备好listview的数据和三种item布局
ListViewMultiTypeActivity$JsonListData:
1 private static class JsonListData { 2 public static class Message { 3 public static final int TYPE_COUNT = 3; 4 public static final int TYPE_DATE = 0x00; 5 public static final int TYPE_TXT_SENT = 0x01; 6 public static final int TYPE_TXT_RECV = 0x02; 7 public int type; 8 public String txt; 9 public long time; 10 } 11 public List<Message> messages = new ArrayList<Message>(); 12 }
listview_multitype_data.json:
{ "messages": [ { "type": 0, "time": 1467284175 }, { "type": 1, "txt": "你好" }, { "type": 2, "txt": "你才好" }, { "type": 1, "txt": "对话,指两个或更多的人用语言交谈,多指小说或戏剧里的人物之间的" }, { "type": 2, "txt": "京东童书节低至300减180" }, { "type": 1, "txt": "http://www.cnblogs.com/snser/" }, { "type": 2, "txt": "京东商城目前已成长为中国最大的自营式电商企业,2015年第三季度在中国自营式B2C电商市场的占有率为56.9%。" }, { "type": 0, "time": 1467289175 }, { "type": 1, "txt": "京东金融现已建立七大业务板块,分别是供应链金融、消费金融、众筹、财富管理、支付、保险、证券,陆续推出了京保贝、白条、京东钱包、小金库、京小贷、产品众筹、私募股权融资、小白理财等创新产品" }, { "type": 2, "txt": "您目前没有新消息" }, { "type": 2, "txt": "黑炎凝聚,竟是直接化为了一头仰天长啸的黑色巨鸟,而后它仿佛是发现了牧尘飘荡的意识,化为一道黑色火焰,眼芒凶狠的对着他的意识暴冲而来" }, { "type": 0, "time": 1467294175 }, { "type": 2, "txt": "国务院罕见派出民间投资督查组:活力不够形势严峻" }, { "type": 1, "txt": "那一道清鸣,并不算太过的响亮,但却是让得牧尘如遭雷击,整个身体都是僵硬了下来,脑子里回荡着嗡嗡的声音。" }, { "type": 2, "txt": "据海关统计,今年前4个月,我国进出口总值7.17万亿元人民币,比去年同期(下同)下降4.4%。其中,出口4.14万亿元,下降2.1%;进口3.03万亿元,下降7.5%;贸易顺差1.11万亿元,扩大16.5%。" }, { "type": 1, "txt": "在介绍算法的时空复杂度分析方法前,我们先来介绍以下如何来量化算法的实际运行性能,这里我们选取的衡量算法性能的量化指标是它的实际运行时间。" }, { "type": 2, "txt": "你拍一" }, { "type": 2, "txt": "我拍一" }, { "type": 1, "txt": "一二三四五六七" } ] }
ListViewMultiTypeActivity.onCreate:
1 protected void onCreate(Bundle savedInstanceState) { 2 super.onCreate(savedInstanceState); 3 setContentView(R.layout.listview_multi_type); 4 5 JsonListData data = null; 6 try { 7 InputStream is = getResources().getAssets().open("listview_multitype_data.json"); 8 InputStreamReader isr = new InputStreamReader(is); 9 Gson gson = new GsonBuilder().serializeNulls().create(); 10 data = gson.fromJson(isr, JsonListData.class); 11 } catch (Exception e) { 12 e.printStackTrace(); 13 } 14 15 if (data != null && data.messages != null) { 16 mList = (ListView)findViewById(R.id.listview_multi_type_list); 17 mList.setAdapter(new MultiTypeAdapter(ListViewMultiTypeActivity.this, data.messages)); 18 } 19 }
listview_multi_type_item_date.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#EEEEEE" 6 android:orientation="vertical" 7 tools:context="${relativePackage}.${activityClass}" > 8 9 <TextView 10 android:id="@+id/listview_multi_type_item_date_txt" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:layout_gravity="center_horizontal" 14 android:layout_margin="6dp" 15 android:padding="3dp" 16 android:background="#CCCCCC" 17 android:textColor="@android:color/white" 18 android:textSize="12sp" 19 android:text="2015年3月25日 18:44" /> 20 21 </LinearLayout>
listview_multi_type_item_txt_sent.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#EEEEEE" 6 android:orientation="vertical" 7 tools:context="${relativePackage}.${activityClass}" > 8 9 <TextView 10 android:id="@+id/listview_multi_type_item_txt_sent_txt" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:maxWidth="250dp" 14 android:layout_gravity="right" 15 android:layout_margin="4dp" 16 android:paddingTop="5dp" 17 android:paddingBottom="5dp" 18 android:paddingRight="10dp" 19 android:paddingLeft="5dp" 20 android:background="@drawable/listview_multi_type_item_txt_sent_bg" 21 android:textColor="@android:color/black" 22 android:textSize="13sp" 23 android:text="发出的消息" 24 android:autoLink="web" /> 25 26 </LinearLayout>
listview_multi_type_item_txt_recv.xml:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#EEEEEE" 6 android:orientation="vertical" 7 tools:context="${relativePackage}.${activityClass}" > 8 9 <TextView 10 android:id="@+id/listview_multi_type_item_txt_recv_txt" 11 android:layout_width="wrap_content" 12 android:layout_height="wrap_content" 13 android:maxWidth="250dp" 14 android:layout_gravity="left" 15 android:layout_margin="4dp" 16 android:paddingTop="5dp" 17 android:paddingBottom="5dp" 18 android:paddingRight="5dp" 19 android:paddingLeft="10dp" 20 android:background="@drawable/listview_multi_type_item_txt_recv_bg" 21 android:textColor="@android:color/black" 22 android:textSize="13sp" 23 android:text="接收的消息" 24 android:autoLink="web" /> 25 26 </LinearLayout>
3.2 重头戏在于Adapter的处理
1 private class MultiTypeAdapter extends BaseAdapter { 2 private LayoutInflater mInflater; 3 private List<JsonListData.Message> mMessages; 4 private SimpleDateFormat mSdfDate = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss", Locale.getDefault()); 5 6 public MultiTypeAdapter(Context context, List<JsonListData.Message> messages) { 7 mInflater = LayoutInflater.from(context); 8 mMessages = messages; 9 } 10 11 private class DateViewHolder { 12 public DateViewHolder(View viewRoot) { 13 date = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_date_txt); 14 } 15 public TextView date; 16 } 17 18 private class TxtSentViewHolder { 19 public TxtSentViewHolder(View viewRoot) { 20 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_sent_txt); 21 } 22 public TextView txt; 23 } 24 25 private class TxtRecvViewHolder { 26 public TxtRecvViewHolder(View viewRoot) { 27 txt = (TextView)viewRoot.findViewById(R.id.listview_multi_type_item_txt_recv_txt); 28 } 29 public TextView txt; 30 } 31 32 @Override 33 public int getViewTypeCount() { 34 return JsonListData.Message.TYPE_COUNT; 35 } 36 37 @Override 38 public int getItemViewType(int position) { 39 return getItem(position).type; 40 } 41 42 @Override 43 public int getCount() { 44 return mMessages.size(); 45 } 46 47 @Override 48 public JsonListData.Message getItem(int position) { 49 return mMessages.get(position); 50 } 51 52 @Override 53 public long getItemId(int position) { 54 return position; 55 } 56 57 @Override 58 public View getView(int position, View convertView, ViewGroup parent) { 59 switch (getItemViewType(position)) { 60 case JsonListData.Message.TYPE_DATE: 61 return handleGetDateView(position, convertView, parent); 62 case JsonListData.Message.TYPE_TXT_SENT: 63 return handleGetTxtSentView(position, convertView, parent); 64 case JsonListData.Message.TYPE_TXT_RECV: 65 return handleGetTxtRecvView(position, convertView, parent); 66 default: 67 return null; 68 } 69 } 70 71 private View handleGetDateView(int position, View convertView, ViewGroup parent) { 72 if (convertView == null) { 73 convertView = mInflater.inflate(R.layout.listview_multi_type_item_date, parent, false); 74 convertView.setTag(new DateViewHolder(convertView)); 75 } 76 if (convertView != null && convertView.getTag() instanceof DateViewHolder) { 77 final DateViewHolder holder = (DateViewHolder)convertView.getTag(); 78 holder.date.setText(formatTime(getItem(position).time)); 79 } 80 return convertView; 81 } 82 83 private View handleGetTxtSentView(int position, View convertView, ViewGroup parent) { 84 if (convertView == null) { 85 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_sent, parent, false); 86 convertView.setTag(new TxtSentViewHolder(convertView)); 87 } 88 if (convertView != null && convertView.getTag() instanceof TxtSentViewHolder) { 89 final TxtSentViewHolder holder = (TxtSentViewHolder)convertView.getTag(); 90 holder.txt.setText(getItem(position).txt); 91 } 92 return convertView; 93 } 94 95 private View handleGetTxtRecvView(int position, View convertView, ViewGroup parent) { 96 if (convertView == null) { 97 convertView = mInflater.inflate(R.layout.listview_multi_type_item_txt_recv, parent, false); 98 convertView.setTag(new TxtRecvViewHolder(convertView)); 99 } 100 if (convertView != null && convertView.getTag() instanceof TxtRecvViewHolder) { 101 final TxtRecvViewHolder holder = (TxtRecvViewHolder)convertView.getTag(); 102 holder.txt.setText(getItem(position).txt); 103 } 104 return convertView; 105 } 106 107 private String formatTime(long time) { 108 return mSdfDate.format(new Date(time * 1000)); 109 } 110 }
可以看到, 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并没有本质区别,却也能正常的工作。
[转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.html]
四、demo工程
保存下面的图片,扩展名改成 .zip 即可
[转载请保留本文地址:http://www.cnblogs.com/snser/p/5539749.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中对应都做了什么:
1 public void setViewTypeCount(int viewTypeCount) { 2 if (viewTypeCount < 1) { 3 throw new IllegalArgumentException("Can\'t have a viewTypeCount < 1"); 4 } 5 //noinspection unchecked 6 ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount]; 7 for (int i = 0; i < viewTypeCount; i++) { 8 scrapViews[i] = new ArrayList<View>(); 9 } 10 mViewTypeCount = viewTypeCount; 11 mCurrentScrap = scrapViews[0]; 12 mScrapViews = scrapViews; 13 }
看源码,说白了就是创建了一个大小为 getViewTypeCount() 的数组 mScrapViews ,从而为每种类型的view维护了一个回收站,此外每种类型的回收站自身又是一个View数组。
这也就解释了为什么ViewType必须在 [0, getViewTypeCount() - 1] 范围内。
5.2 View回收站的构建和维护
AbsListView在滑动时,会调用 trackMotionScroll 方法:
1 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { 2 //... 3 final boolean down = incrementalDeltaY < 0; 4 //... 5 if (down) { 6 int top = -incrementalDeltaY; 7 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 8 top += listPadding.top; 9 } 10 for (int i = 0; i < childCount; i++) { 11 final View child = getChildAt(i); 12 if (child.getBottom() >= top) { 13 break; 14 } else { 15 count++; 16 int position = firstPosition + i; 17 if (position >= headerViewsCount && position < footerViewsStart) { 18 // The view will be rebound to new data, clear any 19 // system-managed transient state. 20 if (child.isAccessibilityFocused()) { 21 child.clearAccessibilityFocus(); 22以上是关于ListView之多种类型Item的主要内容,如果未能解决你的问题,请参考以下文章