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

【Android】结合View的事件分发机制解决滑动冲突

一、常见的滑动冲突类型及处理规则

常见的滑动冲突类型

1.外部滑动方向和内部方向不一致
2.外部滑动方向和内部方向一致
3.上面两种类型的嵌套

处理规则

场景1:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件

根据坐标得到滑动方向的方式:

1.依据滑动路径和水平方向形成的夹角

2.依据水平方向和竖直方向的距离差

3.依据水平和竖直方向的速度差

场景2和场景3:依据业务需求得出相应的处理规则

二、简单了解View的事件分发机制

MotionEvnet的传递规则

事件分发机制的核心就是对MotionEvnet(点击事件)的传递,而这个过程由以下三个重要方法来完成。

public boolean dispatchTouchEvent(MotionEvent ev)//用来进行事件的分发,如果事件能够传递给当前View,此方法一定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件
public boolean onInterceptTouchEvent(MotionEvent ev)//用来判断是否拦截某个事件,如果当前View拦截了此事件,在同一事件序列中只会调用一次此方法,返回结果表示是否拦截当前事件
public boolean onTouchEvent(MotionEvent ev)// dispatchTouchEvent中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在用一个时间序列中,当前View无法再次接收到事件   

由此我们可以总结出事件分发机制的过程:对于一个ViewGroup来说,点击事件产生后,首先传给它,调用它的dispatchTouchEvent,如果onInterceptTouchEvent返回true,表示拦截该事件,即调用onTouchEvent;如果onInterceptTouchEvent返回false,表示不拦截该事件,事件继续向下传递,调用子元素的dispatchTouchEvent,直到事件被处理。

特殊情况:
通过requestDisallowInterceptTouchEvent 设置FLAG_DISALLOW_INTERCEPT标记位,设置后ViewGroup无法拦截除ACTION_DOWN之外的其他事件

事件分发机制有以下几点需特别注意:

1.一个事件序列只能被一个View拦截并消耗,但可以通过特殊手段做到分别由两个View同时处理
2.某个View处理事件时如果不消耗ACTION_DOWN事件(onTouchEvent返回false),同一事件序列中的其他事件不会再交给他处理,并调用父元素的onTouchEvent
3.如果View不消耗ACTION_DOWN以外事件,那么这个点击事件消失,并且当前View可以接收后续事件,最终消失的事件交给Activity处理
4.ViewGroup默认不拦截任何事件
5.View没有onInterceptTouchEvent方法,直接调用onTouchEvent
6.View的onTouchEvent默认消耗事件,View的longClickable默认为false
7.事件传递过程从外到内,除ACTION_DOWN事件外,子元素可以通过requestDisallowInterceptTouchEvent方法干预父元素事件分发过程

View对点击事件的处理(不包含ViewGroup)

首先判断是否定义了onTouchListener,根据onTouch的返回结果,判断是否调用onTouchEvent

在onTouchEvent中,即使View处于不可用状态,依旧会消耗点击事件

只要View的clickable和long_clickable有一个为true,就会消耗事件

当发生ACTION_UP事件时,会触发performClick,如果设置了OnClickListener就会调用onClick方法

当我们设置setOnClickListener/setOnLongClickListener时,会自动将View的clickable/long_clickable设为true

三、解决滑动冲突的两种方法

根据上面对View事件分发机制的了解,我们可以知道,View的事件分发是从父容器逐层向内传递的,由此,我们可以分别通过内部拦截和外部拦截两种方式解决滑动冲突。

内部拦截

自定义控件继承原来的控件,并重写onInterceptTouchEvent方法

父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要就消耗掉,否则交给父容器处理

重点:

子视图通过判断是否需要消费此事件,如果需要,通过设置getParent().requestDisallowInterceptTouchEvent(true)取消父容器拦截,并在子视图消费;如果不需要,设置getParent().requestDisallowInterceptTouchEvent(false)让父容器拦截。

public class HorizontalScrollRecyclerView extends RecyclerView {private float startX, startY;private boolean isScrolling = false;public HorizontalScrollRecyclerView(Context context) {super(context);}public HorizontalScrollRecyclerView(Context context, AttributeSet attrs) {super(context, attrs);}public HorizontalScrollRecyclerView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);}public boolean onInterceptTouchEvent(MotionEvent e){switch (e.getAction()){case MotionEvent.ACTION_DOWN://按下startX = e.getX();startY = e.getY();isScrolling = false;boolean intercepted = super.onInterceptTouchEvent(e);//先交给父类让他自己判断是否拦截,防止影响父视图正常纵向滑动if (intercepted) {getParent().requestDisallowInterceptTouchEvent(true);}break;case MotionEvent.ACTION_MOVE:float endX = e.getX();float endY = e.getY();float distanceX = Math.abs(endX - startX);float distanceY = Math.abs(endY - startY);if (distanceX > distanceY) {//如果是横向滚动,取消父视图拦截isScrolling = true;getParent().requestDisallowInterceptTouchEvent(true);}else {getParent().requestDisallowInterceptTouchEvent(false);}break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:break;}return super.onInterceptTouchEvent(e);}
}

注意:父容器不能拦截ACTION_DOWN事件,因为此事件不受FLAG_DISALLOW_INTERCEPT标记位控制,父容器一旦拦截,所有事件都无法传递到子元素中

父容器也需要修改为默认拦截除ACTION_DOWN以外事件,保证当子元素设置

 getParent().requestDisallowInterceptTouchEvent(false);

时,父元素能正常拦截所需事件。

外部拦截

指点击事件都先经过父容器拦截处理

从父视图下手,重写onInterceptTouchEvent方法,在父视图需要拦截时拦截事件,否则返回false

import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;public class MySwipeRefreshLayout extends SwipeRefreshLayout{private float startX;private float startY;private float mTouchSlop;public MySwipeRefreshLayout(Context context) {super(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}public MySwipeRefreshLayout(Context context, AttributeSet attrs) {super(context, attrs);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()){case MotionEvent.ACTION_DOWN:startX = ev.getX();startY = ev.getY();break;case MotionEvent.ACTION_MOVE:float distanceX = Math.abs(ev.getX() - startX);float distanceY = Math.abs(ev.getY() - startY);if(distanceX > mTouchSlop && distanceX > distanceY){  //判断为横向滑动return false;}break;}return super.onInterceptTouchEvent(ev);}
}

注意:ACTION_DOWN事件必须返回false,否则后续事件就会直接交给父容器处理;

ACTION_UP必须返回false,否则子元素无法接收此事件,无法响应onClick

http://www.dtcms.com/a/561939.html

相关文章:

  • python 异步编程 -- 理解asyncio里的Future 对象
  • zoho crmvue做网站对seo
  • Java---System 类
  • 31.使用等待队列实现阻塞访问
  • Tyme 技术赋能:节气与季节的高效求解实战攻略
  • 【C++】2025CSP-J第二轮真题及解析
  • 网站建设教程流程更改wordpress主题语言
  • 朝阳区网站建设蒙特网设计公司
  • 济南网站优化厂家做同城服务网站比较成功的网站
  • 老鼠目标检测数据集(3000张)
  • 想做个网站怎么做长沙五百强企业名单
  • 九江建网站报价wordpress wiki 整合
  • 中英文版网站建设小广告制作
  • 05-深度学习的原理:探讨深度学习的工作原理和数学基础
  • 【深度学习新浪潮】AI缺陷检测:从技术原理到工业落地实践
  • lol英雄介绍网站模板网络广告推广员
  • 接单网站设计 只做设计图报价cpa推广联盟平台
  • kotlin学习 基础知识一览
  • 开通建立企业网站谷歌浏览器下载安卓版
  • 基于 venv 快速搭建 Python 环境
  • C 文件操作全解速览
  • MCP指南
  • 基于双向时序卷积网络(BiTCN)与支持向量机(SVM)混合模型的时间序列预测代码Matlab源码
  • 怎样免费做一个网站免费推广app是什么意思
  • 构建现代Web应用:使用React框架打造单页面应用
  • 仿站是什么企业vi设计欣赏
  • 招聘 负责网站开发购买了域名怎么使用
  • C++信息学奥赛 递推-动态规划 数塔与过河卒模型实战解析 图例+详解+状态转移方程
  • 具身智能实战(一):物体的抓取1(sdk)
  • 公网ip与内网ip