view的事件体系(一)

涉及到的内容

1.view的位置参数

2.view事件处理工具类

3.view事件分发

4.简单view冲突处理

 

View的位置参数

在android的坐标体系中,x的右为正方向 y的下为正方向

view的位置如下图


width = right – left

height = bottom – top

 

3.0之后,View增加了参数:x, y,translationX, translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。

X = left + translationX;

Y  =right + translationY;

View在平移的过程中,top和left表示的是原始左上角的位置信息,他们的值不会发生改变,而x,y, translationX,translationY的值会随着变化。

 

需要注意的是这些坐标都是相对于View的父容器来说的

 

示例:

在button调用

        button1.setTranslationX(100);

        button1.setTranslationY(100);

之后,打印log

 

10-11 09:58:21.642:E/GesturetestActivity(1724):  调用之前:

10-11 09:58:21.642:E/GesturetestActivity  getX(1724): 0.0

10-11 09:58:21.642:E/GesturetestActivity  getY(1724): 0.0

10-11 09:58:21.642:E/GesturetestActivity  getLeft(1724): 0

10-11 09:58:21.642:E/GesturetestActivity  getTop(1724): 0

10-11 09:58:21.642: E/GesturetestActivity  getTranslationX(1724): 0.0

10-11 09:58:21.642:E/GesturetestActivity getTranslationY(1724): 0.0

10-11 09:58:21.642:E/GesturetestActivity(1724):  调用之后:

10-11 09:58:21.642:E/GesturetestActivity  getX(1724): 100.0

10-11 09:58:21.642: E/GesturetestActivity  getY(1724): 100.0

10-11 09:58:21.642:E/GesturetestActivity  getLeft(1724): 0

10-11 09:58:21.642:E/GesturetestActivity  getTop(1724): 0

10-11 09:58:21.642:E/GesturetestActivity getTranslationX(1724): 100.0

10-11 09:58:21.642: E/GesturetestActivity  getTranslationY(1724): 100.0

 

后话:

但是View动画和ScrollTo或者ScrollBy的方式不会改变Viewxy translationXtranslationY

因为

1. scrollTo/ ScrollBy作用于父容器的,而View的这些属性值也是基于父容器的。

2.View动画移动的是View的影像


View事件处理工具类

MotionEvent

系统提供获取触摸事件的两组方法:

getX/getY  返回的是相对于当前View左上角的x和y的坐标

getRawX/getRawY 返回的是相对于手机屏幕左上角的x和y的坐标

 

ViewConfiguration

这里面所有的方法基本都是用来获取常量数据的,没有什么业务操作。

ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下: 

1
、获取ViewConfiguration对象,由于ViewConfiguration的构造方法为私有的,只能通过这个静态方法来获取到该对象。 

ViewConfiguration configure =ViewConfiguration.get(context); 


2、通过该对象调用相关的函数,将返回相关的常量数据。

http://www.aichengxu.com/view/65180  ViewConfiguration

 

 

VelocityTracker

速度测量

用于测量手指在滑动过程中的速度,包括水平和竖直方向的速度。


mVelocityTracker =VelocityTracker.obtain();//获得VelocityTracker类实例

mVelocityTracker.addMovement(ev);//将事件加入到VelocityTracker类实例中

velocityTracker.computeCurrentVelocity(1000);//设置units的值为1000,意思为一秒时间内运动了多少个像素

velocityTracker.getXVelocity()

velocityTracker.getYVelocity()

 

velocityTracker.clear();

velocityTracker.recycle();


GestureDetector

手势检测

用于辅助检测用户的单击,滑动,长按,双击等事件。

 

Eg

privatevoidinitGes(){

gestureDetector =newGestureDetector(this,newGestureDetector.SimpleOnGestureListener(){

        private Screen screen;

        @Override

        public boolean onFling(MotionEvent e1,MotionEvent e2,

                 float velocityX, float velocityY) {

            

             screen = GestureUtils.getScreenPix(GesturetestActivity.this);

            

             int x_ = (int) (e2.getX() -e1.getX());

             int y_ = (int) (e2.getY() -e1.getY());

            

             int width_s = screen.widthPixels /4;

             int height_s = screen.heightPixels/4;

            

             int x_abs = Math.abs(x_);

             int y_abs = Math.abs(y_);

            

             //在水平方向移动的距离大于在竖直方向移动的距离

             if(x_abs > y_abs){

                 //水平方向移动的距离大于宽度的1/4

                 if(x_abs > width_s){

                     if(x_>0) show("向右") ;elseshow("向左");

                 }

             }else{

                 if(y_abs>height_s){

                     if(y_>0) show("向下") ;elseshow("向上");

                 }

             }

             return true;

        }

        });

   }

   @Override

   publicbooleanonTouchEvent(MotionEvent event) {

        return gestureDetector.onTouchEvent(event);

    }

 

GestureDetector我们在实际开发中可以完全不使用,而自己在ViewonTouchEvent方法中实现所需的监听。

具体使用哪种方法处理事件,看实际情况

 

View事件分发

所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了之后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是事件的分发过程。

由三个很重要的方法完成:

dispatchTouchEvent    进行事件分发,如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前ViewonTouchEvent和下级的dispatchTouchEvent影响,表示是否消耗当前事件。

onInterceptTouchEvent dispatchtouchEvent内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么过一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前的事件。

onTouchEvent dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

 

他们的关系可以使用如下伪代码表示:

Public boolean dispatchTouchEventMotionEvent ev{

         booleanconsume = false;

         if(onInterceptTouchEvent(ev)){

                   consume= onTouchEvent(ev)

}else{

         consume = child.dispatchTouchEvent();

}

return  consume;

}

 

ViewGroup接收到一个事件,首先调用dispatchTouchEvent方法,然后调用onInterceptTouchEvent方法,如果onInterceptTouchEvent返回trueViewGroup调用自己的OnTouchEvent事件将不会传给子View。如果onInterceptTouchEvent返回false,ViewdispatchTouchEvent方法被调用。

 

在一个View的内部,如果设置了OnTouchListener,那么OntouchListener中的onTouch方法会被回调。如果onTouch返回true,那么onTouchEvent将不会被调用。即给View设置的OnClickListeneronclick将不会被调用。所以OnTouchListener的优先级要高于 onTouchEvent

 

事件传递顺序是:ActivityWindow ViewView………….如果没有view处理这个事件,事件将会交给Activity处理。

 

 

注:一下这一部分关于事件分发的分析来自于hongyang的博客

1.  /** 

2.      * Pass the touch screen motion event down to the target view, or this 

3.       * view if it is the target. 

4.      * 

5.       * @param event The motion event to be dispatched. 

6.      * @return True if the event was handled by the view, false otherwise. 

7.       */  

8.     public boolean dispatchTouchEvent(MotionEvent event) {  

9.          if (!onFilterTouchEventForSecurity(event)) {  

10.            return false;  

11.         }  

12.  

13.         if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  

14.                mOnTouchListener.onTouch(this, event)) {  

15.             return true;  

16.        }  

17.         return onTouchEvent(event);  

18.    }

首先判断mOnTouchListener不为null,并且viewenable的状态,然后 mOnTouchListener.onTouch(this,event)返回true,这三个条件如果都满足,直接return true ; 也就是下面的onTouchEvent(event)不会被执行了

 

如果我们设置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不会被执行了 

ViewGroup

1ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用      mMotionTarget.dispatchTouchEvent

2ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

3ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)

当然了在分发之前都会修改下坐标系统,把当前的xy分别减去child.left child.top,然后传给child;

 

 

 

默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UPView都不会捕获事件;如果你在MOVE return true , 则子ViewMOVEUP都不会捕获事件。

原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget置为null ; 

 

如果ViewGrouponInterceptTouchEvent(ev)ACTION_MOVEreturn true ,即拦截了子ViewMOVE以及UP事件;

此时子View希望依然能够响应MOVEUP时该咋办呢?

Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean)用于设置是否允许拦截,我们在子ViewdispatchTouchEvent中直接这么写:

1.  @Override  

2.     public boolean dispatchTouchEvent(MotionEvent event)  

3.      {  

4.         getParent().requestDisallowInterceptTouchEvent(true);    

5.          int action = event.getAction();  

6.   

7.          switch (action)  

8.         {  

9.          case MotionEvent.ACTION_DOWN:  

10.            Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");  

11.             break;  

12.        case MotionEvent.ACTION_MOVE:  

13.             Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");  

14.            break;  

15.         case MotionEvent.ACTION_UP:  

16.            Log.e(TAG, "dispatchTouchEvent ACTION_UP");  

17.             break;  

18.  

19.         default:  

20.            break;  

21.         }  

22.        return super.dispatchTouchEvent(event);  

23.     }  

getParent().requestDisallowInterceptTouchEvent(true);  这样即使ViewGroupMOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。

因为ViewGroup源码中:

if (!disallowIntercept && onInterceptTouchEvent(ev)) { 

当我们把disallowIntercept设置为true时,!disallowIntercept直接为false,于是拦截的方法体就被跳过了

 

ACTION_DOWN的时候,子View.dispatchTouchEvent(ev)返回的为false ;

如果你仔细看了,你会注意到ViewGroupdispatchTouchEvent(ev)ACTION_DOWN代码是这样的

1. if (child.dispatchTouchEvent(ev))  {  

2.                               // Event handled, we have a target now.  

3.                               mMotionTarget = child;  

4.                               return true;  

5.                           }  


只有在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,即mMotionTarget= child;

但是如果返回false,那么mMotionTarget依然是null

mMotionTarget null会咋样呢?

其实ViewGroup也是View的子类,如果没有找到能够处理该事件的子View,或者干脆就没有子View

那么,它作为一个View,就相当于View的事件转发了~~直接super.dispatchTouchEvent(ev);

源码是这样的:

1. final View target = mMotionTarget;  

2.        if (target == null) {  

3.            // We don't have a target, this means we're handling the  

4.            // event as a regular view.  

5.            ev.setLocation(xf, yf);  

6.            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  

7.                ev.setAction(MotionEvent.ACTION_CANCEL);  

8.                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

9.            }  

10.           return super.dispatchTouchEvent(ev);  

11.        }  


我们没有一个能够处理该事件的目标元素,意味着我们需要自己处理~~~就相当于传统的View~ 

 

1、如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;

2、可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即returntrue),把事件交给自己处理,则会执行自己对应的onTouchEvent方法

3、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;

结合简书上Kelin 的文章,自己重新画了一遍流程

一张图终结:


View简单冲突处理

eg:

当出现上下,左右滑动时

在父View中判断是否拦截

 @Override

   publicbooleanonInterceptTouchEvent(MotionEvent event) {

        boolean intercepted = false;

        int x = (int) event.getX();

        int y = (int) event.getY();

 

        switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN: {

            intercepted = false;

            if (!mScroller.isFinished()) {

                mScroller.abortAnimation();

               intercepted = true;

            }

            break;

        }

        case MotionEvent.ACTION_MOVE: {

            int deltaX = x - mLastXIntercept;

            int deltaY = y - mLastYIntercept;

            if (Math.abs(deltaX) > Math.abs(deltaY)){

                intercepted = true;

            } else {

                intercepted = false;

            }

            break;

        }

        case MotionEvent.ACTION_UP: {

            intercepted = false;

            break;

        }

        default:

            break;

        }

 

        Log.d(TAG,"intercepted="+ intercepted);

        mLastX = x;

        mLastY = y;

        mLastXIntercept = x;

        mLastYIntercept = y;

        return intercepted;

   }

参考:

1.图解 Android 事件分发机制

2.Hongyang的一遍文章(抱歉不记得网址了)

3.任玉刚的《andorid开发艺术探索》



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值