Android开发中RecyclerView因为扩展性强,导致使用很广泛,而且效果也很酷炫,比如
AppBarLayout+CollapsingToolbarLayout
的使用可以达到很酷炫的折叠效果,今天我们不学习这些控件的具体的使用,今天我们来看一个在实际工作中会遇到的AppBarLayout+CoordinatorLayout+RecyclerView(其中包含横向滑动的RecyclerView)
滑动冲突的问题。
问题复现
- 首先来看一下动态图的问题吧(这个图中顶部是一个ImageView,下面是整体是一个RecyclerView,其中包含横向可滑动的RecyclerView),如图所示滑动下面的item时候,顶部的ImageView并不会自动折叠,下面我们来看一下代码和解决方式:
- 首先看一下页面布局,从布局看出来使用的是
AppBarLayout+CoordinatorLayout+RecyclerView
的形式:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:fitsSystemWindows="true"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:collapsedTitleTextAppearance="@style/AppBarTitle"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleTextAppearance="@style/TransparentTitle"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed|exitUntilCollapsed"
app:title="@string/app_name"
app:toolbarId="@id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@drawable/banner"
app:layout_constraintDimensionRatio="2.048:1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.appcompat.widget.Toolbar
...........................
app:titleTextAppearance="@style/AppBarTitle" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/appList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 我们看一下界面Activity的布局:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
toolbar.title = "Home"
//模拟recyclerview的数据
val categories = resources.getStringArray(R.array.categories).toList()
val names = resources.getStringArray(R.array.names).toList()
val namesAll = listOf(names, names, names, names, names)
val appList = findViewById<RecyclerView>(R.id.appList)
// 适配数据的Adapter
val adapter = TestAdapter(this, namesAll, categories)
//Adapter采用的是竖向
val manager = LinearLayoutManager(this)
appList.layoutManager = manager
appList.adapter = adapter
}
}
- 我们看一下
TestAdapter
的内容:
class TestAdapter(
private val context: Context,
private val apps: List<List<String>>,
private val categories: List<String>,
private val inflater: LayoutInflater = LayoutInflater.from(context))
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@IntDef(ITEM_TYPE_CATEGORY, ITEM_TYPE_CAROUSAL)
@Retention(AnnotationRetention.SOURCE)
private annotation class ItemType
override fun onCreateViewHolder(parent: ViewGroup, @ItemType viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_TYPE_CATEGORY) {
CategoryViewHolder(inflater.inflate(R.layout.item_category, parent, false))
} else {
CarousalViewHolder(inflater.inflate(R.layout.carousel, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is CategoryViewHolder) {
holder.bind(position)
} else if (holder is CarousalViewHolder) {
holder.bind(position)
}
}
override fun getItemViewType(position: Int): Int {
return if (position % 2 == 0) ITEM_TYPE_CATEGORY else ITEM_TYPE_CAROUSAL
}
override fun getItemCount(): Int {
return apps.size * 2
}
inner class CategoryViewHolder(container: View) : RecyclerView.ViewHolder(container) {
private val titleView: TextView = container.findViewById(R.id.category)
internal fun bind(position: Int) {
val title = categories[position / 2]
val list = apps[position / 2]
if (list.isEmpty()) return
titleView.text = title
}
}
inner class CarousalViewHolder(container: View) : RecyclerView.ViewHolder(container) {
private val appList: RecyclerView = container.findViewById(R.id.appList)
fun bind(position: Int) {
val manager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
val data = apps[position % 2]
val adapter = CarousalAdapter(context, data)
appList.layoutManager = manager
appList.adapter = adapter
}
}
....................
}
- 到这里主要的代码就写完了,出现的问题就是上面动图上面的问题,滑动的时候顶部的图片不随着滑动折叠起来。
解决问题
- 经过了很多尝试,包括自定义解决滑动冲突等,但是后来都不及下面这个效果好,其实后来经过查看源码发现解决问题的代码也很简单,仅仅只需要一行代码就可以了,就是
对内部嵌套的横向滑动的RecyclerView设置它的setNestedScrollingEnabled(false)即可
,如下所示:
fun bind(position: Int) {
val manager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
val data = apps[position % 2]
val adapter = CarousalAdapter(context, data)
// 仅仅加上下面这句就可以了
appList.isNestedScrollingEnabled = false
appList.layoutManager = manager
appList.adapter = adapter
}
- 效果如下图:
分析感悟
通过查看源码得知,CoordinatorLayout
实现了NestedScrollingParent2
, 外层的RecyclerView是CoordinatorLayout的子类
,滑动的时候会通知CoordinatorLayout
,进而由其协调CollapsingToolbarLayout
发生折叠。而内部嵌套的横向RecyclerView
只是实现了NestedScrollingChild2
, 属于外层RecyclerView
的子类, 如果不关闭横向滑动的嵌套滑动功能,就不能像其它纵向嵌入的View一样触发折叠
,加上这一行代码后就可以了。
结尾
本文章中涉及的源码请查看项目源码。
平时工作的过程中要注重源码的学习,要知其然,还要知其所以然,这样才能工作起来游刃有余。