Android 中解决 RecyclerView 和子控件之间的滑动冲突问题

一、简述滑动冲突问题

在 Android 开发中,RecyclerView 和其子控件之间的滑动冲突是一个常见的问题。这种冲突通常发生在 RecyclerView 的子项本身也支持滑动操作时,例如子项是一个 ProgressBar、WebView 或其他自定义的滑动视图。当用户在子控件上滑动时,可能会触发 RecyclerView 的滑动,从而导致子控件滑动异常。

二、解决方案

  • requestDisallowInterceptTouchEvent 是 Android 中一个非常重要的方法,用于解决嵌套滑动(Nested Scrolling)或滑动冲突问题。它允许子视图(如 ScrollView、WebView 等)告诉父视图(如 RecyclerView、ViewPager 等)不要拦截当前的触摸事件,从而确保子视图能够正确处理这些事件。
  • 当调用 requestDisallowInterceptTouchEvent(true) 时,父视图的 onInterceptTouchEvent 方法将不会拦截当前的触摸事件。这意味着触摸事件会直接传递给子视图,而不是被父视图处理。

三、示例(RecyclerView 和 子控件 ProgressBar)

假设你有一个 RecyclerView,其中的每个子项包含一个可滑动的 ProgressBar 横向进度条。如果没有调用 requestDisallowInterceptTouchEvent(true),当用户在 ProgressBar 上滑动时,RecyclerView 可能会拦截这些事件,导致 ProgressBar 无法正常滑动。通过在 ProgressBar 的 onTouchEvent 中调用 requestDisallowInterceptTouchEvent(true),可以确保 ProgressBar 能够正常处理滑动事件。

1、子项布局文件 item_recycleview.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:padding="20dp">

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        style="?android:attr/progressBarStyleHorizontal"
        android:max="100"
        android:progress="50"/>

</androidx.constraintlayout.widget.ConstraintLayout>

2、适配器类 CustomAdapter.kt

在子控件的 onTouchEvent 方法中根据触摸事件类型来调用 requestDisallowInterceptTouchEvent 方法:

  • 在触摸事件开始时(MotionEvent.ACTION_DOWN),调用 requestDisallowInterceptTouchEvent(true),通知父控件不要拦截事件。
  • 在触摸事件结束时( MotionEvent.ACTION_UP 或 MotionEvent.ACTION_CANCEL),调用 requestDisallowInterceptTouchEvent(false),允许父控件重新拦截触摸事件。
package com.example.helloworld

import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import androidx.recyclerview.widget.RecyclerView

class CustomAdapter: RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
    
    @SuppressLint("ClickableViewAccessibility")
    class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
        private val progressBar: ProgressBar = itemView.findViewById(R.id.progress_bar)

        init {
            var lastX = 0f
            progressBar.setOnTouchListener { view, motionEvent ->
                when (motionEvent.action) {
                    MotionEvent.ACTION_DOWN -> {
                        // 当触摸事件发生时,通知父视图不要拦截触摸事件
                        progressBar.parent.requestDisallowInterceptTouchEvent(true)
                    }
                    MotionEvent.ACTION_UP -> {
                        // 当触摸事件发生时,通知父视图可以拦截触摸事件
                        progressBar.parent.requestDisallowInterceptTouchEvent(false)
                    }
                    MotionEvent.ACTION_MOVE -> {
                        if (motionEvent.x > lastX) {
                            progressBar.progress += 2
                        } else {
                            progressBar.progress -= 2
                        }
                        lastX = motionEvent.x
                    }
                }
                true
            }
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycleview, parent, false)
        return ViewHolder(view)
    }

    override fun getItemCount(): Int {
        return 5
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {

    }
}

3、Activity 的布局文件 activity_scroll_conflict.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="300dp"
        android:layout_height="100dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="20dp"
        android:layout_marginStart="20dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

4、ScrollConflictActivity.kt 文件

package com.example.helloworld

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.helloworld.databinding.ActivityScrollConflictBinding

class ScrollConflictActivity: AppCompatActivity() {

    private lateinit var _binding: ActivityScrollConflictBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityScrollConflictBinding.inflate(layoutInflater)
        setContentView(_binding.root)

        _binding.recyclerView.adapter = CustomAdapter()
        _binding.recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
    }
}

四、其他注意事项

  • 性能考虑:频繁调用 requestDisallowInterceptTouchEvent 可能会影响性能,尤其是在复杂的布局中,建议仅在必要时调用此方法。
  • 嵌套滚动支持:如果子控件支持嵌套滚动(如 NestedScrollView),可以使用 NestedScrollingParent 和 NestedScrollingChild 接口来实现更复杂的嵌套滚动逻辑。

你可能感兴趣的:(新起点,Android,android)