前言:继《RecycleView懒加载失效问题》之后,再介绍另一个场景,如下图。两层RecycleView,外层是竖向列表样式,内层是网格样式,由于内层网格个数不固定,需要内层RecycleView的根布局高度为wrap_content(注:这是导致内层RecycleView懒加载失效的原因)

android viewpage2 fragment懒加载 recyclerview懒加载_懒加载失效

问题:尽管第2项只显示了一小部分,但仍加载了全部数据,内层懒加载失效,当内层数据量很大,一次要全部加载,会感觉到卡顿。

分析:首先,分析为什么外层的RecycleView懒加载正常,而内层的RecycleView失效了呢?区别在于外层RecycleView高度是确定的,至少不会超过父控件,而内层根布局高度为wrap_content,即item没有对最大高度做限制,内层需要多大就赋予多大,于是就加载了全部内层的数据,我们可以先将内层的根布局高度设置为固定值验证一下,发现高度固定后内层也支持懒加载了,毕竟RecycleView高度限制了,超过高度的item就不加载了,但是这个就不满足我们的需求了,内层数据少的时候会留白,数据多的时候显示不全,而且只是外层响应滑动事件,内层无法响应,所有我们只能将内层根布局高度设置为wrap_content。

1、外层适配器

class TestPercentAdapter : RecyclerView.Adapter<TestPercentAdapter.TestPercentViewHolder>(){
    private val testPercentList = arrayListOf<TestPercentBean>()

    init {
        for (i in 1..20){
            testPercentList.add(TestPercentBean("外层第${i}项"))
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestPercentViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_test, parent, false)
        return TestPercentViewHolder(view)
    }

    override fun getItemCount(): Int {
        return testPercentList.size
    }

    override fun onBindViewHolder(holder: TestPercentViewHolder, position: Int) {
        holder.tvItem.text = testPercentList[position].mTitle
        holder.adapterTwo.testChildrenList = testPercentList[position].mChildrenList
        holder.adapterTwo.notifyDataSetChanged()
        Log.e("aa", "***************${testPercentList[position].mTitle}")
    }


   inner class TestPercentViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val tvItem = view.findViewById<TextView>(R.id.tv_item)
        val rvItem = view.findViewById<RecyclerView>(R.id.rv_item)
        val adapterTwo = TestChildrenAdapter()
        init {
            rvItem.layoutManager = GridLayoutManager(rvItem.context, 2)
            rvItem.adapter = adapterTwo
        }
    }
}

2、外层布局

<?xml version="1.0" encoding="utf-8"?>
<com.hualala.myapplication.MyLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tv_item"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        android:textColor="@color/colorAccent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_item"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</com.hualala.myapplication.MyLinearLayout>

3、内层适配器

class TestChildrenAdapter : RecyclerView.Adapter<TestChildrenAdapter.TestChildrenViewHolder>(){
    var testChildrenList = arrayListOf<TestPercentBean.TestChildrenBean>()


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestChildrenViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_test_two, parent, false)
        return TestChildrenViewHolder(view)
    }

    override fun getItemCount(): Int {
        return testChildrenList.size
    }

    override fun onBindViewHolder(holder: TestChildrenViewHolder, position: Int) {
        holder.tv_test.text = testChildrenList[position].mTitle
        Log.e("aa", "***********${testChildrenList[position].mTitle}")
    }

    class TestChildrenViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tv_test = itemView.findViewById<TextView>(R.id.tv_test)
    }
}

4、内层布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_test"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:gravity="center"
    android:textColor="@color/colorPrimaryDark"/>

5、主页布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_test"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintWidth_percent="0.5"/>
    <View
        android:layout_width="1px"
        android:layout_height="match_parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:background="#666666"/>
    <Button
        android:id="@+id/bt_update"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@+id/rv_test"
        android:layout_marginStart="10dp"
        android:layout_marginRight="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:gravity="center"
        android:text="更新"/>
</androidx.constraintlayout.widget.ConstraintLayout>

6、加载数据

rv_test.layoutManager = LinearLayoutManager(this)
        rv_test.adapter = TestPercentAdapter()
        bt_update.setOnClickListener { (rv_test.adapter as TestPercentAdapter).notifyDataSetChanged() }

8、实体类

class TestPercentBean constructor(title: String){
    val mTitle = title
    val mChildrenList = ArrayList<TestChildrenBean>()
    init {
        for (i in 1..(Math.random() * 100).toInt()){
            mChildrenList.add(TestChildrenBean("内层第{$i}项"))
        }
    }
    
    class TestChildrenBean constructor(title: String){
        val mTitle = title
    }
}

方案:分析了原因,有了初步的方案,需要满足这两个条件即可解决:1、给内部RecycleView设置最大高度为外层RecycleView高度(解决懒加载,最多加载条数进行了限制,不会无限大) 2、滑动冲突(这部分太过于复杂,导致最终放弃了该方案,可以想一下我点击在第一项内层的RecycleView上向上滑动,由于已经都显示了无需再加载了需要把事件交给外层RecycleView,但当第2项内层RecycleView全部显示出来时,又要将事件给第2项内层RecycleView继续加载剩下的数据,全部显示后再将事件交给外层RecycleView——即内层RecycleView布局未全部显示出来时外层响应滑动,全部显示则内层响应,如果内层滑动到第1item或最后item,则再由外层响应

简单方案:考虑上面的方案复杂,那就换一种思路,用单层RecycleView实现,则需要网格支持动态分配列数,当为外层数据时为1列,为内层数据时为2列。

通过GridLayoutManager.SpanSizeLookup实现动态列数

1、GridLayoutManager第2个参数spanCount为每行列数的最小公倍数
2、getSpanSize返回值为spanCount除以该行列数

1、适配器

class TestAdapter : RecyclerView.Adapter<TestAdapter.TestViewHolder>() {

    var testList = ArrayList<TestBean>()

    init {
        for (i in 1..20) {
            testList.add(TestBean("外层第${i}项", true))
            for (j in 1..(Math.random() * 100).toInt()) {
                testList.add(TestBean("内层第${j}项", false))
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestViewHolder {
        val view =
            LayoutInflater.from(parent.context).inflate(R.layout.item_test_two, parent, false)
        return TestViewHolder(view)
    }

    override fun getItemCount(): Int {
        return testList.size
    }

    override fun onBindViewHolder(holder: TestViewHolder, position: Int) {
        holder.tv_test.text = testList[position].mTitle
        if (testList[position].mIsPercent){
            holder.tv_test.setTextColor(holder.tv_test.resources.getColor(R.color.colorAccent))
        }else{
            holder.tv_test.setTextColor(holder.tv_test.resources.getColor(R.color.colorPrimaryDark))
        }
        Log.e("aa", "***********${testList[position].mTitle}")
    }

    class TestViewHolder constructor(view: View) : RecyclerView.ViewHolder(view) {
        val tv_test = itemView.findViewById<TextView>(R.id.tv_test)
    }
}

2、子项布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_test"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:gravity="center"
    android:textColor="@color/colorPrimaryDark"/>

3、实体类

class TestBean(title: String, isPercent: Boolean) {
    val mTitle = title
    val mIsPercent = isPercent
}

4、加载数据

/* 1、GridLayoutManager第2个参数spanCount为每行列数的最小公倍数
 * 2、getSpanSize返回值为spanCount除以该行列数
 * */
val gridLayoutManager = GridLayoutManager(this, 2)
        val testAdapter = TestAdapter()
        gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup(){
            override fun getSpanSize(position: Int): Int {
                return if (testAdapter.testList[position].mIsPercent) 2 else 1
            }
        }
        rv_test.layoutManager = gridLayoutManager
        rv_test.adapter = testAdapter
        bt_update.setOnClickListener { (rv_test.adapter as TestAdapter).notifyDataSetChanged() }