android ListView使用ViewHolder优化时,EditText设置TextWatcher时,滑动ListView导致内容错乱
一般为了避免ListView卡顿,会在 adapter 的 getView方法中使用 convertView 和 ViewHolder 优化。这样可以重用之前加载过的布局并减少在母视图中查找子视图的时间,以达到最大限度优化的目的。
当ListView的Item中有要支持更新操作的控件时,一般要添加各种监听器。这里就要注意布局重用带来的问题;因为重用布局时,某一次 getView中使用的convertView可能是之前使用过的,里面的子视图的监听事件仍然有效,而重新设置子视图的值时,会使得之前注册的监听事件错误地执行。比如下面这段代码,如果EditText的TextWatcher使用注释掉的代码添加,则滑动ListView时,由于重用布局的 EditText 的内容发生了改变,为其注册的监听器发生作用,但是 onTextChanged 方法执行时,里面的position
仍是该EditText 第一次使用时的 position(内部类创建时使用的final 的position 不会随外部的position而变化)。于是代码就错误地更新了 该EditText第一次使用时的 position位置上的 CommField。
对于有些监听器,比如 RadioButton 的 选中状态变化监听,可以采用 每一次 getView 时都先重设 监听器,再设置RadioButton 的值,来避免该问题。但是 EditText的TextWatcher 监听器使用同样的方法却无法避免,甚至 每次getView 中先移除监听器,再设值,再重新添加监听器,都仍然会出现前面的错误。
这种情况下,可以使用下面代码中未被注释的方式,把当前 position 挂载在EditText 上, 更新时取EditText中挂载的position,这样就可以保证position与当前EditText的当前位置一致。
/** * 属性编辑listView的Adapter */ class AdapterNameSurveyRecordDetail extends BaseAdapter { private LayoutInflater inflater; private List<CommField> commFldLst; public AdapterNameSurveyRecordDetail(Context context) { this.inflater = LayoutInflater.from(context); } public void setFldLst(List<CommField> commFldLst) { this.commFldLst = commFldLst; } public List<CommField> getCommFlds() { return this.commFldLst; } public NameSurveyRecord getNameSurveyRecord() { return this.nsr; } @Override public int getCount() { if (this.commFldLst == null) { return 0; } return commFldLst.size(); } @Override public Object getItem(int position) { if (this.commFldLst == null) { return null; } return commFldLst.get(position); } @Override public long getItemId(int position) { return 0; } @Override public View getView(final int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { convertView = inflater.inflate(R.layout.layout_module_gathercenter_detail_item, parent, false); holder = new ViewHolder(); holder.tvName = (TextView) convertView.findViewById(R.id.tv_attname); holder.etValue = (EditText) convertView.findViewById(R.id.et_attvalue); holder.etValue.setTag(position); convertView.setTag(holder); holder.etValue.addTextChangedListener(new AttTextWatcher(holder)); } else { holder = (ViewHolder) convertView.getTag(); holder.etValue.setTag(position); } holder.tvName.setText(commFldLst.get(position).getStrName()); holder.etValue.setVisibility(View.VISIBLE); holder.etValue.setText(commFldLst.get(position).getStrValue()); holder.etValue.setTextColor(Color.BLACK); holder.etValue.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { Log.w("AdapterNameSurveyRecordDetail", hasFocus + " setOnFocusChangeListener"); if (hasFocus) { lv.smoothScrollToPositionFromTop(position, 10); } } }); // 监听属性值的变化,实时地把更新放入commFldLst中 // holder.etValue.addTextChangedListener(new TextWatcher() { // // @Override // public void onTextChanged(CharSequence s, int start, int before, int count) { // // commFldLst.get(position).setStrValue(s.toString()); // nsr.update(commFldLst.get(position).getStrName(), s.toString()); // } // // @Override // public void beforeTextChanged(CharSequence s, int start, int count, int after) { // // } // // @Override // public void afterTextChanged(Editable s) { // // } // }); return convertView; } /** * 属性值 {@link EditText} 的监听器 * @author yangsheng * @date 2014年12月15日 */ private class AttTextWatcher implements TextWatcher{ ViewHolder holder = null; public AttTextWatcher(ViewHolder holder){ this.holder = holder; } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { int position = (Integer) holder.etValue.getTag(); commFldLst.get(position).setStrValue(s.toString().trim()); nsr.update(commFldLst.get(position).getStrName(), s.toString()); } } private class ViewHolder { TextView tvName; EditText etValue; } }