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

如何在Xamarin Android中实现多级ExpandableListView或树形视图

嘿,刚接触Xamarin Android就能搞定单级ListView,已经很厉害啦!下面我给你详细捋捋ExpandableListView的二级实现,还有多级树形视图的两种思路,结合你的代码场景一步步来~

一、先搞定原生二级ExpandableListView

ExpandableListView本身就是官方为二级列表设计的组件,核心是自定义BaseExpandableListAdapter,步骤如下:

1. 准备层级数据结构

先定义两个简单的模型类,分别存组项和子项的数据:

// 一级组项模型
public class GroupModel
{
    public string GroupName { get; set; }
    public List<ChildModel> ChildItems { get; set; }
}

// 二级子项模型
public class ChildModel
{
    public string ChildName { get; set; }
}

2. 自定义ExpandableListAdapter

继承BaseExpandableListAdapter,实现组和子项的视图渲染、数据绑定逻辑:

public class CustomExpandableAdapter : BaseExpandableListAdapter
{
    private readonly Context _context;
    private readonly List<GroupModel> _groupData;

    public CustomExpandableAdapter(Context context, List<GroupModel> groupData)
    {
        _context = context;
        _groupData = groupData;
    }

    // 获取指定组的子项数量
    public override int GetChildrenCount(int groupPosition)
    {
        return _groupData[groupPosition].ChildItems.Count;
    }

    // 获取指定位置的子项数据
    public override Java.Lang.Object GetChild(int groupPosition, int childPosition)
    {
        return _groupData[groupPosition].ChildItems[childPosition];
    }

    public override long GetChildId(int groupPosition, int childPosition)
    {
        return childPosition;
    }

    // 渲染子项视图
    public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
    {
        var child = (ChildModel)GetChild(groupPosition, childPosition);
        
        if (convertView == null)
        {
            var inflater = (LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService);
            convertView = inflater.Inflate(Resource.Layout.child_item_layout, null);
        }

        var childTxt = convertView.FindViewById<TextView>(Resource.Id.child_txt);
        childTxt.Text = child.ChildName;
        return convertView;
    }

    // 获取指定组的数据
    public override Java.Lang.Object GetGroup(int groupPosition)
    {
        return _groupData[groupPosition];
    }

    // 获取组的总数
    public override int GroupCount => _groupData.Count;

    public override long GetGroupId(int groupPosition)
    {
        return groupPosition;
    }

    // 渲染组项视图
    public override View GetGroupView(int groupPosition, bool isExpanded, View convertView, ViewGroup parent)
    {
        var group = (GroupModel)GetGroup(groupPosition);
        
        if (convertView == null)
        {
            var inflater = (LayoutInflater)_context.GetSystemService(Context.LayoutInflaterService);
            convertView = inflater.Inflate(Resource.Layout.group_item_layout, null);
        }

        var groupTxt = convertView.FindViewById<TextView>(Resource.Id.group_txt);
        groupTxt.Text = group.GroupName;

        // 可选:添加展开/折叠箭头图标
        var arrowImg = convertView.FindViewById<ImageView>(Resource.Id.expand_arrow);
        arrowImg.SetImageResource(isExpanded ? Resource.Drawable.ic_expanded : Resource.Drawable.ic_collapsed);
        return convertView;
    }

    public override bool IsChildSelectable(int groupPosition, int childPosition)
    {
        return true; // 允许子项被点击
    }

    public override bool HasStableIds => true;
}

3. 准备布局文件

  • 主布局(Main.axml):添加ExpandableListView
<?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/main_expandable_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
  • 组项布局(group_item_layout.axml)
<?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:orientation="horizontal"
    android:gravity="center_vertical">

    <TextView
        android:id="@+id/group_txt"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"/>

    <ImageView
        android:id="@+id/expand_arrow"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@drawable/ic_collapsed"/>
</LinearLayout>
  • 子项布局(child_item_layout.axml)
<?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:paddingLeft="32dp"
    android:paddingVertical="12dp">

    <TextView
        android:id="@+id/child_txt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"/>
</LinearLayout>

4. 在MainActivity中绑定数据

把你的MainActivity代码补充完整,初始化数据并绑定适配器:

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

    // 初始化测试数据
    var groupList = new List<GroupModel>
    {
        new GroupModel
        {
            GroupName = "系统设置",
            ChildItems = new List<ChildModel>
            {
                new ChildModel{ChildName="显示设置"},
                new ChildModel{ChildName="声音设置"},
                new ChildModel{ChildName="隐私设置"}
            }
        },
        new GroupModel
        {
            GroupName = "应用管理",
            ChildItems = new List<ChildModel>
            {
                new ChildModel{ChildName="已安装应用"},
                new ChildModel{ChildName="权限管理"}
            }
        }
    };

    // 绑定适配器
    var expandableList = FindViewById<ExpandableListView>(Resource.Id.main_expandable_list);
    var adapter = new CustomExpandableAdapter(this, groupList);
    expandableList.SetAdapter(adapter);

    // 可选:监听组点击事件,手动控制展开/折叠
    expandableList.GroupClick += (sender, e) =>
    {
        if (expandableList.IsGroupExpanded(e.GroupPosition))
        {
            expandableList.CollapseGroup(e.GroupPosition);
        }
        else
        {
            expandableList.ExpandGroup(e.GroupPosition);
        }
        e.Handled = true;
    };

    // 可选:监听子项点击事件
    expandableList.ChildClick += (sender, e) =>
    {
        var selectedChild = (ChildModel)adapter.GetChild(e.GroupPosition, e.ChildPosition);
        Toast.MakeText(this, $"选中了:{selectedChild.ChildName}", ToastLength.Short).Show();
    };
}
二、实现多级/树形视图(三级及以上)

ExpandableListView原生只支持二级,要做三级或无限层级的树形结构,有两种常用思路:

1. 递归嵌套Adapter(推荐)

让每个子项也具备展开/折叠能力,也就是在子项的布局中嵌套另一个ExpandableListView,递归创建适配器。首先修改数据模型为树形结构:

public class TreeNodeModel
{
    public string NodeName { get; set; }
    public List<TreeNodeModel> ChildNodes { get; set; }
    public bool IsExpanded { get; set; } // 标记当前节点是否展开
}

然后自定义TreeAdapter,在子项视图中嵌入ExpandableListView,递归绑定子节点数据。这种方式能支持无限层级,交互体验更接近原生树形控件。

2. 扁平化数据+缩进模拟

把所有层级的数据扁平化,通过设置不同的左边距来区分层级(比如一级无缩进,二级缩进32dp,三级缩进64dp),用普通ListView实现。这种方式简单易上手,适合层级不多的场景:

// 扁平化树形数据模型
public class FlatTreeItem
{
    public string ItemName { get; set; }
    public int Level { get; set; } // 0=一级,1=二级,2=三级...
}

// ListView适配器中设置缩进
public class FlatTreeAdapter : BaseAdapter<FlatTreeItem>
{
    private readonly Context _context;
    private readonly List<FlatTreeItem> _dataList;

    public FlatTreeAdapter(Context context, List<FlatTreeItem> dataList)
    {
        _context = context;
        _dataList = dataList;
    }

    public override FlatTreeItem this[int position] => _dataList[position];
    public override int Count => _dataList.Count;
    public override long GetItemId(int position) => position;

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var item = _dataList[position];
        if (convertView == null)
        {
            convertView = LayoutInflater.From(_context).Inflate(Resource.Layout.flat_tree_item, null);
        }

        var txt = convertView.FindViewById<TextView>(Resource.Id.tree_item_txt);
        txt.Text = item.ItemName;
        // 根据层级设置左边距
        int indent = item.Level * 32; // 每级缩进32dp
        txt.SetPadding(indent, txt.PaddingTop, txt.PaddingRight, txt.PaddingBottom);
        return convertView;
    }
}
三、小提示
  • 递归嵌套Adapter时,注意避免Activity的强引用导致内存泄漏,可以用WeakReference包装Context。
  • 展开/折叠的动画可以通过Android的属性动画(PropertyAnimation)自定义实现,提升交互体验。
  • 数据量大时,一定要做好ViewHolder复用,避免列表卡顿。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:15:40