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一样触发折叠,加上这一行代码后就可以了。

结尾

本文章中涉及的源码请查看项目源码
平时工作的过程中要注重源码的学习,要知其然,还要知其所以然,这样才能工作起来游刃有余。