手动实现滑动冲突场景并解决

122 阅读3分钟

最近在看到滑动冲突的两种解决方案即内部拦截法和外部拦截法收获颇丰,故想制造一个滑动冲突的场景并使用这两种方法解决。

滑动的场景其实很好制造,就是一个ViewGroup里面嵌套一个View或者ViewGroup都有一个方向的滑动功能即可,这里我很容易就想到了使用RecycleView嵌套RecycleView来实现该场景。

首先我们在activity的布局下直接实现一个可以横向移动的RecycleView,布局文件如下:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv1"
        android:layout_margin="10dp"
        android:layout_width="match_parent"
        android:layout_height="400dp"/>

</RelativeLayout>

activity中的是代码如下


val testData : ArrayList<TestData> = arrayListOf(
    TestData("111","one"),
    TestData("222","two"),
    TestData("333","three"),
    TestData("444","four"),
    TestData("555","five"),
    TestData("666","six"),
    TestData("777","seven"),
    TestData("888","eight"),
)

val linearLayoutManager1 = LinearLayoutManager(this)
linearLayoutManager1.orientation = LinearLayoutManager.HORIZONTAL
rv1.layoutManager = linearLayoutManager1
mAdapter = TestAdapter(this)
rv1.adapter = mAdapter
mAdapter!!.setDataList(testData)

这个RecycleView的适配器代码如下,在这个RecycleView里面嵌套一个RecycleView,这个嵌套使用的RecycleView也是一个横向的

class TestAdapter(context: Context) : RecyclerView.Adapter<TestAdapter.TestViewHolder>() {

    private val mContext = context
    private var dataList = ArrayList<TestData>()
    private var testAdapter : CommonAdapter? = null

    fun setDataList(dataList : ArrayList<TestData>){
        this.dataList = dataList
        notifyDataSetChanged()
    }

    inner class TestViewHolder(itemView : View): RecyclerView.ViewHolder(itemView){
        var test1 = itemView.siteNameTv
        var test2 = itemView.valueTv
        var test3 = itemView.rv2
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestAdapter.TestViewHolder {
        return TestViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_test,parent,false))
    }

    override fun onBindViewHolder(holder: TestAdapter.TestViewHolder, position: Int) {
        val data = dataList[position]

        holder.test1.text = data.id
        holder.test2.text = data.name
        
        val linearLayoutManager2 = LinearLayoutManager(mContext)
        linearLayoutManager2.orientation = LinearLayoutManager.HORIZONTAL

        holder.test3.layoutManager = linearLayoutManager2
        testAdapter = CommonAdapter(mContext)
        holder.test3.adapter = testAdapter
        testAdapter!!.setDataList(dataList)
    }

    override fun getItemCount(): Int = dataList.size
}

该RecycleView的item的布局文件如下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/siteNameTv"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="2"
            android:text="123"
            android:gravity="center"
            android:padding="10dp"
            android:textStyle="bold"
            />

        <TextView
            android:id="@+id/valueTv"
            android:layout_width="0dp"
            android:layout_height="50dp"
            android:layout_weight="2"
            android:text="456"
            android:gravity="center"
            android:padding="10dp"
            android:textStyle="bold"
            />
    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv2"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/grey"
        />
</LinearLayout>

在外部RecycleView的onBindViewHolder方法中对内部RecycleView进行适配器的绑定,CommonAdapter就是TestAdapter去掉RecycleView的一个适配器,运行这个嵌套的RecycleView之后会发现无论你滑动外部的RecycleView区域还是滑动内部的RecycleView都只有外部的RecycleView响应。

其实也很好理解,因为根据事件分发规则,从你的ACTION_DOWN到ACTION_MOVE这一系列动作事件在外部就已经消耗掉了所以内部的RecycleView不响应是正常的,这样就算一个滑动冲突的场景了。

首先我们使用外部拦截法,我们自定义一个RecycleView为CustomRecyclerView,根据外部拦截法直接重写onInterceptTouchEvent方法并返回false,表示不拦截所有动作事件,替换外部RecycleView后运行

class CustomRecyclerView constructor(context: Context
                                     ,attrs: AttributeSet?) : RecyclerView(context,attrs) {

    override fun onInterceptTouchEvent(e: MotionEvent?): Boolean {
        return false
    }

//    override fun onTouchEvent(e: MotionEvent?): Boolean {
//        return false
//    }
}

滑动外部RecycleView区域外部RecycleView响应滑动,滑动内部RecycleView区域内部RecycleView响应滑动,效果达到。

这时候又来一个需求,外部RecycleView不需要响应滑动事件,这个和我们在App中的NoScrollViewPager一样了,即外部ViewGroup不响应任何动作事件,直接重写onTouchEvent方法返回false即可,运行试试效果确实如此。

外部拦截法说完了我们使用内部拦截法试试看吧,同样是自定义RecycleView为CommonRecycleView,重写dispatchTouchEvent方法,替换内部RecycleView后运行

class CommonRecyclerView constructor(context: Context
                                     ,attrs: AttributeSet?) : RecyclerView(context,attrs){

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        parent.requestDisallowInterceptTouchEvent(true)
        return super.dispatchTouchEvent(ev)
    }
}

效果达到!如果想实现外部RecycleView不响应动作事件只需要重写onTouchEvent方法返回false即可。

当内部View调用parent.requestDisallowInterceptTouchEvent(false)时,外部View才能继续拦截所需事件。

这里注意一点外部View不能拦截ACTION_DOWN事件,因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦外部View拦截ACTION_DOWN事件,那么所有的事件都无法传递到内部View中去,这样内部拦截法就无法起作用了。