文章目录

  • 1. ViewPager2和RecyclerView滑动冲突。
    • 背景
    • 现象
    • 解决办法
  • 2.ViewPager2滑动至边缘阴影取消。
    • 现象
    • 解决办法
  • 3. TabLayout和ViewPager2滑动效果
    • 背景
    • 解决办法
    • 使用

记录一下,ViewPager2使用过程中碰到的几个小问题:

1. ViewPager2和RecyclerView滑动冲突。

背景

ViewPager2中使用了水平的RecyclerView。

现象

在网上看,大部分人碰见的都是RecyclerView无法滑动或者是ViewPager2无法滑动。但我碰到的现象是:如果点击RecyclerView后立刻就进行左右滑动,则是ViewPager2被滑动,而非RecyclerView被滑动;如果点击RecyclerView后停顿一下,再滑动,则可以正常滑动RecyclerView。

解决办法

使用Google提供的NestedScrollableHost.kt,包装一下RecyclerView即可。


/** 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 = 0private var initialX = 0fprivate var initialY = 0fprivate val parentViewPager: ViewPager2?get() {var v: View? = parent as? Viewwhile (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 nullinit {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) ?: false1 -> child?.canScrollVertically(direction) ?: falseelse -> 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 parentif (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {return}if (e.action == MotionEvent.ACTION_DOWN) {initialX = e.xinitialY = e.yparent.requestDisallowInterceptTouchEvent(true)} else if (e.action == MotionEvent.ACTION_MOVE) {val dx = e.x - initialXval dy = e.y - initialYval isVpHorizontal = orientation == ORIENTATION_HORIZONTAL// assuming ViewPager2 touch-slop is 2x touch-slop of childval scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1fval scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5fif (scaledDx > touchSlop || scaledDy > touchSlop) {if (isVpHorizontal == (scaledDy > scaledDx)) {// Gesture is perpendicular, allow all parents to interceptparent.requestDisallowInterceptTouchEvent(false)} else {// Gesture is parallel, query child if movement in that direction is possibleif (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {// Child can scroll, disallow all parents to interceptparent.requestDisallowInterceptTouchEvent(true)} else {// Child cannot scroll, allow all parents to interceptparent.requestDisallowInterceptTouchEvent(false)}}}}}
}

使用方法:

    <com.example.NestedScrollableHostandroid:id="@+id/nsh"android:layout_width="match_parent"android:layout_height="72dp"android:orientation="horizontal"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv"android:layout_width="match_parent"android:layout_height="72dp"/></com.example.NestedScrollableHost>

2.ViewPager2滑动至边缘阴影取消。

现象

不仅是ViewPager2,类似ScrollView、RecyclerView类的控件,都会存在滑动至第一项或最后一项时,再继续滑动,会出现淡紫色波纹阴影。

解决办法

如果是RecyclerView,则直接设置:

binding.rvRoom.overScrollMode = View.OVER_SCROLL_NEVER

或在xml中设置:

android:overScrollMode="never"

即可。
但ViewPager2这么设置不可以,需用如下办法:

 //ViewPager 取消滑动到边缘阴影效果val child = binding.viewPager.getChildAt(0)if (child is RecyclerView) child.overScrollMode = View.OVER_SCROLL_NEVER

3. TabLayout和ViewPager2滑动效果

背景

当在TabLayout中间隔多个Tab点击时,ViewPager2会连续滑动过多个页面。
需要点击Tab时无滑动动画效果,滑动ViewPager2时依旧有动画效果。(类似Android端微信效果)

解决办法

重写TabLayoutMediator

public class TabLayoutMediators {private TabLayout tabLayout;private ViewPager2 viewPager;private boolean autoRefresh;private static boolean smoothScroll;private TabConfigurationStrategy tabConfigurationStrategy;@Nullableprivate RecyclerView.Adapter<?> adapter;private boolean attached;@Nullableprivate TabLayoutOnPageChangeCallback onPageChangeCallback;@Nullableprivate TabLayout.OnTabSelectedListener onTabSelectedListener;@Nullableprivate RecyclerView.AdapterDataObserver pagerAdapterObserver;/*** A callback interface that must be implemented to set the text and styling of newly created* tabs.*/public interface TabConfigurationStrategy {/*** Called to configure the tab for the page at the specified position. Typically calls {@link* TabLayout.Tab#setText(CharSequence)}, but any form of styling can be applied.** @param tab      The Tab which should be configured to represent the title of the item at the given*                 position in the data set.* @param position The position of the item within the adapter's data set.*/void onConfigureTab(@NonNull TabLayout.Tab tab, int position);}public TabLayoutMediators(@NonNull TabLayout tabLayout,@NonNull ViewPager2 viewPager,@NonNull TabConfigurationStrategy tabConfigurationStrategy) {this(tabLayout, viewPager, /* autoRefresh= */ true, tabConfigurationStrategy);}public TabLayoutMediators(@NonNull TabLayout tabLayout,@NonNull ViewPager2 viewPager,boolean autoRefresh,@NonNull TabConfigurationStrategy tabConfigurationStrategy) {this(tabLayout, viewPager, autoRefresh, /* smoothScroll= */ true, tabConfigurationStrategy);}public TabLayoutMediators(@NonNull TabLayout tabLayout,@NonNull ViewPager2 viewPager,boolean autoRefresh,boolean smoothScroll,@NonNull TabConfigurationStrategy tabConfigurationStrategy) {this.tabLayout = tabLayout;this.viewPager = viewPager;this.autoRefresh = autoRefresh;this.smoothScroll = smoothScroll;this.tabConfigurationStrategy = tabConfigurationStrategy;}/*** Link the TabLayout and the ViewPager2 together. Must be called after ViewPager2 has an adapter* set. To be called on a new instance of TabLayoutMediator or if the ViewPager2's adapter* changes.** @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no*                               adapter.*/public void attach() {if (attached) {throw new IllegalStateException("TabLayoutMediator is already attached");}adapter = viewPager.getAdapter();if (adapter == null) {throw new IllegalStateException("TabLayoutMediator attached before ViewPager2 has an " + "adapter");}attached = true;// Add our custom OnPageChangeCallback to the ViewPageronPageChangeCallback = new TabLayoutOnPageChangeCallback(tabLayout);viewPager.registerOnPageChangeCallback(onPageChangeCallback);// Now we'll add a tab selected listener to set ViewPager's current itemonTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager, smoothScroll);tabLayout.addOnTabSelectedListener(onTabSelectedListener);// Now we'll populate ourselves from the pager adapter, adding an observer if// autoRefresh is enabledif (autoRefresh) {// Register our observer on the new adapterpagerAdapterObserver = new PagerAdapterObserver();adapter.registerAdapterDataObserver(pagerAdapterObserver);}populateTabsFromPagerAdapter();// Now update the scroll position to match the ViewPager's current itemtabLayout.setScrollPosition(viewPager.getCurrentItem(), 0f, true);}/*** Unlink the TabLayout and the ViewPager. To be called on a stale TabLayoutMediator if a new one* is instantiated, to prevent holding on to a view that should be garbage collected. Also to be* called before {@link #attach()} when a ViewPager2's adapter is changed.*/public void detach() {if (autoRefresh && adapter != null) {adapter.unregisterAdapterDataObserver(pagerAdapterObserver);pagerAdapterObserver = null;}tabLayout.removeOnTabSelectedListener(onTabSelectedListener);viewPager.unregisterOnPageChangeCallback(onPageChangeCallback);onTabSelectedListener = null;onPageChangeCallback = null;adapter = null;attached = false;}/*** Returns whether the {@link TabLayout} and the {@link ViewPager2} are linked together.*/public boolean isAttached() {return attached;}@SuppressWarnings("WeakerAccess")void populateTabsFromPagerAdapter() {tabLayout.removeAllTabs();if (adapter != null) {int adapterCount = adapter.getItemCount();for (int i = 0; i < adapterCount; i++) {TabLayout.Tab tab = tabLayout.newTab();tabConfigurationStrategy.onConfigureTab(tab, i);tabLayout.addTab(tab, false);}// Make sure we reflect the currently set ViewPager itemif (adapterCount > 0) {int lastItem = tabLayout.getTabCount() - 1;int currItem = Math.min(viewPager.getCurrentItem(), lastItem);if (currItem != tabLayout.getSelectedTabPosition()) {tabLayout.selectTab(tabLayout.getTabAt(currItem));}}}}/*** A {@link ViewPager2.OnPageChangeCallback} class which contains the necessary calls back to the* provided {@link TabLayout} so that the tab position is kept in sync.** <p>This class stores the provided TabLayout weakly, meaning that you can use {@link* ViewPager2#registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback)} without removing the* callback and not cause a leak.*/private static class TabLayoutOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {@NonNullprivate final WeakReference<TabLayout> tabLayoutRef;private int previousScrollState;private int scrollState;TabLayoutOnPageChangeCallback(TabLayout tabLayout) {tabLayoutRef = new WeakReference<>(tabLayout);reset();}@Overridepublic void onPageScrollStateChanged(final int state) {if (state == SCROLL_STATE_DRAGGING) {smoothScroll = true;} else if (state == SCROLL_STATE_IDLE) {smoothScroll = false;}previousScrollState = scrollState;scrollState = state;}@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {TabLayout tabLayout = tabLayoutRef.get();if (tabLayout != null) {// Only update the text selection if we're not settling, or we are settling after// being draggedboolean updateText =scrollState != SCROLL_STATE_SETTLING || previousScrollState == SCROLL_STATE_DRAGGING;// Update the indicator if we're not settling after being idle. This is caused// from a setCurrentItem() call and will be handled by an animation from// onPageSelected() instead.boolean updateIndicator =!(scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE);tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);}}@Overridepublic void onPageSelected(final int position) {TabLayout tabLayout = tabLayoutRef.get();if (tabLayout != null&& tabLayout.getSelectedTabPosition() != position&& position < tabLayout.getTabCount()) {// Select the tab, only updating the indicator if we're not being dragged/settled// (since onPageScrolled will handle that).boolean updateIndicator =scrollState == SCROLL_STATE_IDLE|| (scrollState == SCROLL_STATE_SETTLING&& previousScrollState == SCROLL_STATE_IDLE);tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);}}void reset() {previousScrollState = scrollState = SCROLL_STATE_IDLE;}}/*** A {@link TabLayout.OnTabSelectedListener} class which contains the necessary calls back to the* provided {@link ViewPager2} so that the tab position is kept in sync.*/private static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {private final ViewPager2 viewPager;
//        private final boolean smoothScroll;ViewPagerOnTabSelectedListener(ViewPager2 viewPager, boolean smoothScroll) {this.viewPager = viewPager;
//            this.smoothScroll = smoothScroll;}@Overridepublic void onTabSelected(@NonNull TabLayout.Tab tab) {viewPager.setCurrentItem(tab.getPosition(), smoothScroll);}@Overridepublic void onTabUnselected(TabLayout.Tab tab) {// No-op}@Overridepublic void onTabReselected(TabLayout.Tab tab) {// No-op}}private class PagerAdapterObserver extends RecyclerView.AdapterDataObserver {PagerAdapterObserver() {}@Overridepublic void onChanged() {populateTabsFromPagerAdapter();}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount) {populateTabsFromPagerAdapter();}@Overridepublic void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {populateTabsFromPagerAdapter();}@Overridepublic void onItemRangeInserted(int positionStart, int itemCount) {populateTabsFromPagerAdapter();}@Overridepublic void onItemRangeRemoved(int positionStart, int itemCount) {populateTabsFromPagerAdapter();}@Overridepublic void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {populateTabsFromPagerAdapter();}}
}

使用

注意:使用的是自定义的TabLayoutMediators ,而非TabLayoutMediator

 //定义TabLayoutval tabLayoutMediator = TabLayoutMediators(binding.tabLayout,binding.viewPager) { tab, position ->when (position) {0 -> tab.text = "智能"1 -> tab.text = "情景"2 -> tab.text = " 我 "}}

ViewPager2使用记录相关推荐

  1. mysql建立联合索引,mysql建立唯一键,mysql如何解决重复记录联合索引

    在项目中,常常要用到联合唯一   在一些配置表中,一些列的组合成为一条记录.   比如,在游戏中,游戏的分区和用户id会形成一条记录.(比如,一个qq用户可以在艾欧尼亚.德玛西亚创建两个账号) 添加联 ...

  2. 实现 连续15签到记录_MySQL和Redis实现用户签到,你喜欢怎么实现?

    现在的网站和app开发中,签到是一个很常见的功能 如微博签到送积分,签到排行榜 微博签到 如移动app ,签到送流量等活动, 移动app签到 用户签到是提高用户粘性的有效手段,用的好能事半功倍! 下面 ...

  3. 记录一次http请求失败的问题分析

    问题背景 当前我有一个基于Flask编写的Restful服务,由于业务的需求,我需要将该服务打包成docker 镜像进行离线部署,原始服务的端口是在6661端口进行开启,为了区分,在docker中启动 ...

  4. Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)

    Pytorch学习记录-torchtext和Pytorch的实例1 0. PyTorch Seq2Seq项目介绍 1. 使用神经网络训练Seq2Seq 1.1 简介,对论文中公式的解读 1.2 数据预 ...

  5. LeetCode简单题之学生出勤记录 I

    题目 给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤.迟到.到场).记录中只含下面三种字符: 'A':Absent,缺勤 'L':Late,迟到 'P':Pre ...

  6. 关于TVM的点滴记录

    关于TVM的点滴记录

  7. MySql数据库Update批量更新与批量更新多条记录的不同值实现方法

    批量更新 mysql更新语句很简单,更新一条数据的某个字段,一般这样写: UPDATE mytable SET myfield = 'value' WHERE other_field = 'other ...

  8. 记录篇,自己在项目中使用过的。

    图片选择器,6.0已经适配过,类似qq空间上传 点击打开链接_胡小牧记录 下面是效果图: PictureSelector PhotoPicker 类似qq空间发布心情. 点击打开链接 BubbleSe ...

  9. HTML5与CSS3权威指南之CSS3学习记录

    title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...

最新文章

  1. 百度盯上媒体生意?百度CTO王海峰详解智能媒体中台
  2. 校招社招必备核心前端面试问题与详细解答
  3. python实现文件下载-python实现文件下载的方法总结
  4. Angular property binding重复触发的问题讨论
  5. mysql垃圾清理_mysql 垃圾图片清理
  6. 一位女孩对男孩的忠告(转贴)
  7. c#语言经典程序100例,C#入门必看的实例程序100个 - 源码下载|Windows编程|其他小程序|源代码 - 源码中国...
  8. laravel 核心类Kernel
  9. CUPS-Centos6-dockerfile
  10. atitit.印度教与java宗教的特点与观念对比 attilax总结
  11. 采用C语言写文本文件实例
  12. matlab计算prc曲线auc面积,ROC曲线及其matlab实现ROC曲线的绘画
  13. (一)C++游戏开发-本地存储-介绍
  14. 使用LR和XGBoost跑通criteo点击率预测数据集
  15. 电脑录音软件大全,推荐一波优秀的录音软件!
  16. 【解决方法】屏幕滚动时文字短暂变蓝
  17. 通用产品 云OA SaaS三管齐下
  18. 13款用于拍摄全景照片的iOS应用
  19. JQuery之常用插件
  20. thinkphp表单验证

热门文章

  1. 生成六位随机数字、随机字符串
  2. 字节与位的关系,百兆宽带的百兆是什么意思
  3. oauth2关于websocket携带token的探讨
  4. 基于瑞芯微3399的嵌入式linux,瑞芯微x3399 linux QT平台WIFI移植详解
  5. M2芯片首发,苹果MacBook Air是否值得买
  6. Spark:HanLP+Word2Vec+LSH实现文本推荐(kotlin)
  7. 题解-bzoj2560 串珠子
  8. Java面试题 详解 由易到难
  9. 逆水寒登录服务器未响应,逆水寒进剧情过图画面黑屏卡死无反应但是有声音的解决方法...
  10. html字两边的横线_css如何在文字两边加上横线的效果?