解决折叠状态栏+viewPager2嵌套带有下拉刷新的RecyclerView滑动冲突

CoordinatorLayout+AppBarLayout+viewPager2+fragment+下拉刷新的RecyclerView

下滑
在这里插入图片描述
上滑
在这里插入图片描述
当手指下滑的时候,因为AppBarLayout还没有折叠,滑动事件会被PtrFrameLayout消费,但上滑是如果AppBarLayout还没有折叠,滑动事件也会被PtrFrameLayout消费但和AppBarLayout.Behavior的滑动产生冲突,会发生滑动抖动,网上说重写AppBarLayout.Behavior可以解决,或者重写PtrFrameLayout,对上下滑动事件进行拦截,也可以。
这个涉及安卓事件分发机制,不会的朋友可以搜一下。
当下滑时让PtrFrameLayout自行消费,上滑时进行拦截。
以上是我个人理解,有错误希望大佬指出。
上代码。
fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    
    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".module.home.HomeFragment">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/appBarLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:layout_scrollFlags="scroll|enterAlways|snap">
                <LinearLayout
                    android:id="@+id/ll_start_search"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/tv_title"
                        android:layout_width="36dp"
                        android:layout_height="36dp"
                        android:layout_gravity="left|center"
                        android:layout_marginLeft="16dp"
                        android:background="@drawable/ic_search_gray_24dp"
                        android:textColor="@color/textColorPrimary" />

                    <EditText
                        android:id="@+id/et_search"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginLeft="16dp"
                        android:layout_marginTop="6dp"
                        android:layout_marginRight="16dp"
                        android:layout_marginBottom="3dp"
                        android:maxLines="1"
                        android:background="@drawable/gray_rounded_shape"
                        android:drawableLeft="@drawable/ic_search_gray_24dp"
                        android:drawablePadding="8dp"
                        android:hint="do you need?"
                        android:padding="9dp"
                        android:textColorHint="#9ea1b0" />
                </LinearLayout>
            </com.google.android.material.appbar.CollapsingToolbarLayout>



            <net.lucode.hackware.magicindicator.MagicIndicator
                android:id="@+id/magic_indicator"
                android:layout_width="match_parent"
                android:layout_height="36dp"           
                android:paddingLeft="16dp"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:tabMode="scrollable" />

        </com.google.android.material.appbar.AppBarLayout>


            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/viewpager2_classify"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>

</layout>

重写PtrFrameLayout


class PullRefreshLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : PtrFrameLayout(context, attrs, defStyleAttr), PtrUIHandler {

    private val viewBinding = ViewRecyclerHeaderBinding.inflate(LayoutInflater.from(context), this, false)
    private var onPullRefreshListener: OnPullRefreshListener? = null

    companion object {
        private const val TAG = "PullRefreshLayout"
    }

    init {
        disableWhenHorizontalMove(true)
        isKeepHeaderWhenRefresh = true
        setRatioOfHeaderHeightToRefresh(1F)
        headerView = viewBinding.root
        addPtrUIHandler(this)
        setPtrHandler(
            object : PtrDefaultHandler() {
                override fun onRefreshBegin(layout: PtrFrameLayout) {
                    onPullRefreshListener?.onRefreshBegin()
                }
            })
    }

    override fun onUIReset(layout: PtrFrameLayout) {

    }

    override fun onUIRefreshPrepare(layout: PtrFrameLayout) {
        Log.d(TAG, "onUIRefreshPrepare")
        viewBinding.tvRefreshState.setText(R.string.refresh_pull_down_to_refresh)
    }

    override fun onUIRefreshBegin(layout: PtrFrameLayout) {
        Log.d(TAG, "onUIRefreshBegin")
        viewBinding.tvRefreshState.setText(R.string.refresh_refreshing)
    }

    override fun onUIRefreshComplete(layout: PtrFrameLayout) {
        Log.d(TAG, "onUIRefreshComplete")
        viewBinding.tvRefreshState.setText(R.string.refresh_refresh_complete)
    }

    override fun onUIPositionChange(layout: PtrFrameLayout, isUnderTouch: Boolean, status: Byte, ptrIndicator: PtrIndicator) {
        val offsetToRefresh: Int = layout.offsetToRefresh
        val currentPos = ptrIndicator.currentPosY
        val lastPos = ptrIndicator.lastPosY

        if (currentPos < offsetToRefresh && lastPos >= offsetToRefresh) {
            if (isUnderTouch && status == PTR_STATUS_PREPARE) {
                viewBinding.tvRefreshState.setText(R.string.refresh_pull_down_to_refresh)
            }
        } else if (currentPos > offsetToRefresh && lastPos <= offsetToRefresh) {
            if (isUnderTouch && status == PTR_STATUS_PREPARE) {
                viewBinding.tvRefreshState.setText(R.string.refresh_release_to_refresh)
                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
            }
        }
    }

    fun setOnPullRefreshListener(listener: OnPullRefreshListener) {
        this.onPullRefreshListener = listener
    }

    interface OnPullRefreshListener {
        fun onRefreshBegin()
    }

    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {

        val x: Float = ev.getX()
        val y: Float = ev.getY()
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                //将按下时的坐标存储
                downX = x
                downY = y
            }
            MotionEvent.ACTION_MOVE -> {
                //获取到距离差
                val dx: Float = x - downX
                val dy: Float = y - downY
                //通过距离差判断方向
                val orientation: Int = getOrientation(dx, dy)
                when (orientation) {
                    UP ->{
                        return super.dispatchTouchEventSupper(ev)
                    }
                }
            }

        }
        return super.dispatchTouchEvent(ev)
    }

    override fun dispatchTouchEventSupper(ev: MotionEvent): Boolean {

        val x: Float = ev.getX()
        val y: Float = ev.getY()
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                //将按下时的坐标存储
                downX = x
                downY = y
            }
            MotionEvent.ACTION_MOVE -> {
                //获取到距离差
                val dx: Float = x - downX
                val dy: Float = y - downY
                //通过距离差判断方向
                val orientation: Int = getOrientation(dx, dy)
                when (orientation) {
                    UP ->{
                        return super.dispatchTouchEventSupper(ev)
                    }
                }
            }

        }
        return super.dispatchTouchEventSupper(ev)
    }

    private var downX: Float = 0f//按下时 的X坐标
    private var downY: Float = 0f//按下时 的Y坐标
    private val LEFT: Int = 0
    private val RIGHT: Int = 1
    private val UP: Int = 2
    private val DOWN: Int = 3
    private fun getOrientation(dx: Float, dy: Float): Int {
        return if (Math.abs(dx) > Math.abs(dy)) {
            //X轴移动
            if (dx > 0) RIGHT else LEFT //右,左
        } else {
            //Y轴移动
            if (dy > 0) DOWN else UP //下//上
        }
    }

view_recycler_header.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="80dp">

        <TextView
            android:id="@+id/tv_refresh_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:fontFamily="sans-serif-medium"
            android:text="@string/refresh_pull_down_to_refresh"
            android:textSize="15sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在使用ViewPager2嵌套RecyclerView时,建议使用FragmentStateAdapter来设置ViewPager2的适配器,并在每个Fragment中使用RecyclerView。这样可以确保在滑动ViewPager2时,RecyclerView能够正确地重用并显示不同的数据。 同时,由于RecyclerView默认会拦截滑动事件,导致ViewPager2滑动失效,因此需要在RecyclerView中禁用滑动事件的拦截。可以通过设置RecyclerView的NestedScrollingEnabled属性为false来实现,即recyclerView.setNestedScrollingEnabled(false)。 另外,还需要在RecyclerView的Adapter中实现getItemCount()和getItemViewType()方法,并根据需要显示不同的布局类型。在使用多个RecyclerView的情况下,建议使用不同的ViewType来避免布局重复和数据错乱等问题。 总之,正确地使用ViewPager2RecyclerView嵌套需要考虑多方面的因素,包括适配器、布局、事件处理等等。需要仔细思考和实践,才能达到最佳效果。 ### 回答2: ViewPager2Android 系统中的一个控件,可以用来创建包含多个页面的用户界面。而 RecyclerView 则是一个用于显示大量数据列表的控件。嵌套 ViewPager2RecyclerView 可以带来更加丰富的用户界面和更好的交互体验。 在将 RecyclerView 嵌套ViewPager2 中时,需要注意以下几点: 1. 使用 FragmentStateAdapter 或 RecyclerView.Adapter ViewPager2 中的每一页都可以是一个 Fragment 或 View,我们可以使用 FragmentStateAdapter 或 RecyclerView.Adapter 作为 ViewPager2 的数据源。如果我们使用 RecyclerView.Adapter,可以创建多个 RecyclerView,每个 RecyclerView 显示不同的数据列表,而每个列表可以是独立的数据流。而使用 FragmentStateAdapter,我们可以创建不同的 Fragment,每个 Fragment 显示自己独立的数据流。 2.设置recyclerView为可滑动 当我们将 RecyclerView 嵌套ViewPager2 中时,需要为 RecyclerView 设置合适的滑动方式。默认情况下,RecyclerView 会拦截 ViewPager2滑动事件,导致 ViewPager2滑动失效。我们可以使用 setNestedScrollingEnabled 方法为 RecyclerView 开启嵌套滑动,或使用 ViewPager2.OnPageChangeCallback 监听 ViewPager2滑动事件,并通过调用 RecyclerView 的 scrollBy 和 scrollToPosition 方法使得 RecyclerView 能够正确滑动。 3.注意 RecyclerView 的布局 在将 RecyclerView 嵌套ViewPager2 中时,需要给 RecyclerView 设置适当的布局,以免出现滑动冲突、数据显示过大等问题。我们可以对 RecyclerView 进行水平或垂直的滚动,但需要注意 RecyclerView 的布局高度。 综上,ViewPager2RecyclerView 的组合可以带来更加丰富和高效的用户界面和交互体验。它可以用于显示各种类型的列表数据,并通过 ViewPager2 的分页显示功能提供更好的用户体验。但在使用时如上文所述,需要注意一些细节问题。 ### 回答3: ViewPager2RecyclerView都是Android中常用的控件之一。ViewPager2是一个可滑动的容器,常用于页面之间的切换和滑动RecyclerView是一个高度可定制的列表工具,可用于呈现大量数据,并提供了很多的回收和性能优化功能。 在某些场景下,需要ViewPager2嵌套RecyclerView来实现滑动和展示数据的需求,这种需求可能出现在新闻客户端中,每个tab对应一种类型的新闻,每种类型的新闻数据量很大。这时候就可以考虑使用ViewPager2嵌套RecyclerView来优化用户体验和性能。 具体的实现方法如下: 1、创建一个Activity或Fragment来承载ViewPager2; 2、在ViewPager2中添加多个Fragment,每个Fragment都对应一个tab,包含一个RecyclerView; 3、在Fragment中创建一个合适的适配器类Adapter; 4、在Adapter中重写onCreateViewHolder、onBindViewHolder和getItemCount等方法,并将RecyclerView需要的数据进行绑定; 5、在使用RecyclerView时考虑合适的数据源和异步加载等优化。将RecyclerView中的数据源从Main Thread中移除,使用异步线程进行数据的加载和显示; 6、在ViewPager2中添加TabLayout用于切换不同的Fragment。 需要注意的是,ViewPager2嵌套RecyclerView能够实现数据的高效切换和渐变。为了更好地优化性能,应该尽量减少RecyclerView嵌套层数,并考虑分页加载等策略来优化加载速度和性能。 总之,ViewPager2嵌套RecyclerView是一种常用的Android开发技术,可以使用它来优化用户体验和性能,提高应用的质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九狼JIULANG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值