ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

使用ListView多Type的错误姿势

2019-10-22 10:06:59  阅读:341  来源: 互联网

标签:姿势 java holder convertView position android ListView Type View


项目中,有这样的一个需求:

有三种打印机类型,每种类型可以添加、删除对应类型的打印机。
按照以往,我写的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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
public class  extends BaseAdapter {

public final static int BLUETOOTH_HEADER = 0;
public final static int BLUETOOTH_PRINTER = 1;
public final static int NET_HEADER = 2;
public final static int NET_PRINTER = 3;
public final static int CLOUD_HEADER = 4;
public final static int CLOUD_PRINTER = 5;

private Context mContext;
private LayoutInflater mInflater;
private OnItemFunctionClickListener listener;

private List<PrinterInfo> mPrinters = new ArrayList<>();
private int blueToothCount = 0;
private int netCount = 0;
private int cloudCount = 0;

public (Context context, List<PrinterInfo> printers, SwipePartMenuListView listView) {
mContext = context;
mPrinters = printers;
mInflater = LayoutInflater.from(context);

getNotSwipeItem(listView);
}

private void getNotSwipeItem(SwipePartMenuListView listView) {
blueToothCount = 0;
netCount = 0;
cloudCount = 0;

for (PrinterInfo info : mPrinters) {
if (info.printerType == PrinterInfo.TYPE_BLUETOOTH) {
blueToothCount++;
} else if (info.printerType == PrinterInfo.TYPE_NETWORK) {
netCount++;
} else if (info.printerType == PrinterInfo.TYPE_CLOUD) {
cloudCount++;
}
}

List<Integer> titleList = new LinkedList<>();
titleList.add(0);
titleList.add(1 + blueToothCount);
titleList.add(2 + blueToothCount + netCount);
listView.setCannotSwipePositionList(titleList);
}

public void setPrinters(List<PrinterInfo> printers, SwipePartMenuListView listView) {
this.mPrinters = printers;

getNotSwipeItem(listView);
}

public void setListener(OnItemFunctionClickListener listener) {
this.listener = listener;
}


public View getView(int position, View convertView, ViewGroup parent) {
PrinterManagerViewHolder holder;
final int viewType = getItemViewType(position);
holder = new PrinterManagerViewHolder();
if (viewType == BLUETOOTH_HEADER || viewType == NET_HEADER || viewType == CLOUD_HEADER) {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_printer_title, parent, false);
holder.headerTitle = (TextView) convertView.findViewById(R.id.printer_category_tv);
holder.headerIcon = (ImageView) convertView.findViewById(R.id.printer_icon_iv);
holder.headerAdd = (ImageView) convertView.findViewById(R.id.printer_add_device_iv);
holder.headerDivider = convertView.findViewById(R.id.printer_title_divider);
convertView.setTag(holder);
} else {
holder = (PrinterManagerViewHolder) convertView.getTag();
}
} else {
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item_printer_devices, parent, false);
holder.connect = (TextView) convertView.findViewById(R.id.printer_connect_tv);
holder.divider = convertView.findViewById(R.id.printer_last_divider);
holder.setting = (TextView) convertView.findViewById(R.id.printer_setting_tv);
holder.title = (TextView) convertView.findViewById(R.id.printer_title_tv);
holder.connectIcon = (ImageView) convertView.findViewById(R.id.printer_connect_iv);
convertView.setTag(holder);
} else {
holder = (PrinterManagerViewHolder) convertView.getTag();
}
}
switch (viewType) {
case BLUETOOTH_HEADER:
holder.headerTitle.setText("蓝牙打印机");
holder.headerAdd.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.btn_setting_goods_add));
holder.headerAdd.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
BlueToothPrinterActivity.navigateTo(mContext);
}
});
break;
case NET_HEADER:
holder.headerTitle.setText("网络打印机");
holder.headerAdd.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.btn_setting_goods_add));
holder.headerAdd.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
NetPrinterActivity.navigateTo(mContext);
}
});
break;
case CLOUD_HEADER:
holder.headerTitle.setText("云打印机");
holder.headerAdd.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.btn_setting_goods_scan));
if (position == getCount() - 1) {
holder.headerDivider.setVisibility(View.VISIBLE);
}
break;
case BLUETOOTH_PRINTER:
PrinterInfo printer = mPrinters.get(position - 1);
initPrinter(holder, printer);
break;
case NET_PRINTER:
PrinterInfo netPrinter = mPrinters.get(position - 2);
initPrinter(holder, netPrinter);
break;
case CLOUD_PRINTER:
if (position == getCount() - 1) {
holder.divider.setVisibility(View.VISIBLE);
}
PrinterInfo cloudPrinter = mPrinters.get(position - 3);
initPrinter(holder, cloudPrinter);
break;
}
return convertView;
}

private void initPrinter(PrinterManagerViewHolder holder, final PrinterInfo printerInfo) {
boolean connected = false;
holder.title.setText(printerInfo.name);

HashMap<String, IPrinter> printers = PrinterManager.getInstance().getPrinterList();
if (printers.keySet().contains(printerInfo.mac)) {
connected = true;
holder.connect.setText("断开");
holder.connectIcon.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.ic_connect));
} else {
holder.connect.setText("连接");
holder.connectIcon.setBackgroundDrawable(mContext.getResources().getDrawable(R.drawable.ic_break));
}

holder.setting.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
PrinterSettingActivity.navigateTo(mContext, printerInfo);
}
});
final boolean finalConnected = connected;
holder.connect.setOnClickListener(new View.OnClickListener() {

public void onClick(View v) {
if (listener != null) {
listener.onConnectClicked(printerInfo, finalConnected);
}
}
});
}

@Override
public int getItemViewType(int position) {
if (position == 0) {
return BLUETOOTH_HEADER;
} else if (position > 0 && position <= blueToothCount) {
return BLUETOOTH_PRINTER;
} else if (position == blueToothCount + 1) {
return NET_HEADER;
} else if (position > blueToothCount + 1 && position <= netCount + blueToothCount + 1) {
return NET_PRINTER;
} else if (position == netCount + blueToothCount + 2) {
return CLOUD_HEADER;
} else {
return CLOUD_PRINTER;
}
}

@Override
public int getCount() {
if (mPrinters != null) {
return mPrinters.size() + 3;
}
return 0;
}

@Override
public Object getItem(int position) {
return null;
}

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

private class PrinterManagerViewHolder {


private TextView headerTitle;
private ImageView headerIcon;
private ImageView headerAdd;
private View headerDivider;
// Printer
private TextView connect;
private TextView title;
private View divider;
private TextView setting;
private ImageView connectIcon;
}

public interface OnItemFunctionClickListener {
void onConnectClicked(PrinterInfo printer, boolean connected);
}

}

我将 view 分为了6个 Type ,三种头部 Type 使用一种布局,三种打印机 Type 使用一种布局,然后总共用了一个 ViewHoloder 。
然后发现一个问题:
当我删除一个打印机之后,刷新界面的时候崩溃了
问题其实很简单:就是删除的打印机(BLUETOOTH_PRINTER Type)convertView 进入到缓存里面,然后下个 Item 的 Type 是 NET_HEADER Type,由于重用机制,这个 Item 会重用 convertView,此时这个 convertView 绑定的 ViewHoloder 是 Printer 部分,而自己要使用的是 Header 部分,其 view 都为 null了,导致空指针崩溃。
解决办法:添加代码

1
2
3
4
@Override
public int getViewTypeCount() {
return 6;
}

ListView 的缓存机制是可以针对不同 Type 来进行缓存的,当不复写这个方法的时候,其默认的实现是 返回1 ,所以导致getItemViewType返回的 Type 实际上是没有用的,不管是什么 Type, ListView 填充的 convertView 永远是一样的。所以,当改成 返回6 的时候, ListView 便会填充 6 种 convertView 了,所绑定的 ViewHoloder 具有的属性也会一样,就避免了空指针崩溃了。
当和同事讨论这点的时候,同事指出: 有几种布局,就用几种 Type,几种 ViewHoloder,一一对应才是官方推荐的行为。
自己想了下,确实是的。当网络打印机这个 Item 要显示的时候,如果缓存中有蓝牙打印机的 convertView,我是用不了的,因为他们的 Type 不一样。这样的一个做法,就是自己把 ListView 的缓存机制整乱了。
修改后的代码如下:

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 大专栏  使用ListView多Type的错误姿势
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
public class  extends BaseAdapter {

private final static int TYPE_HEADER = 0;
private final static int TYPE_PRINTER = 1;

private Context mContext;
private LayoutInflater mInflater;
private OnItemFunctionClickListener listener;

private ArrayList<PrinterInfo> mPrinters;
private int blueToothCount = 0;
private int netCount = 0;
private int cloudCount = 0;

public (Context context, ArrayList<PrinterInfo> printers, SwipePartMenuListView listView) {
mContext = context;
mPrinters = printers;
mInflater = LayoutInflater.from(context);

setNotSwipeItems(listView);
}

private void setNotSwipeItems(SwipePartMenuListView listView) {
blueToothCount = 0;
netCount = 0;
cloudCount = 0;

for (PrinterInfo info : mPrinters) {
if (info.printerType == PrinterInfo.TYPE_BLUETOOTH) {
blueToothCount++;
} else if (info.printerType == PrinterInfo.TYPE_NETWORK) {
netCount++;
} else if (info.printerType == PrinterInfo.TYPE_CLOUD) {
cloudCount++;
}
}

List<Integer> titleList = new ArrayList<>();
titleList.add(0);
titleList.add(1 + blueToothCount);
titleList.add(2 + blueToothCount + netCount);
listView.setCannotSwipePositionList(titleList);
}

public void setPrinters(ArrayList<PrinterInfo> printers, SwipePartMenuListView listView) {
this.mPrinters = printers;

setNotSwipeItems(listView);
}

public void setListener(OnItemFunctionClickListener listener) {
this.listener = listener;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder headerHolder = null;
PrinterViewHolder printerHolder = null;
int viewType = getItemViewType(position);
if (viewType == TYPE_HEADER) {
if (convertView == null) {
headerHolder = new HeaderViewHolder();
convertView = mInflater.inflate(R.layout.item_printer_title, parent, false);
headerHolder.headerTitle = (TextView) convertView.findViewById(R.id.printer_category_tv);
headerHolder.headerIcon = (ImageView) convertView.findViewById(R.id.printer_icon_iv);
headerHolder.headerAdd = (ImageView) convertView.findViewById(R.id.printer_add_device_iv);
headerHolder.headerDivider = convertView.findViewById(R.id.printer_title_divider);
convertView.setTag(headerHolder);
} else {
headerHolder = (HeaderViewHolder) convertView.getTag();
}
} else {
if (convertView == null) {
printerHolder = new PrinterViewHolder();
convertView = mInflater.inflate(R.layout.item_printer_devices, parent, false);
printerHolder.connect = (TextView) convertView.findViewById(R.id.printer_connect_tv);
printerHolder.divider = convertView.findViewById(R.id.printer_last_divider);
printerHolder.setting = (TextView) convertView.findViewById(R.id.printer_setting_tv);
printerHolder.title = (TextView) convertView.findViewById(R.id.printer_title_tv);
printerHolder.connectIcon = (ImageView) convertView.findViewById(R.id.printer_connect_iv);
convertView.setTag(printerHolder);
} else {
printerHolder = (PrinterViewHolder) convertView.getTag();
}
}
switch (viewType) {
case TYPE_HEADER:
if (headerHolder != null) {
if (position == 0) {
headerHolder.headerTitle.setText("蓝牙打印机");
headerHolder.headerIcon.setImageResource(R.drawable.ic_printer_bluetooth);
headerHolder.headerAdd.setImageResource(R.drawable.btn_setting_goods_add);
headerHolder.headerAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BlueToothPrinterActivity.navigateTo(mContext, mPrinters);
}
});
} else if (position == blueToothCount + 1) {
headerHolder.headerTitle.setText("网络打印机");
headerHolder.headerIcon.setImageResource(R.drawable.ic_printer_wifi);
headerHolder.headerAdd.setImageResource(R.drawable.btn_setting_goods_add);
headerHolder.headerAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// NetPrinterActivity.navigateTo(mContext);
}
});
} else if (position == netCount + blueToothCount + 2) {
headerHolder.headerTitle.setText("云打印机");
headerHolder.headerIcon.setImageResource(R.drawable.ic_printer_cloud);
headerHolder.headerAdd.setImageResource(R.drawable.btn_setting_goods_scan);
headerHolder.headerAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// NetPrinterActivity.navigateTo(mContext);
}
});
if (position == getCount() - 1) {
headerHolder.headerDivider.setVisibility(View.VISIBLE);
} else {
headerHolder.headerDivider.setVisibility(View.GONE);
}
}
}
break;
case TYPE_PRINTER:
if (printerHolder != null) {
int index;
if (position > 0 && position <= blueToothCount) {
index = position - 1;
} else if (position > blueToothCount + 1 && position <= netCount + blueToothCount + 1) {
index = position - 2;
} else {
index = position - 3;
if (position == getCount() - 1) {
printerHolder.divider.setVisibility(View.VISIBLE);
} else {
printerHolder.divider.setVisibility(View.GONE);
}
}
PrinterInfo printer = mPrinters.get(index);
initPrinter(printerHolder, printer);
}
break;
default:
break;
}
return convertView;
}

private void initPrinter(PrinterViewHolder holder, final PrinterInfo printerInfo) {
if (holder != null) {
boolean connected = false;
holder.title.setText(printerInfo.name);

HashMap<String, IPrinter> printers = PrinterManager.getInstance().getPrinterList();
if (printers.keySet().contains(printerInfo.mac)) {
connected = true;
holder.connect.setText("断开");
holder.connectIcon.setImageResource(R.drawable.ic_connect);
} else {
holder.connect.setText("连接");
holder.connectIcon.setImageResource(R.drawable.ic_break);
}

holder.setting.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PrinterSettingActivity.navigateTo(mContext, printerInfo);
}
});
final boolean finalConnected = connected;
holder.connect.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (listener != null) {
listener.onConnectClicked(printerInfo, finalConnected);
}
}
});
}
}

public PrinterInfo getPrinterInfo(int position) {
int index;
if (position > 0 && position <= blueToothCount) {
index = position - 1;
} else if (position > blueToothCount + 1 && position <= netCount + blueToothCount + 1) {
index = position - 2;
} else {
index = position - 3;
}
return mPrinters.get(index);
}

@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
} else if (position > 0 && position <= blueToothCount) {
return TYPE_PRINTER;
} else if (position == blueToothCount + 1) {
return TYPE_HEADER;
} else if (position > blueToothCount + 1 && position <= netCount + blueToothCount + 1) {
return TYPE_PRINTER;
} else if (position == netCount + blueToothCount + 2) {
return TYPE_HEADER;
} else {
return TYPE_PRINTER;
}
}

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

@Override
public int getCount() {
if (mPrinters != null) {
return mPrinters.size() + 3;
}
return 3;
}

}

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

private class HeaderViewHolder {
private TextView headerTitle;
private ImageView headerIcon;
private ImageView headerAdd;
private View headerDivider;
}

private class PrinterViewHolder {
private TextView connect;
private TextView title;
private View divider;
private TextView setting;
private ImageView connectIcon;
}

public interface OnItemFunctionClickListener {
void onConnectClicked(PrinterInfo printer, boolean connected);
}

}

这样的话,结构其实会更加清晰,拆分得更具体。
另外,注意一点: 代码中的 Type 类型 TYPE_HEADER 是从0开始的。这是因为不从 0 开始当 Adapter notifyDataSetChanged 时就会报错。举个栗子:

1
2
private final static int TYPE_HEADER = 5;
private final static int TYPE_PRINTER = 6;

报错信息:

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
FATAL EXCEPTION: main
Process: com.maimairen.app.jinchuhuo.dev, PID: 15967
java.lang.ArrayIndexOutOfBoundsException: length=2; index=5
at android.widget.AbsListView$RecycleBin.addScrapView(AbsListView.java:6726)
at android.widget.ListView.layoutChildren(ListView.java:1644)
at android.widget.AbsListView.onLayout(AbsListView.java:2148)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16653)
at android.view.ViewGroup.layout(ViewGroup.java:5438)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2198)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1958)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1134)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6050)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:860)
at android.view.Choreographer.doCallbacks(Choreographer.java:672)
at android.view.Choreographer.doFrame(Choreographer.java:608)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:846)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5438)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:629)

可以看到,ListView 从缓存中去取 view 的时候,是用的 Type 的值来作为 index 的,所以 Type 类型一定是从0开始的
因为自己长期以来一直是之前的那种做法,错了太多次了,却没有及时发现错误,经过这次同事的指正,总算是纠正过来了。写篇博客备忘,忘性太大了~

标签:姿势,java,holder,convertView,position,android,ListView,Type,View
来源: https://www.cnblogs.com/wangziqiang123/p/11718109.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有