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

Xamarin中ExpandableListView实现及子项绑定不同事件的方法(求示例)

在Xamarin.Android中实现ExpandableListView及多类型子项事件绑定

嘿,我来帮你搞定Xamarin里的ExpandableListView和不同子项的事件绑定问题!这玩意儿在做多级列表的时候确实挺实用的,尤其是要放Switch、Button、SeekBar这类不同控件的场景,下面我一步步给你讲清楚,还带完整示例哦。

一、先搞定基础的ExpandableListView实现

首先得把基础框架搭起来,分这几步走:

1. 定义数据模型

咱们需要两个模型:一个是分组模型,一个是带类型标识的子项模型(用来区分不同控件)。

// 子项类型枚举,明确区分不同控件
public enum ChildItemType
{
    Switch,
    Button,
    SeekBar
}

// 子项模型,存储对应控件的状态和显示文本
public class ChildItem
{
    public ChildItemType Type { get; set; }
    public string Text { get; set; }
    public bool SwitchIsChecked { get; set; } // Switch专属状态
    public int SeekBarProgress { get; set; } // SeekBar专属进度
}

// 分组模型,包含分组标题和子项列表
public class GroupItem
{
    public string GroupTitle { get; set; }
    public List<ChildItem> ChildItems { get; set; } = new List<ChildItem>();
}

2. 准备布局文件

需要分组头部布局+三种不同子项布局:

  • group_layout.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:padding="16dp"
    android:background="#f0f0f0">
    <TextView
        android:id="@+id/tv_group_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"/>
</LinearLayout>
  • switch_child.xml(Switch子项):
<?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:padding="12dp"
    android:orientation="horizontal"
    android:gravity="center_vertical">
    <TextView
        android:id="@+id/tv_switch_text"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>
    <Switch
        android:id="@+id/switch_control"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>
  • button_child.xml(Button子项):
<?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:padding="12dp">
    <Button
        android:id="@+id/btn_child"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>
</LinearLayout>
  • seekbar_child.xml(SeekBar子项):
<?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:padding="12dp"
    android:orientation="vertical">
    <TextView
        android:id="@+id/tv_seekbar_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>
    <SeekBar
        android:id="@+id/seekbar_control"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tv_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:gravity="end"
        android:paddingTop="4dp"/>
</LinearLayout>

3. 自定义ExpandableListAdapter(核心部分)

这是实现多类型子项和事件绑定的关键,重点在GetChildView方法里根据子项类型加载布局、绑定数据和事件:

public class CustomExpandableAdapter : BaseExpandableListAdapter
{
    private readonly Context _context;
    private readonly List<GroupItem> _groupList;

    public CustomExpandableAdapter(Context context, List<GroupItem> groupList)
    {
        _context = context;
        _groupList = groupList;
    }

    public override int GroupCount => _groupList.Count;
    public override int GetChildrenCount(int groupPosition) => _groupList[groupPosition].ChildItems.Count;
    public override Java.Lang.Object GetGroup(int groupPosition) => _groupList[groupPosition];
    public override Java.Lang.Object GetChild(int groupPosition, int childPosition) => _groupList[groupPosition].ChildItems[childPosition];
    public override long GetGroupId(int groupPosition) => groupPosition;
    public override long GetChildId(int groupPosition, int childPosition) => childPosition;
    public override bool HasStableIds => true;
    public override bool IsChildSelectable(int groupPosition, int childPosition) => true; // 必须返回true,否则事件可能不触发

    // 加载分组头部视图
    public override View GetGroupView(int groupPosition, bool isExpanded, View convertView, ViewGroup parent)
    {
        ViewHolderGroup holder;
        if (convertView == null)
        {
            convertView = LayoutInflater.From(_context).Inflate(Resource.Layout.group_layout, parent, false);
            holder = new ViewHolderGroup { GroupTitle = convertView.FindViewById<TextView>(Resource.Id.tv_group_title) };
            convertView.Tag = holder;
        }
        else
        {
            holder = (ViewHolderGroup)convertView.Tag;
        }
        holder.GroupTitle.Text = _groupList[groupPosition].GroupTitle;
        return convertView;
    }

    // 加载子项视图(处理多类型和事件绑定)
    public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
    {
        var childItem = _groupList[groupPosition].ChildItems[childPosition];
        ViewHolderChild holder = null;

        // 根据子项类型加载对应布局
        switch (childItem.Type)
        {
            case ChildItemType.Switch:
                if (convertView == null || !(convertView.Tag is ViewHolderSwitch))
                {
                    convertView = LayoutInflater.From(_context).Inflate(Resource.Layout.switch_child, parent, false);
                    holder = new ViewHolderSwitch
                    {
                        SwitchText = convertView.FindViewById<TextView>(Resource.Id.tv_switch_text),
                        SwitchControl = convertView.FindViewById<Switch>(Resource.Id.switch_control)
                    };
                    convertView.Tag = holder;
                }
                else
                {
                    holder = (ViewHolderSwitch)convertView.Tag;
                }
                var switchHolder = (ViewHolderSwitch)holder;
                switchHolder.SwitchText.Text = childItem.Text;
                // 先解绑旧事件,避免复用视图时重复触发
                switchHolder.SwitchControl.CheckedChange -= SwitchControl_CheckedChange;
                switchHolder.SwitchControl.Checked = childItem.SwitchIsChecked;
                // 绑定新事件
                switchHolder.SwitchControl.CheckedChange += (s, e) =>
                {
                    childItem.SwitchIsChecked = e.IsChecked;
                    Toast.MakeText(_context, $"「{childItem.Text}」状态:{e.IsChecked}", ToastLength.Short).Show();
                };
                break;

            case ChildItemType.Button:
                if (convertView == null || !(convertView.Tag is ViewHolderButton))
                {
                    convertView = LayoutInflater.From(_context).Inflate(Resource.Layout.button_child, parent, false);
                    holder = new ViewHolderButton { ChildButton = convertView.FindViewById<Button>(Resource.Id.btn_child) };
                    convertView.Tag = holder;
                }
                else
                {
                    holder = (ViewHolderButton)convertView.Tag;
                }
                var btnHolder = (ViewHolderButton)holder;
                btnHolder.ChildButton.Text = childItem.Text;
                btnHolder.ChildButton.Click -= ChildButton_Click;
                btnHolder.ChildButton.Click += (s, e) =>
                {
                    Toast.MakeText(_context, $"你点击了「{childItem.Text}」", ToastLength.Short).Show();
                };
                break;

            case ChildItemType.SeekBar:
                if (convertView == null || !(convertView.Tag is ViewHolderSeekBar))
                {
                    convertView = LayoutInflater.From(_context).Inflate(Resource.Layout.seekbar_child, parent, false);
                    holder = new ViewHolderSeekBar
                    {
                        SeekBarText = convertView.FindViewById<TextView>(Resource.Id.tv_seekbar_text),
                        SeekBarControl = convertView.FindViewById<SeekBar>(Resource.Id.seekbar_control),
                        ProgressText = convertView.FindViewById<TextView>(Resource.Id.tv_progress)
                    };
                    convertView.Tag = holder;
                }
                else
                {
                    holder = (ViewHolderSeekBar)convertView.Tag;
                }
                var seekBarHolder = (ViewHolderSeekBar)holder;
                seekBarHolder.SeekBarText.Text = childItem.Text;
                seekBarHolder.SeekBarControl.Progress = childItem.SeekBarProgress;
                seekBarHolder.ProgressText.Text = $"进度:{childItem.SeekBarProgress}%";
                seekBarHolder.SeekBarControl.ProgressChanged -= SeekBarControl_ProgressChanged;
                seekBarHolder.SeekBarControl.ProgressChanged += (s, e) =>
                {
                    if (e.FromUser) // 只处理用户手动拖动的情况
                    {
                        childItem.SeekBarProgress = e.Progress;
                        seekBarHolder.ProgressText.Text = $"进度:{e.Progress}%";
                        Toast.MakeText(_context, $"「{childItem.Text}」进度更新为:{e.Progress}%", ToastLength.Short).Show();
                    }
                };
                break;
        }

        return convertView;
    }

    // 视图持有者类,优化列表性能
    private class ViewHolderGroup : Java.Lang.Object
    {
        public TextView GroupTitle { get; set; }
    }
    private abstract class ViewHolderChild : Java.Lang.Object { }
    private class ViewHolderSwitch : ViewHolderChild
    {
        public TextView SwitchText { get; set; }
        public Switch SwitchControl { get; set; }
    }
    private class ViewHolderButton : ViewHolderChild
    {
        public Button ChildButton { get; set; }
    }
    private class ViewHolderSeekBar : ViewHolderChild
    {
        public TextView SeekBarText { get; set; }
        public SeekBar SeekBarControl { get; set; }
        public TextView ProgressText { get; set; }
    }
}

4. 在Activity中初始化列表

最后把数据、适配器和ExpandableListView绑定起来:

public class MainActivity : AppCompatActivity
{
    private ExpandableListView _expandableListView;
    private List<GroupItem> _groupData;

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.activity_main);

        // 初始化测试数据
        InitTestData();
        // 绑定列表和适配器
        _expandableListView = FindViewById<ExpandableListView>(Resource.Id.expandable_list);
        var adapter = new CustomExpandableAdapter(this, _groupData);
        _expandableListView.SetAdapter(adapter);

        // 可选:默认展开所有分组
        for (int i = 0; i < adapter.GroupCount; i++)
        {
            _expandableListView.ExpandGroup(i);
        }
    }

    private void InitTestData()
    {
        _groupData = new List<GroupItem>
        {
            new GroupItem
            {
                GroupTitle = "设置选项",
                ChildItems = new List<ChildItem>
                {
                    new ChildItem{ Type = ChildItemType.Switch, Text = "开启通知", SwitchIsChecked = true },
                    new ChildItem{ Type = ChildItemType.Button, Text = "清除缓存" },
                    new ChildItem{ Type = ChildItemType.SeekBar, Text = "音量调节", SeekBarProgress = 50 }
                }
            },
            new GroupItem
            {
                GroupTitle = "功能测试",
                ChildItems = new List<ChildItem>
                {
                    new ChildItem{ Type = ChildItemType.Switch, Text = "夜间模式", SwitchIsChecked = false },
                    new ChildItem{ Type = ChildItemType.SeekBar, Text = "亮度调节", SeekBarProgress = 70 },
                    new ChildItem{ Type = ChildItemType.Button, Text = "重置设置" }
                }
            }
        };
    }
}

对应的activity_main.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="match_parent"
    android:orientation="vertical">
    <ExpandableListView
        android:id="@+id/expandable_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

二、关键注意点

  1. 事件解绑:复用视图时一定要先解绑旧事件,否则会出现一个控件触发多个事件的问题。
  2. 子项类型区分:通过枚举明确子项类型,是实现多类型布局的核心。
  3. 数据同步:控件状态(比如Switch开关、SeekBar进度)要存在子项模型里,确保视图刷新时状态正确。
  4. IsChildSelectable:必须返回true,否则子项的点击事件可能无法正常触发。

这样一套下来,你就能实现带多类型子项和各自事件的ExpandableListView了,亲测可用哦!

内容的提问来源于stack exchange,提问作者user9694167

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:44:54