Android 自定义View篇 之 Touch方面 知识梳理

注:转载请注明出处,请珍惜成果
https://blog.csdn.net/treasureqian/article/details/103488763

 

谈起Touch可以联想很多东西,比如OnTouchEvent触摸事件、事件分发机制、滑动冲突等等。本文旨在将这一方面相关的知识做一下梳理,丰富自己,丰富别人。Ready GO!

 

一、OnTouchEvent

1.基本坐标系以及XY值的区别

        1)先了解一下Android的基本坐标系(看图)

 

          2)View来获取相对位置

2.MotionEvent的详解

            1)getAction()getActionMasked()的区别

简单来讲:一个应用于单点触控比较多,一个应用于多点触控比较多(当然也可以单点触控)。

复杂来讲:当多个手指在屏幕上按下的时候,会产生大量的事件,一般来说我们可以通过为事件添加一个int类型的index属性来区分,或者让二者结合在一起,毕竟一个变量更简洁一点。

int类型共32位(0x00000000),他们用最低8位(0x000000ff)表示事件类型,再往前的8位(0x0000ff00)表示事件编号。

*注:1、多点触控时必须使用 getActionMasked() 来获取事件类型。
               2、单点触控时由于事件数值不变,使用 getAction() 和 getActionMasked() 两个方法都可以。
               3、使用 getActionIndex() 可以获取到这个index数值。不过请注意,getActionIndex() 只在 down 和 up 时有效,

             move 时是无效的。

         2) 单点触控一般的coding方式   

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
        	// 手指按下
            break;
        case MotionEvent.ACTION_MOVE:
            // 手指移动
            break;
        case MotionEvent.ACTION_UP:
            // 手指抬起
            break;
        case MotionEvent.ACTION_CANCEL:
            // 事件被拦截   触发条件是事件被上层拦截
//事实上,只有上层 View 回收事件处理权的时候,ChildView 才会收到一个 ACTION_CANCEL 事件

            break;
        case MotionEvent.ACTION_OUTSIDE:
            // 超出区域 
            break;
    }
    return super.onTouchEvent(event);
}

      3)多指触控的coding方式

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int actionMasked = event.getActionMasked();
        int actionIndex = event.getActionIndex();//获取该事件是哪个指针(手指)产生的
        int pointerCount = event.getPointerCount();//获取在屏幕上手指的个数
        int pointerId = event.getPointerId(0);//获取一个指针(手指)的唯一标识符ID,在手指按下和抬起之间ID始终不变
        int pointerIndex = event.findPointerIndex(0);//通过PointerId获取到当前状态下PointIndex,之后通过PointIndex获取其他内容
        float x = event.getX(0);//获取某一个指针(手指)的X坐标
        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
            case MotionEvent.ACTION_CANCEL:
                break;
            case MotionEvent.ACTION_OUTSIDE:
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                //有非主要的手指按下(即按下之前已经有手指在屏幕上)
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //有非主要的手指抬起(即抬起之后仍然有手指在屏幕上)
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

 

二、事件分发机制 

先祭一张流程图,其实看图完全可以明白,不说废话,图在此 |||(当然层级是Activity -> ViewGroup -> View)

  • 其实想想也是语气写一堆Demo来验证这个问题 既繁琐也易忘,不如一张图片来的实在
  •  
  • 以这个层级来看,事件传递的起点是Activity的dispatchTouchEvent 方法
  •  
  • 每个层级的dispathTouchEvent的方法 只有返回super才会正常向下传递,返回true都会被自己本方法消费,返回false都会抛给上层的onTouchEvent方法,由于activity为顶级,故也是抛出事件
  •  
  • OnTouchEvent方法只有返回true的时候才会自己做处理,返回false或者super都会抛给上层的OnTouchEvent方法
  •  
  • 只有ViewGroup才有onInceptTouchEvent方法,此方法也是事件拦截方法,多用于处理滑动冲突等等方面

mmp,好像能说的就是这些,说的不繁琐,简单点,说太多了我也不会 哈哈哈

 

三、滑动冲突

其实滑动冲突,现在都是基础,常备之课,也没啥难的简单说一说,简单记一记。

1.原因(说什么之前先说原因)

说到底,都是事件分发的锅(事件分发:mmp,这锅我不背),子View滑动的时候想要权限,老子又不给,子View又划不动,或者给了也没说好 给到哪里,滑动到边界的时候 模棱两可,用起来卡卡顿顿。滑动冲突出现在同方向滑动比较多。

2.举个栗子

同向的ScrollView和RecyclerView、同向ViewPager和RecyclerView (不过ViewPager内部已经做过处理,会检测子View的是否可以滑动)、很多很多吧

来个图吧,没个图不太好进行下去,不然有点乏味

  • 第一种情况是最好解决的,可以通过touch时间处理,move的XY差值作比较,用初中的学到的Sin函数和tan(弹镇特)函数来解决
  •  
  • 第二种情况其实也不难,还分外部(父View)解决和内部(子View)解决两种方法
  • 其中外部解决就是 父View的OnInceptTouchEvent 方法,根据业务需求做相应的返回值 以此来拦截事件传递
  • 内部解决就是 子View都是有这个方法  requestDisallowInceptTouchEvent(boolean) 汉译  请求不允许执行拦截 ,字如其意

 

3.解决问题

             1)外部拦截法

即父View根据需要对事件进行拦截。逻辑处理放在父View的onInterceptTouchEvent方法中。我们只需要重写父View的onInterceptTouchEvent方法,并根据逻辑需要做相应的拦截即可。

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (isViewGroup) {//满足父容器的拦截要求
                    intercepted = true;
                } else {
                    intercepted = false;
                    break;
                }
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            default:
                break;
        }

        return intercepted;
    }
  • 根据业务逻辑需要,在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理。
  •  
  • ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE 与 ACTION_UP事件都将默认交给父View去处理!
  •  
  • 原则上ACTION_UP也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发。而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理
     

              2)内部拦截法

父View不拦截任何事件,所有事件都传递给子View,子View根据需要决定是自己消费事件还是给父View处理。这需要子View使用requestDisallowInterceptTouchEvent方法才能正常工作。

@子View Coding 风格

@Override
    public boolean dispatchTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (isViewGroup) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            }
            case MotionEvent.ACTION_UP: {
                break;
            }
            default:
                break;
        }

        return super.dispatchTouchEvent(event);
    }

@父View  Coding 风格

@Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }
  • 内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。

  •  

  • 滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。

 

自此,粗略的讲了onTouch方面的知识,就这样的,以后用过新的会在补充


                                                                   good good study , day day up!

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值