Android应用基础 嵌套的RecyclerView

简介

  使用ExpandableListView可以实现每一项都自带展开/关闭功能的列表。但如果我们不需要展开/关闭的动态效果,只想让一般的recyclerView每一项都显示一些子项该如何实现呢?分享一种在LinearLayout中动态添加View的实现方法。

确定数据结构

   目标实现一个菜单画面,食物品类下嵌套具体餐点。效果如下:

    

   品类标题和具体餐点分别需要一个DTO定义, 前者包含后者

  • 品类标题(ParentItemEntity.kt):
1
2
3
4
    class ParentItemEntity {
        var title: String? = null
        var childItems: List<ChildItemEntity>? = null
    }
  • 具体餐点(ChildItemEntity.kt):
1
2
3
4
    class ChildItemEntity {
        var title: String? = null
        var isFavorite = false
    }

创建布局

   RecyclerView中的一个项目单元应该是“标题+若干菜品”,可以用一个固定的布局表示标题,而用一个空的LinearLayout表示该标题下菜品们的存放空间。之所以为空是由于菜品的数量未定。这里另外为每一道菜准备一个通用的布局,这样在读取数据集的时候就可以为菜品动态地添加相应数量的布局。

  • RecyclerView的项目单元(parent_item_layout.xml)
        效果:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_parent_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/orange_primary"
            android:paddingHorizontal="24sp"
            android:paddingVertical="16sp"
            android:textColor="@color/white"
            android:textSize="24sp"
            android:textStyle="bold"
            tools:text="主 食" />

        <!-- 用于存放子项的空LinearLayout -->
        <LinearLayout
            android:id="@+id/layout_child_items"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/text_parent_title"
            android:background="@color/white"
            android:orientation="vertical" />
    </RelativeLayout>
  • 每一道菜品的共通布局(child_item_layout.xml)
        效果:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/layout_child_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <View
            android:id="@+id/view_divider"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_above="@+id/text_child_title"
            android:background="@color/orange_dark" />

        <TextView
            android:id="@+id/text_child_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:paddingHorizontal="24dp"
            android:paddingVertical="12dp"
            android:textColor="@color/black"
            android:textSize="20sp"
            tools:text="苏格兰打卤面" />

        <CheckBox
            android:id="@+id/checkbox_favorite"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_centerVertical="true"
            android:button="@drawable/check_box_selector"
            android:buttonTint="@color/pink_primary" />
    </RelativeLayout>

用adapter绑定数据与视图

  • MainAdapter.kt
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    class MainAdapter(private val mList: List<ParentItemEntity>?, private val mContext: Context) :
        RecyclerView.Adapter<ParentViewHolder>() {
        // 使用ViewBinding
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParentViewHolder {
            val listBinding = ParentItemLayoutBinding.inflate(
                LayoutInflater.from(mContext), parent, false
            )
            return ParentViewHolder(listBinding)
        }

        // ViewHolder class
        inner class ParentViewHolder(val listBinding: ParentItemLayoutBinding) :
                RecyclerView.ViewHolder(
                        listBinding.root
                )

        override fun onBindViewHolder(holder: ParentViewHolder, position: Int) {
            // 该position对应的品类标题
            val item = mList!![position]
            holder.listBinding.textParentTitle.text = item.title

            // 每个品类下的菜品列表
            val childList = item.childItems
            // 每次添加子项之前清空LinearLayout。
            // 因为notifyDatasetChanged执行时会调用onBindViewHolder,如不清空列表会越来越长。
            holder.listBinding.layoutChildItems.removeAllViews() 
            for (id in childList!!.indices) {
                val childListBinding = ChildItemLayoutBinding.inflate(LayoutInflater.from(mContext), null, false)
                childListBinding.root.id = id
                childListBinding.textChildTitle.text = childList[id].title
                childListBinding.checkboxFavorite.isChecked = childList[id].isFavorite
                holder.listBinding.layoutChildItems.addView(childListBinding.root) //动态地向linearLayout添加一个菜品的布局
            }
        }
        // ......

   最后,以通常的方式向RecyclerView的Adapter传入ParentItemEntity型的列表,再向列表传入一些数据就可以查看效果啦。