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一样触发折叠
,加上这一行代码后就可以了。
结尾
本文章中涉及的源码请查看项目源码。
平时工作的过程中要注重源码的学习,要知其然,还要知其所以然,这样才能工作起来游刃有余。