代码先锋网 代码片段及技术文章聚合

ViewPager2中嵌套使用ViewPager2或RecyclerView造成内部不能滑动问题

技术标签: Java  Android  android  android studio  java

文档:官网文档
解决问题的答案在上面文档链接中已有解决办法。不想读下文的朋友可以直接阅览。

ViewPager2是官方大力推荐取代老ViewPager的方案,很多朋友还没有使用过,这里算是安利一波吧,早用早得劲。。

首先ViewPager2已经从根上就不同于ViewPager了,它内部是通过RecyclerView实现的,因为RecyclerView的强大灵活特性,所以ViewPager2自然也要比ViewPager灵活的多。例举2个我认为ViewPager2最实用的特性(因为我用的比较频繁):

1、orientation:这个属性在LinearLayout中应该很熟悉了,ViewPager2也同过这个属性支持修改方向。
2、动态修改Fragment 集合:这个要比viewPager方便太多了,可以直接调用ViewPager2的adapter.notifyDatasetChanged()方法更新。

而且也同样支持与一些三方tab组件配合使用,TabLayout就不说了,举例一个与MagicIndicator配合使用时要注意的点:
因为MagicIndicator自带的ViewPagerHelper只支持绑定ViewPager:

public class ViewPagerHelper {
    public static void bind(final MagicIndicator magicIndicator, ViewPager viewPager) {
        viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                magicIndicator.onPageScrolled(position, positionOffset, positionOffsetPixels);
            }

            @Override
            public void onPageSelected(int position) {
                magicIndicator.onPageSelected(position);
            }

            @Override
            public void onPageScrollStateChanged(int state) {
                magicIndicator.onPageScrollStateChanged(state);
            }
        });
    }
}

这里只需自己修改一下这个bind方法(我代码用的kotlin,直接粘了):

object ViewPager2Helper {
    fun bind(magicIndicator: MagicIndicator, viewPager: ViewPager2) {
        viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels)
                magicIndicator.onPageScrolled(position, positionOffset, positionOffsetPixels)
            }

            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                magicIndicator.onPageSelected(position)
            }

            override fun onPageScrollStateChanged(state: Int) {
                super.onPageScrollStateChanged(state)
                magicIndicator.onPageScrollStateChanged(state)
            }
        })
    }
}

最后把文章的问题答案摘出来:如何解决ViewPager2中嵌套使用ViewPager2或RecyclerView造成内部不能滑动问题呢?
一句话:创建一个 NestedScrollableHost 自定义view类。包裹住内部的滚动视图,例如:

<路径名.NestedScrollableHost
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
            
</路径名.NestedScrollableHost>

解决思路:由于ViewPager2 是在 RecyclerView 的基础上构建的,ViewPager2 本身并不支持内部同方向的滚动视图。与老ViewPager不同,ViewPager2是一个final类,无法继承它创建子类,也就是无法修改它的内部手势处理。所以只能在嵌套的view中对 ViewPager2 对象调用 requestDisallowInterceptTouchEvent()来解决同方向的手势冲突。这里不太了解android手势传递过程的可以先去了解一下,我之前也写了一篇简要文章《Android》事件传递过程可以了解一下。最后官方提供了一种使用通用自定义封装容器布局解决此问题的办法。下面贴出NestedScrollableHost代码:

/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign

/**
 * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
 * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
 * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
 *
 * This solution has limitations when using multiple levels of nested scrollable elements
 * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
 */
class NestedScrollableHost : FrameLayout {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)

    private var touchSlop = 0
    private var initialX = 0f
    private var initialY = 0f
    private val parentViewPager: ViewPager2?
        get() {
            var v: View? = parent as? View
            while (v != null && v !is ViewPager2) {
                v = v.parent as? View
            }
            return v as? ViewPager2
        }

    private val child: View? get() = if (childCount > 0) getChildAt(0) else null

    init {
        touchSlop = ViewConfiguration.get(context).scaledTouchSlop
    }

    private fun canChildScroll(orientation: Int, delta: Float): Boolean {
        val direction = -delta.sign.toInt()
        return when (orientation) {
            0 -> child?.canScrollHorizontally(direction) ?: false
            1 -> child?.canScrollVertically(direction) ?: false
            else -> throw IllegalArgumentException()
        }
    }

    override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
        handleInterceptTouchEvent(e)
        return super.onInterceptTouchEvent(e)
    }

    private fun handleInterceptTouchEvent(e: MotionEvent) {
        val orientation = parentViewPager?.orientation ?: return

        // Early return if child can't scroll in same direction as parent
        if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
            return
        }

        if (e.action == MotionEvent.ACTION_DOWN) {
            initialX = e.x
            initialY = e.y
            parent.requestDisallowInterceptTouchEvent(true)
        } else if (e.action == MotionEvent.ACTION_MOVE) {
            val dx = e.x - initialX
            val dy = e.y - initialY
            val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL

            // assuming ViewPager2 touch-slop is 2x touch-slop of child
            val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
            val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f

            if (scaledDx > touchSlop || scaledDy > touchSlop) {
                if (isVpHorizontal == (scaledDy > scaledDx)) {
                    // Gesture is perpendicular, allow all parents to intercept
                    parent.requestDisallowInterceptTouchEvent(false)
                } else {
                    // Gesture is parallel, query child if movement in that direction is possible
                    if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
                        // Child can scroll, disallow all parents to intercept
                        parent.requestDisallowInterceptTouchEvent(true)
                    } else {
                        // Child cannot scroll, allow all parents to intercept
                        parent.requestDisallowInterceptTouchEvent(false)
                    }
                }
            }
        }
    }
}
版权声明:本文为u010823943原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u010823943/article/details/112132177

智能推荐

ViewPager2使用记录

文章目录 1. ViewPager2和RecyclerView滑动冲突。 背景 现象 解决办法 2.ViewPager2滑动至边缘阴影取消。 现象 解决办法 3. TabLayout和ViewPager2滑动效果 背景 解决办法 使用 记录一下,ViewPager2使用过程中碰到的几个小问题: 1. ViewPager2和RecyclerView滑动冲突。 背景 ViewPager2中使用了水平的...

【安卓笔记】解决ViewPager2嵌套RecyclerView,上下滑动冲突

当ViewPager2嵌套RecyclerView上下滑动的事件会被RecyclerView分发下去到ViewPager2,这样就会造成卡顿。解决办法就是重写RecyclerView的dispatchTouchEvent函数,将上滑事件拦截下来选择不分发给下一层的View自己处理。 Java代码 kotlin代码 点个赞呗 😃...

ViewPager2

相比较于ViewPager,他新增了一些功能, 垂直方向的支持 ViewPager2可以在垂直方向上翻页,仅需要在布局文件添加一行代码 这种场景应该极少,和多屏布局有滑动冲突 从右到左的支持 ViewPager2支持从右到左分页,仅需要在布局文件添加一行代码 这种场景应该也是极少的,滑动不太符合人操作的习惯,可能特殊需求时会用到 当页面数量发生变化时,可以通过notifyDataSetChange...

viewpager2

viewpager2 在mainactivity的xml文件中添加一个viewpager2的控件 在mainactivity的java文件中定义viewpager 需要新建一个viewpageradapter适配器 新建一个item_pager的xml文件...

Android CoordinatorLayout嵌套横向ViewPager2导致嵌套滑动失效问题

现象 当CoordinatorLayout嵌套横向ViewPager2时,在ViewPager2上点住上下滑动会失效。 原因 ViewPager2使用RecyclerView实现,RecyclerView吃掉了nested的一系列方法,导致无法回传至CoordinatorLayout,导致behavior失效,最终无法滑动 解决办法 对CoordinatorLayout内部嵌套的 一切、一切、一切...

猜你喜欢

ViewPager2滑动事件冲突

1. 场景分析 RecyclerView或SwipeRefreshLayout中嵌套ViewPager2,ViewPager2左右滑动困难,事件容易给RecyclerView或SwipeRefreshLayout吃掉。 ViewPager2中嵌套SwipeRefreshLayout,SwipeRefreshLayout和RecyclerView上下滑动困难。 解决方案分是干预事件分发,判断手势是左...

ViewPager2用法上下滑动

ViewPager2用法上下滑动 界面 主界面 继承FragmentStateAdapter...

Android viewPager2禁止滑动

直接给viewPager2设置 onTouchListener 是无效的,页面还是能够继续滑动,应该要给 viewPager2 的每个页面的 view 设置 onTouchListener 才有效...

viewPager2实现fragment滑动

1.创建ViewPager2 ViewPager是一个容器,需要用来滑动的几个fragment放在ViewPager中,在ViewPager容器内的fragment可以通过ViewPager给的方法实现滑动。 ViewPager2是ViewPager升级版,Google的文档推荐用新版,本文所有的ViewPager都指的是ViewPager2这个新版,使用需要添加依赖,在module的build....

ViewPager2左右滑动判断

 这是我第一次的写法,其实如果数据大于一条时(detailsData.size()>1),这样是没问题的,但是当数据只有一条时,这样就不行,就要判断滑动方向了。 这是现在的写法,加了左右滑动的判断...