Android ListView如何实现分区内条目自分组效果
Android 分区列表实现内部条目聚合分组效果方案
现有实现基础
- 已完成分区列表适配器开发,通过
getView()方法返回分区内单元格视图,核心实现代码如下:
fun getView( section: Int, position: Int, convertView: View?, parent: ViewGroup? ): View { var cellView = convertView if (cellView == null) { cellView = LayoutInflater.from(context).inflate(R.layout.my_cell_layout, parent, false) // 通用初始化逻辑 } // 通用数据绑定逻辑 return cellView }
- 现有单元格布局
my_cell_layout.xml基于自定义CellLayout实现,代码如下:
<CellLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical"> <LinearLayout android:orientation="vertical"> <!-- 通用条目布局代码 --> </LinearLayout> </CellLayout>
- 当前已实现列表大分区分隔,需要进一步实现分区内部条目聚合圆角分组效果:同组条目贴合为一个整体,整体带圆角,分组间保留间距。直接使用CardView包裹每个单元格的方案会出现单条目独立带圆角、条目间有缝隙的异常效果。
错误原因
直接为每个item外层包裹CardView的实现逻辑不符合聚合分组的渲染逻辑:CardView默认自带独立圆角、内边距与阴影属性,每个条目会被渲染为独立卡片,自然会出现单卡分离、中间留缝的问题。
推荐实现方案(动态设置背景,性能最优)
该方案不需要额外嵌套布局,仅通过判断条目在分组内的位置动态设置背景与边距即可实现效果,性能损耗最低。
准备分组对应背景资源
在res/drawable目录下创建4份shape背景,分别对应分组内不同位置的条目:- 单条目分组:四个角均为圆角
- 分组首项:顶部两角为圆角,底部为直角
- 分组中间项:四个角均为直角
- 分组末项:底部两角为圆角,顶部为直角
也可以直接在代码中通过MaterialShapeDrawable动态生成对应圆角背景,避免编写多份xml文件。
新增分组位置判断逻辑
在适配器中增加方法,判断当前条目在所属分组中的位置类型:
// 分组位置类型常量 const val TYPE_GROUP_SINGLE = 0 // 分组仅包含当前1个条目 const val TYPE_GROUP_FIRST = 1 // 分组首项 const val TYPE_GROUP_MIDDLE = 2 // 分组中间项 const val TYPE_GROUP_LAST = 3 // 分组末项 /** * 获取当前条目在分组内的位置类型 * @param section 当前所属分区 * @param position 条目在分区内的位置 */ private fun getItemGroupPositionType(section: Int, position: Int): Int { // 需自行实现:获取当前条目所属分组的总条目数、首末位置 val currentGroupItemCount = getGroupItemCount(section, position) val groupFirstPos = getGroupFirstPosition(section, position) val groupLastPos = getGroupLastPosition(section, position) return when { currentGroupItemCount == 1 -> TYPE_GROUP_SINGLE position == groupFirstPos -> TYPE_GROUP_FIRST position == groupLastPos -> TYPE_GROUP_LAST else -> TYPE_GROUP_MIDDLE } }
注:如果分区内不需要再拆分子分组、直接整个分区为一个聚合卡片,直接取分区总条目数判断位置即可。
- 修改
getView()逻辑,动态设置背景与边距
首先给布局根节点CellLayout增加id@+id/cell_root,然后在getView()中根据位置类型设置对应属性:
fun getView( section: Int, position: Int, convertView: View?, parent: ViewGroup? ): View { var cellView = convertView if (cellView == null) { cellView = LayoutInflater.from(context).inflate(R.layout.my_cell_layout, parent, false) // 原有初始化逻辑 } // 原有数据绑定逻辑 // 新增分组样式设置 val cellRoot = cellView.findViewById<CellLayout>(R.id.cell_root) val layoutParams = cellRoot.layoutParams as ViewGroup.MarginLayoutParams // 自行定义尺寸值:卡片左右边距、分组间上下间距 val horizontalMargin = resources.getDimensionPixelOffset(R.dimen.group_card_horizontal_margin) val groupVerticalGap = resources.getDimensionPixelOffset(R.dimen.group_vertical_gap) when(getItemGroupPositionType(section, position)) { TYPE_GROUP_SINGLE -> { cellRoot.setBackgroundResource(R.drawable.bg_group_item_single) layoutParams.setMargins(horizontalMargin, groupVerticalGap, horizontalMargin, groupVerticalGap) } TYPE_GROUP_FIRST -> { cellRoot.setBackgroundResource(R.drawable.bg_group_item_first) layoutParams.setMargins(horizontalMargin, groupVerticalGap, horizontalMargin, 0) } TYPE_GROUP_MIDDLE -> { cellRoot.setBackgroundResource(R.drawable.bg_group_item_middle) layoutParams.setMargins(horizontalMargin, 0, horizontalMargin, 0) } TYPE_GROUP_LAST -> { cellRoot.setBackgroundResource(R.drawable.bg_group_item_last) layoutParams.setMargins(horizontalMargin, 0, horizontalMargin, groupVerticalGap) } } cellRoot.layoutParams = layoutParams return cellView }
- 调整item布局
移除原有多余嵌套,不需要额外包裹CardView,根布局直接设置wrap_content高度,内部内容添加统一内边距即可:
<CellLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/cell_root" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <!-- 原有条目内容代码 --> </LinearLayout> </CellLayout>
可选替代方案
如果列表基于RecyclerView实现,可以自定义ItemDecoration统一绘制分组圆角背景与分割线:在onDrawOver回调中判断条目所属分组位置,对应绘制不同圆角的背景块,不需要修改item布局与适配器逻辑,代码解耦性更高,核心判断逻辑与上述方案一致。
实现注意事项
- 同组内相邻条目不要设置上下间距,保证条目完全贴合,视觉上为一个整体
- 如果需要实现卡片阴影效果,仅为分组首项设置顶部阴影、末项设置底部阴影、单条目设置全阴影即可,中间项不需要添加阴影
- 同组内的分割线仅在中间项之间绘制,首项顶部、末项底部不需要绘制分割线
内容的提问来源于stack exchange,提问作者panjutajack
相关产品推荐
相关产品推荐

