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>
二、关键注意点
- 事件解绑:复用视图时一定要先解绑旧事件,否则会出现一个控件触发多个事件的问题。
- 子项类型区分:通过枚举明确子项类型,是实现多类型布局的核心。
- 数据同步:控件状态(比如Switch开关、SeekBar进度)要存在子项模型里,确保视图刷新时状态正确。
- IsChildSelectable:必须返回true,否则子项的点击事件可能无法正常触发。
这样一套下来,你就能实现带多类型子项和各自事件的ExpandableListView了,亲测可用哦!
内容的提问来源于stack exchange,提问作者user9694167
相关产品推荐
相关产品推荐

