ViewPager2使用记录
文章目录
- 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使用记录相关推荐
- mysql建立联合索引,mysql建立唯一键,mysql如何解决重复记录联合索引
在项目中,常常要用到联合唯一 在一些配置表中,一些列的组合成为一条记录. 比如,在游戏中,游戏的分区和用户id会形成一条记录.(比如,一个qq用户可以在艾欧尼亚.德玛西亚创建两个账号) 添加联 ...
- 实现 连续15签到记录_MySQL和Redis实现用户签到,你喜欢怎么实现?
现在的网站和app开发中,签到是一个很常见的功能 如微博签到送积分,签到排行榜 微博签到 如移动app ,签到送流量等活动, 移动app签到 用户签到是提高用户粘性的有效手段,用的好能事半功倍! 下面 ...
- 记录一次http请求失败的问题分析
问题背景 当前我有一个基于Flask编写的Restful服务,由于业务的需求,我需要将该服务打包成docker 镜像进行离线部署,原始服务的端口是在6661端口进行开启,为了区分,在docker中启动 ...
- Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)
Pytorch学习记录-torchtext和Pytorch的实例1 0. PyTorch Seq2Seq项目介绍 1. 使用神经网络训练Seq2Seq 1.1 简介,对论文中公式的解读 1.2 数据预 ...
- LeetCode简单题之学生出勤记录 I
题目 给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤.迟到.到场).记录中只含下面三种字符: 'A':Absent,缺勤 'L':Late,迟到 'P':Pre ...
- 关于TVM的点滴记录
关于TVM的点滴记录
- MySql数据库Update批量更新与批量更新多条记录的不同值实现方法
批量更新 mysql更新语句很简单,更新一条数据的某个字段,一般这样写: UPDATE mytable SET myfield = 'value' WHERE other_field = 'other ...
- 记录篇,自己在项目中使用过的。
图片选择器,6.0已经适配过,类似qq空间上传 点击打开链接_胡小牧记录 下面是效果图: PictureSelector PhotoPicker 类似qq空间发布心情. 点击打开链接 BubbleSe ...
- HTML5与CSS3权威指南之CSS3学习记录
title: HTML5与CSS3权威指南之CSS3学习记录 toc: true date: 2018-10-14 00:06:09 学习资料--<HTML5与CSS3权威指南>(第3版) ...
最新文章
- 百度盯上媒体生意?百度CTO王海峰详解智能媒体中台
- 校招社招必备核心前端面试问题与详细解答
- python实现文件下载-python实现文件下载的方法总结
- Angular property binding重复触发的问题讨论
- mysql垃圾清理_mysql 垃圾图片清理
- 一位女孩对男孩的忠告(转贴)
- c#语言经典程序100例,C#入门必看的实例程序100个 - 源码下载|Windows编程|其他小程序|源代码 - 源码中国...
- laravel 核心类Kernel
- CUPS-Centos6-dockerfile
- atitit.印度教与java宗教的特点与观念对比 attilax总结
- 采用C语言写文本文件实例
- matlab计算prc曲线auc面积,ROC曲线及其matlab实现ROC曲线的绘画
- (一)C++游戏开发-本地存储-介绍
- 使用LR和XGBoost跑通criteo点击率预测数据集
- 电脑录音软件大全,推荐一波优秀的录音软件!
- 【解决方法】屏幕滚动时文字短暂变蓝
- 通用产品 云OA SaaS三管齐下
- 13款用于拍摄全景照片的iOS应用
- JQuery之常用插件
- thinkphp表单验证
热门文章
- 生成六位随机数字、随机字符串
- 字节与位的关系,百兆宽带的百兆是什么意思
- oauth2关于websocket携带token的探讨
- 基于瑞芯微3399的嵌入式linux,瑞芯微x3399 linux QT平台WIFI移植详解
- M2芯片首发,苹果MacBook Air是否值得买
- Spark:HanLP+Word2Vec+LSH实现文本推荐(kotlin)
- 题解-bzoj2560 串珠子
- Java面试题 详解 由易到难
- 逆水寒登录服务器未响应,逆水寒进剧情过图画面黑屏卡死无反应但是有声音的解决方法...
- html字两边的横线_css如何在文字两边加上横线的效果?