Expandable List子项点击着色异常:重复着色问题求助
ExpandableListView子项选中状态复用问题解决
问题描述
开发Expandable List时,期望实现:用户点击子项时,该子项变色标记已选中,再次点击恢复默认颜色。功能逻辑正常,但出现异常:列表中每隔若干项的子项会被错误着色,推测是View复用导致。
相关代码
1. ExpandableListAdapter
public class ExpandableListAdapter extends BaseExpandableListAdapter { private Context context; private List<String> headers; private Map<String, List<String>> items; public ExpandableListAdapter(Context context, List<String> headers, Map<String, List<String>> items) { this.context = context; this.headers = headers; this.items = items; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); } TextView childItemTextView = convertView.findViewById(R.id.childItemTextView); String item = items.get(headers.get(groupPosition)).get(childPosition); childItemTextView.setText(item); return convertView; } // 其他重写方法省略 }
2. ComplexSearchActivity
public class ComplexSearchActivity extends AppCompatActivity implements ComplexSearchContract.View { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化代码省略 expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { String selectedItem = items.get(headers.get(groupPosition)).get(childPosition); presenter.onChildItemClick(selectedItem, v); return true; } }); } @Override public void updateItemSelection(View clickedView, boolean isSelected) { TextView textView = clickedView.findViewById(R.id.childItemTextView); textView.setSelected(isSelected); } }
3. ComplexSearchPresenter
public class ComplexSearchPresenter implements ComplexSearchContract.Presenter { private Set<String> selectedItems = new HashSet<>(); private ComplexSearchContract.View view; // 构造方法省略 public void onChildItemClick(String clickedItem, View clickedView) { if (selectedItems.contains(clickedItem)) { selectedItems.remove(clickedItem); } else { selectedItems.add(clickedItem); } view.updateItemSelection(clickedView, selectedItems.contains(clickedItem)); } // 提供获取选中集合的方法 public Set<String> getSelectedItems() { return selectedItems; } }
4. 布局资源 list_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/childItemTextView" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingLeft="?android:attr/expandableListPreferredChildPaddingLeft" android:paddingTop="10dp" android:paddingBottom="10dp" android:background="@drawable/item_selector"> </TextView> </LinearLayout>
5. Drawable资源 item_selector.xml
<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_selected="true" android:drawable="@drawable/selected_state" /> <item android:drawable="@drawable/default_state" /> </selector>
问题原因
问题确实是View复用导致:ExpandableListView会复用滑出屏幕的View来展示新进入的项,你仅在点击时修改了当前View的选中状态,但复用View时没有根据数据的实际选中状态重置该属性,导致旧的选中状态被带到新的项上。
解决方案
1. 修改Adapter,绑定数据时同步选中状态
让Adapter持有选中项集合的引用,在getChildView中根据当前项是否被选中,设置View的选中状态:
public class ExpandableListAdapter extends BaseExpandableListAdapter { private Context context; private List<String> headers; private Map<String, List<String>> items; private Set<String> selectedItems; // 新增:持有选中项集合 public ExpandableListAdapter(Context context, List<String> headers, Map<String, List<String>> items, Set<String> selectedItems) { this.context = context; this.headers = headers; this.items = items; this.selectedItems = selectedItems; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false); } TextView childItemTextView = convertView.findViewById(R.id.childItemTextView); String item = items.get(headers.get(groupPosition)).get(childPosition); childItemTextView.setText(item); // 关键:根据数据状态设置View选中状态,覆盖复用View的旧状态 childItemTextView.setSelected(selectedItems.contains(item)); return convertView; } // 其他重写方法保持不变 }
2. 初始化Adapter时传入选中集合
在Activity中初始化Adapter时,传入Presenter的选中项集合:
// 初始化Adapter expandableListAdapter = new ExpandableListAdapter(this, headers, items, presenter.getSelectedItems()); expandableListView.setAdapter(expandableListAdapter);
3. 修改选中状态后通知Adapter刷新
在Presenter的onChildItemClick方法末尾,通知View刷新Adapter:
public void onChildItemClick(String clickedItem, View clickedView) { if (selectedItems.contains(clickedItem)) { selectedItems.remove(clickedItem); } else { selectedItems.add(clickedItem); } view.updateItemSelection(clickedView, selectedItems.contains(clickedItem)); // 新增:通知Adapter刷新数据 view.notifyAdapterDataSetChanged(); }
然后在Activity中实现该方法:
@Override public void notifyAdapterDataSetChanged() { expandableListAdapter.notifyDataSetChanged(); }
原理说明
通过在Adapter的getChildView中每次绑定数据时,都根据当前项的实际选中状态设置View的selected属性,确保复用的View不会保留之前的状态,从根源解决了复用导致的错误着色问题。
内容的提问来源于stack exchange,提问作者Rui Santelmo- 1210861
相关产品推荐
相关产品推荐

