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