ListView加载不同Item布局

看到这个标题首先想到的可能就是聊天页面了,当然,使用最多的估计也是它。我们知道,在聊天的过程中,收到消息和发送的消息的布局是不一样的,这就要求我们需要对接受和发送设置不同的Item布局。和单一布局不同的是,多布局如果使用单一布局的处理方式,那么将一定会出现复用问题,不是消息内容的复用,而是布局的复用。

实现

下面我们通过迭代式的开发,来一步一步完成我们的需求。首先我们来看一下处理普通ListView的方式,伪代码如下:
PS:这里我们只贴一下getView()的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder myHolder = null;
if (convertView == null) {
convertView = getLayoutInflater().from(context).inflate(R.layout.item_layout, parent, false);
myHolder.childView = (ViewType)convertView.findViewById(R.id.xx);
myHolder.childView2 = ...
... 获取所有的子View...
convertView.setTag(myHolder);
} else {
myHolder = (MyViewHolder)convertView.getTag();
}

// 设置Item中的View的内容
...

return convertView;
}

上面的代码基本上是实现getView()的一个模板,在单一布局的情况下,这中处理方式基本都是通用的,但是我们需要加载的是不同的布局。那么就需要进行一番修改,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder myHolder = null;
if (convertView == null) {
// 根据数据的类型加载不同的字段
int type = ListData.get(position).type;
if (type == MSG_TYPE_RECEIVE) {
convertView = getLayoutInflater().from(context).inflate(R.layout.item_conversation_left_layout, parent, false);
} else {
convertView = getLayoutInflater().from(context).inflate(R.layout.item_conversation_right_layout, parent, false);
}
myHolder.childView = (ViewType)convertView.findViewById(R.id.xx);
myHolder.childView2 = ...
... 获取所有的子View...
convertView.setTag(myHolder);
}
// 后面的代码和前面一样
...
}

上面的代码虽然能够加载不同的布局,但却没有解决复用问题。看来还得继续进行改造。这次我们保留了加载不同布局的部分,着重对复用进行修改,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder myHolder = null;
int type = getItemViewType(position);
if (convertView == null) {
switch (type) {
case MSG_TYPE_SEND:
myHolder = new MyViewHolder();
convertView = getLayoutInflater().from(MainActivity.this).inflate(R.layout.item_conversation_right_layout, parent, false);
myHolder.icon = (ImageView) convertView.findViewById(R.id.user_icon);
myHolder.contentText = (TextView)convertView.findViewById(R.id.content_text);
convertView.setTag(myHolder);
break;
case MSG_TYPE_RECEIVE:
otherHolder = new OtherViewHolder();
convertView = getLayoutInflater().from(MainActivity.this).inflate(R.layout.item_conversation_left_layout, parent, false);
otherHolder.icon = (ImageView) convertView.findViewById(R.id.user_icon);
otherHolder.contentText = (TextView)convertView.findViewById(R.id.content_text);
convertView.setTag(otherHolder);
break;
default:
break;
}
} else {
switch (type) {
case MSG_TYPE_SEND:
myHolder = (MyViewHolder) convertView.getTag();
break;
case MSG_TYPE_RECEIVE:
otherHolder = (OtherViewHolder) convertView.getTag();
break;
default:
break;
}
}
// 设置Item内容
...

return convertView;
}

现在想想应该可以了,但是一运行,滑动的时候却闪退了,查了下Log发现getTag的时候类型转换出错了,网上查了下,原来ListView本身就对多布局进行了支持,在加载多布局的时候,需要重写Adapter的getViewTypeCount()和getItemViewType()方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
  @Override
public int getViewTypeCount() {
// 返回布局的个数
return VIWE_TYPE_COUNT;
}

@Override
public int getItemViewType(int position) {
// 返回布局的类型
return listData.get(position).from;
}

再次运行,发现实现了我们需要的功能!下面贴一下我们整个Adapter的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
private class ConversationAdapter extends BaseAdapter {
/**
* 布局类型的数量
*/
private final int VIWE_TYPE_COUNT = 2;

@Override
public int getCount() {
return listData.size();
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public Object getItem(int position) {
return listData.get(position);
}

@Override
public int getViewTypeCount() {
return VIWE_TYPE_COUNT;
}

@Override
public int getItemViewType(int position) {
return listData.get(position).from;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
OtherViewHolder otherHolder = null;
MyViewHolder myHolder = null;
int type = listData.get(position).from;
if (convertView == null) {
switch (type) {
case MSG_TYPE_SEND:
myHolder = new MyViewHolder();
convertView = getLayoutInflater().from(MainActivity.this).inflate(R.layout.item_conversation_right_layout, parent, false);
myHolder.icon = (ImageView) convertView.findViewById(R.id.user_icon);
myHolder.contentText = (TextView)convertView.findViewById(R.id.content_text);
convertView.setTag(myHolder);
break;
case MSG_TYPE_RECEIVE:
otherHolder = new OtherViewHolder();
convertView = getLayoutInflater().from(MainActivity.this).inflate(R.layout.item_conversation_left_layout, parent, false);
otherHolder.icon = (ImageView) convertView.findViewById(R.id.user_icon);
otherHolder.contentText = (TextView)convertView.findViewById(R.id.content_text);
convertView.setTag(otherHolder);
break;
default:
break;
}
} else {
switch (type) {
case MSG_TYPE_SEND:
myHolder = (MyViewHolder) convertView.getTag();
break;
case MSG_TYPE_RECEIVE:
otherHolder = (OtherViewHolder) convertView.getTag();
break;
default:
break;
}
}

if (type == MSG_TYPE_RECEIVE) {
setItemValue(otherHolder, position);
} else {
setItemValue(myHolder, position);
}

return convertView;
}

/**
* 设置子布局的内容
* @param viewHolder
* @param position
*/
private void setItemValue(ViewHolder viewHolder, int position) {
viewHolder.contentText.setText(listData.get(position).content);
if (listData.get(position).from == 0) {
viewHolder.icon.setImageResource(R.drawable.my_icon);
} else {
viewHolder.icon.setImageResource(R.drawable.other_icon);
}
}
}

效果

总结

通过上面的Demo,我们可以总结出ListView加载不同的Item布局时注意以下几点即可:

  1. 根据数据的类型加载不同的布局;
  2. 重写Adapter的getViewTypeCount()和getItemViewType()实现类型强转;

源码

需要源码的同学可以点击下面的网址进行下载!

下载地址:ConversationDamo

,