当前位置: 首页 > news >正文

【Android】模板化解决复杂场景的滑动冲突问题

仿写项目的业务场景刚好覆盖有两种复杂滑动冲突场景:

Horizontal ViewPager2 嵌套 Vertical RecyclerView (OuterRecyclerView) 嵌套 Horizontal RecyclerView (InnerRecyclerView) 的横纵横场景和

Horizontal ViewPager2 嵌套 Horizontal ViewPager2 (InnerViewPager) 嵌套 Vertical RecyclerView 的横横纵场景。

同时要让内层的 InnerRecyclerView 和 InnerViewPager 在横向滑动方向尚可滑动接管事件,不可滑动时将事件交给外层的 Horizontal ViewPager2。

背景原理

我们知道 MotionEvent 事件传递是由 DecorView 到 ViewGroup 再到 View,完整事件序列必定由 MotionEvent.DOWN 开始,ViewGroup 在事件传递过程中会判断是否拦截该事件序列,如果 DOWN 被拦截则该 ViewGroup 会完全接管后续所有事件。

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);ev.setAction(action);} else {intercepted = false;}
} else {intercepted = true;
}

由外层条件语句知,child 的 requestDisallowInterceptTouchEvent 方法无法影响 ViewGroup 对 DOWN 事件的拦截处理,所以大部分情况 ViewGroup 派生类都不会拦截 DOWN 事件,通常逻辑是让 child 消费 DOWN 事件,再根据后续 MOVE 方向判断是否拦截。

requestDisallowInterceptTouchEvent

该方法是 child 发送给父容器的请求,参数的布尔值表示是否期望父容器拦截事件,父容器接收到请求后会将 FLAG_DISALLOW_INTERCELT 标志位设置为 1,内部的 disallowIntercept 字段进而得到 true。

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}
}
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {// We're already in this state, assume our ancestors are too
return;}if (disallowIntercept) {mGroupFlags |= FLAG_DISALLOW_INTERCEPT;} else {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}// Pass it up to our parent
if (mParent != null) {mParent.requestDisallowInterceptTouchEvent(disallowIntercept);}
} /* mGroupFlags可以看成标记位大集合 */

所以解决滑动冲突的方法很明确:父布局不要拦截向下传递的 DOWN 事件,让 child 在 DOWN 事件禁止父布局拦截事件,根据后续 MOVE 事件的滑动方向和 child 本身的情况判断是否允许父布局拦截事件,在 CANCEL 或 UP 事件恢复原状。

横纵横

InnerRecyclerView 判断是否为横向滑动且滑动方向有内容在屏幕外,是则由自己接管,否则交给 ViewPager2,这里利用 View 树的特点冒泡获得以 ViewPager2 为父容器的 View,对该 View 进行 requestDisallowInterceptTouchEvent,隔离了 OuterRecyclerView 和 ViewPager2。

如果为纵向滑动则设置 requestDisallowInterceptTouchEvent(false),让 OuterRecyclerView 来禁止父容器拦截,保证了 InnerRecyclerView 和 OuterRecyclerView 互不冲突。

public class InnerRecyclerView extends RecyclerView {private double x0, y0;private final int touchSlop;public InnerRecyclerView(Context context) {super(context);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public InnerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public InnerRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {ViewGroup v = this;while (v != null && !(v.getParent() instanceof ViewPager2)) {v = (ViewGroup) v.getParent();}switch (e.getActionMasked()) {case MotionEvent.ACTION_DOWN:x0 = e.getX();y0 = e.getY();v.requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:double dx = e.getX() - x0;double dy = e.getY() - y0;if (Math.abs(dx) < touchSlop && Math.abs(dy) < touchSlop) break;if (Math.abs(dx) > Math.abs(dy)) {if (dx > 0) {if (!canScrollHorizontally(-1)) {v.requestDisallowInterceptTouchEvent(false);} else {v.requestDisallowInterceptTouchEvent(true);}} else {if (!canScrollHorizontally(1)) {v.requestDisallowInterceptTouchEvent(false);} else {v.requestDisallowInterceptTouchEvent(true);}}} else {v.requestDisallowInterceptTouchEvent(false);}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:v.requestDisallowInterceptTouchEvent(false);break;}return super.onInterceptTouchEvent(e);}
}

OuterRecyclerView 判断是否为纵向滑动,是则由自己接管,否则交给 ViewPager2,InnerRecycler 和 OuterRecyclerView 的拦截决策均只影响 ViewPager2 是否拦截,所以保证 ViewPager2 和内层整体互不冲突,进而解决横纵横方向的滑动冲突。

public class OuterRecyclerView extends RecyclerView {private double x0, y0;private final int touchSlop;public OuterRecyclerView(@NonNull Context context) {super(context);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public OuterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {super(context, attrs);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public OuterRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:x0 = ev.getX();y0 = ev.getY();break;case MotionEvent.ACTION_MOVE:double dx = ev.getX() - x0;double dy = ev.getY() - y0;if (Math.abs(dx) < touchSlop && Math.abs(dy) < touchSlop) break;if (Math.abs(dy) > Math.abs(dx)) {getParent().requestDisallowInterceptTouchEvent(true);} else getParent().requestDisallowInterceptTouchEvent(false);break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:getParent().requestDisallowInterceptTouchEvent(false);break;}return super.onInterceptTouchEvent(ev);}
}

横横纵

因为 ViewPager2 是 final 类,无法通过重写 onInterceptTouchEvent 方法解决冲突,换种思路想,我们可以用 FrameLayout 包裹 InnerPager2,重写 FrameLayout 的 onInterceptTouchEvent 方法来解决,其内部逻辑和横纵横的 InnerRecyclerView 类似。

public class InnerViewPagerContainer extends FrameLayout {private int touchSlop;private float startX, startY;public InnerViewPagerContainer(Context context) {super(context);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public InnerViewPagerContainer(Context context, AttributeSet attrs) {super(context, attrs);touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent e) {ViewGroup v = this;while (v != null && !(v.getParent() instanceof ViewPager2)) {v = (ViewGroup) v.getParent();}switch (e.getActionMasked()) {case MotionEvent.ACTION_DOWN:startX = e.getX();startY = e.getY();v.requestDisallowInterceptTouchEvent(true);break;case MotionEvent.ACTION_MOVE:float dx = e.getX() - startX;float dy = e.getY() - startY;if (Math.abs(dx) < touchSlop && Math.abs(dy) < touchSlop) break;if (Math.abs(dx) > Math.abs(dy)) {if (dx > 0) {if (!getChildAt(0).canScrollHorizontally(-1)) {v.requestDisallowInterceptTouchEvent(false);} else {v.requestDisallowInterceptTouchEvent(true);}} else {if (!getChildAt(0).canScrollHorizontally(1)) {v.requestDisallowInterceptTouchEvent(false);} else {v.requestDisallowInterceptTouchEvent(true);}}} else {v.requestDisallowInterceptTouchEvent(false);}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:v.requestDisallowInterceptTouchEvent(false);break;}return super.onInterceptTouchEvent(e);}
}
http://www.dtcms.com/a/617932.html

相关文章:

  • LeetCode 热题 100——子串——和为 K 的子数组
  • JVM内存结构深度解析:堆、栈、方法区、元空间、直接内存
  • H-RDT:基于人类操作增强的双臂机器人操作研究
  • hysAnalyser --- UDP实时流分析使用指南
  • 象棋棋理基础
  • 要怎么推广网站全国工程招标信息网
  • 做教育网站多少钱如何选择企业建站公司
  • 高斯db的客户端连接工具
  • 网站建设与维护成本网站负责人核验照
  • 软件架构师技术一览与具体工作思考
  • [Java 算法] 双指针 2
  • Python语言设计模式:外观模式详解
  • 企业网站seo推广设计网站公司 露 联湖南岚鸿
  • 外贸建设网站公司微能力者恶魔网站谁做的
  • Python软件设计模式解析与实战
  • 工业互联网:连接未来制造的数字大脑
  • 基于单片机的水泵效率温差法测量与报警系统设计
  • 推荐工程笔记:设计模式/java与性能优化
  • 【安全函数】C语言安全字符串函数详解:告别缓冲区溢出的噩梦
  • 免费收录软文网站网站制作公司在哪里找
  • 3.FPGA位宽
  • Linux操作系统基础命令基础
  • 永恒之蓝内网横向渗透:原理详解+telnet法渗透实践(CVE-2017-0144)
  • 购物网站答辩ppt怎么做做购物平台网站 民治
  • 【Linux】Linux编译器-gcc/g++使用和gcc具体编译过程以及编译选项的小插曲
  • flume单机版安装
  • C++篇(17)哈希拓展学习
  • 做建筑材料的网站wordpress后台左侧菜单显示
  • 基于SpringBoot的热门旅游推荐系统设计与实现
  • leetcode 1513 仅含1的子串数