CoordinatorLayout和RecyclerView嵌套滑动冲突解决

Android开发中RecyclerView因为扩展性强,导致使用很广泛,而且效果也很酷炫,比如AppBarLayout+CollapsingToolbarLayout的使用可以达到很酷炫的折叠效果,今天我们不学习这些控件的具体的使用,今天我们来看一个在实际工作中会遇到的AppBarLayout+CoordinatorLayout+RecyclerView(其中包含横向滑动的RecyclerView)滑动冲突的问题。

问题复现

  • 首先来看一下动态图的问题吧(这个图中顶部是一个ImageView,下面是整体是一个RecyclerView,其中包含横向可滑动的RecyclerView),如图所示滑动下面的item时候,顶部的ImageView并不会自动折叠,下面我们来看一下代码和解决方式:
    CoordinatorLayout和RecyclerView嵌套滑动冲突
  • 首先看一下页面布局,从布局看出来使用的是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和RecyclerView嵌套滑动冲突

分析感悟

通过查看源码得知,CoordinatorLayout实现了NestedScrollingParent2, 外层的RecyclerView是CoordinatorLayout的子类,滑动的时候会通知CoordinatorLayout,进而由其协调CollapsingToolbarLayout发生折叠。而内部嵌套的横向RecyclerView只是实现了NestedScrollingChild2, 属于外层RecyclerView的子类, 如果不关闭横向滑动的嵌套滑动功能,就不能像其它纵向嵌入的View一样触发折叠,加上这一行代码后就可以了。

结尾

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

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值