前言
项目中需要实现一个支持手势拖拽控件,控件内部包含ViewPager+RecycleView。大概记录下最后解决问题的过程

Android recycleView滑动与bottomsheet 冲突解决_android

1.讨论,找资料

拿到出需求后,首先脑补了下场景,马上拉着小组成员头脑风暴一番,梳理出来几种可能满足的方案

1.在SmartRefresh 提供的类淘宝二楼功能上进行二次开发
2.自定义ViewGroup,或者基于布局,对需要上下滑动的View做手势处理
3.根据BottomSheet或者 BottomSheetFragmentDialog二次开发。

从难易程度上直观看起来说:BottomSheet->SmartRefresh->自定义ViewGroup,或者基于布局

eg:smartRefresh的二楼和需求出入较大,需求是上层View可拖动,下层View固定。

2.实现方案调研

BottomSheet 是官方前两年提供的很成熟控件,毫不费力就把BottomSheet相关配置引入

​BottomSheetDemo​

error:在项目中进行展示直接崩溃

java.lang.IllegalArgumentException: The view is not a child of CoordinatorLayout

网上找了一圈类似错误的索引。又检查了下自己写的代码发现自己的布局写成了androidx.constraintlayout.widget.ConstraintLayout 。而正确应该是要写成
androidx.coordinatorlayout.widget.CoordinatorLayout。

error :忘记定义app:layout_behavior

java.lang.IllegalArgumentException: The view is not associated with BottomSheetBehavior
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:behavior_fitToContents="false"
app:behavior_peekHeight="166dp"
app:layout_behavior="com.google.android.material.bottomsheet.ViewPagerBottomSheetBehavior">

之后熟悉了BottomSheetBehavior相关Api梳理出来几个比较好用的配置

1.app:behavior_peekHeight 设置折叠后的高度
2.BottomSheetBehavior.state 设置BottomSheet初始状态,共7种状态的

override fun onStateChanged(p0: View, newState: Int) {      
when (newState) {
STATE_DRAGGING -> {
println("STATE_DRAGGING =$STATE_DRAGGING")
}
STATE_SETTLING -> {
println("STATE_SETTLING=$STATE_SETTLING")
}
STATE_EXPANDED -> {
println("STATE_EXPANDED=$STATE_EXPANDED")
}
STATE_COLLAPSED -> {
println("STATE_COLLAPSED=$STATE_COLLAPSED")
}
STATE_HIDDEN -> {
println("STATE_HIDDEN=$STATE_HIDDEN")
}
STATE_HALF_EXPANDED -> {
println("STATE_HALF_EXPANDED=$STATE_HALF_EXPANDED")
}
PEEK_HEIGHT_AUTO -> {
println("PEEK_HEIGHT_AUTO=$PEEK_HEIGHT_AUTO")
}

}
}

3.BottomSheetBehavior.setBottomSheetCallback( 回调函数) 状态回调,偏移量回调
4. BottomSheetBehavior.expandedOffset
官方api解释 :解决距离顶部的偏移量

  • determines the top offset of the BottomSheet in the {@link #STATE_EXPANDED} state when
  • fitsToContent is false. The default value is 0, which results in the sheet matching the
  • parent’s top.

完成上面的配置后,让设计人员看了下效果还挺满意。

滑动时候发现刷新问题失效了。打眼一看就明白肯定是BottomSheet的滑动事件和RecycleView的滑动事件冲突导致的。网上搜索下类似问题,还挺多,心中窃喜。

​ViewPagerBottomSheet​

下载资源跑了下,确实解决了问题。紧接着将源码导入自己的项目后,发现ViewPagerUtils的layoutParams.position 报错,看了下原因:
自己项目中使用Androidx依赖,gitHub资源库还是使用Support.v4依赖。而最新的A呢droidx依赖库中的ViewPager.layoutParams.position 非公开。无法访问了

package android.support.v4.view;

import android.view.View;

public class ViewPagerUtils {

public static View getCurrentView(ViewPager viewPager) {
final int currentItem = viewPager.getCurrentItem();
for (int i = 0; i < viewPager.getChildCount(); i++) {
final View child = viewPager.getChildAt(i);
final ViewPager.LayoutParams layoutParams = (ViewPager.LayoutParams) child.getLayoutParams();
if (!layoutParams.isDecor && currentItem == **layoutParams.position**) {
return child;
}
}
return null;
}

}

自己项目两年前已经迁移到AndroidX了。总不能走倒退路。将依赖回退到V4的支持库吧 ​​关闭AndroidX依赖​​

梳理下解决方案:

1.回退到早期依赖私用支持库中的v4.ViewPager
2.拷贝v4.ViewPager,及相关类。
3.改变资源库依赖方式,减少影响范围,跟着报错进行解决
4.寻找替代方案: 采用反射方式获取非public 属性值跳过的无法访问限制。
5.阅读BottomSheet源码,copy一份源码进行改动
对方案1,2,3进行验证,发现需要对项目进行调整,影响范围比较广,而且小组内均不同意禁用AndroidX 相关依赖。
方案4.在不更改依赖情况下影响较小。发现确实可以拿到类中的方法和具体的属性,但是提取属性中的值时崩溃。采用反射方案后期也需要较多的维护,如果哪天position的属性换成其他值了。还需要进行多版本兼容。
方案5是终极解决方案,屡试不爽。花费的时间也无法评估全靠个人经验。(非必要,不会二次更改源码)

总结下:
目前对于冲突原因也知道,(因为BottomSheet只对第一个可以滑动的控件进行事件分发,如果有多个可以滑动的控件就会有滑动问题),也有网友提供的实例代码。没有理由解决不了

早期也总结过关于 ​​Android 嵌套布局简析​​

还是对源码比较紧张啊!!!

之后翻阅 ​​ViewPagerBottomSheet​​ 的Issue 问题时候看到有网友说在AndroidX 中无法使用该库,紧接着看到贴出来一个解决方案

解决方案: 只需集成BottomSheetBehavior,重写onStartNestedScroll(),重新给nestedScrollingChildRef 软引用赋值。就可以完美解决了

​原文链接​

class ViewPagerBottomSheetBehavior<V : View>(context: Context, attrs: AttributeSet?) : BottomSheetBehavior<V>(context, attrs) {

override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
nestedScrollingChildRef = WeakReference(target)
return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
}

其他问题

以上只是记录下遇到问题的解决思路,如果按照上述问题还有冲突。本文在解决完嵌套Viewpager无法滚动问题后,遇到RecycleView 滑动到底部后,向下滑动会连带BottomShee一起滑动问题。看了源码事件拦截部分,发现在BottomSheet 拖动状态,直接消费了事件不传递给子类。这里引用另一篇文章进行方案:
​解决BottomSheet滑动冲突问题​​ ,在BottomSheet 滑动回调中强制更改 STATE_DRAGGING 为 STATE_EXPANDED状态来规避事件分发引起的滑动冲突。

引用
​​​google BottomSheet ​​​

BottomSheetBehavior+ViewPager+RecyclerView引起的滑动冲突事件解决
​​​ 重写onStartNestedScroll ​