You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 09:07:01