View的坐标系,滑动以及事件传递基础

View的坐标系和位置参数

Android中,view的坐标体系是相对于父View而言的

getTop(); //获取子View初始位置左上角距父View顶部的距离
getLeft(); //获取子View初始位置左上角距父View左侧的距离
getBottom(); //获取子View初始位置右下角距父View顶部的距离
getRight(); //获取子View初始位置右下角距父View左侧的距离

在MotionEvent中,获取触摸点的位置时,Event中除了get的一系列方法之外,还有一个getRaw系列,它是相对于屏幕坐标系来说的。
从Anroid3.0开始,View又增加了几个额外的参数,x,y,translationX 和 translationY。

View.getX() //获取View当前位置左侧距离父View左侧的距离
View.getY() //获取View当前位置顶部侧距离父View顶部的距离
View.getTranslationX() //View在X方向的偏移量,即当前位置相对于初始位置的滑动距离,默认值为 0,左滑为负值
View.getTranslationY() //View在Y方向的偏移量,即当前位置相对于初始位置的滑动距离,默认值为 0,左滑为负值

所以,getX() =getLeft() + getTranslationX() 。如果View的位置不发生改变,getX = getLeft,getTranslationX = 0;View通过滑动(比如动画)改变位置的时候,发生改变的只有x,y,translationX 和 translationY。
另外,通过以上方法所获取到的值,单位都是像素(px)。
参考下图:

View的坐标系,滑动以及事件传递基础_第1张图片
View坐标系

View事件的相关概念

MotionEvent

MotionEvent,翻译过来就是动作事件,在Android中用它来接收和处理屏幕上的各种触摸动作。在手指触摸屏幕所产生的一系列事件中,最典型的有以下几种:

  • ACTION_DOWN 手指接触屏幕
  • ACTION_MOVE 手指在屏幕上移动
  • ACTION_UP 手指抬起

结合实际情况,可能会有以下几种结果:

ACTION_DOWN -> ACTION_UP 点击屏幕后松开
ACTION_DOWN -> ACTION_MOVE ->...->ACTION_MOVE ->ACTION_UP 点击滑动,最后松开
这两种情况就是两个完整的事件流。

为了处理点击事件,MotionEvent提供了一组获取点击坐标的方法: getX/RawX,getY/getRawY。
getX/Y:获取点击位置相对于当前View左上角的坐标
getRawX/Y:获取点击位置相对于屏幕左上角的坐标。这里跟View是不一样的,它没有父View的概念。
关于MotionEvent的详细介绍,可以参考这位同学的文章;

TouchSlop

ToucSlop是指系统所能识别出的最小滑动距离,我们可以用它来做一些过滤动作,小于这个值的就不被认为是滑动。这是一个与设备相关的常量,可以通过以下方法获取:

ViewConfiguration.get(getContext()).getScaledTouchSlop()

VelocityTracker

VelocityTracker是速度追踪的意思,顾名思义,它就是用来追踪滑动过程中的速度。使用方法如下:
首先在View的onTouchEvent方法中获取对象,对添加对事件的追踪:

VelocityTracker tracker = VelocityTracker.obtain();
        tracker.addMovement(event);

然后设置速度的单位,这里的单位是指像素与时间的关系。比如我们将单位设置为1000(1s),在这1s内划过的像素数为1000,得到的结果并不是1 px/ms,而是1000,它指的是在整个时间段内划过的像素数。代码如下:

tracker.computeCurrentVelocity(1000);
        int xVelocity = (int) tracker.getXVelocity();
        int yVelocity = (int) tracker.getYVelocity();

GestureDector

手势检测,用于辅助检测用户的动作行为,比如单击,滑动,长按等等。它是和onTouchEvent来配合使用的。当处理比较复杂的事件时,只用onTouchEvent可能会显得非常麻烦,这时候就可以将事件托管给GestureDector来处理。

GestureDector提供了两个接口:
GestureDetector.OnGestureListener 和 GestureDetector.OnDoubleTapListener,分别提供了针对不同事件的方法。

OnGestureListener :

       // 用户轻触触摸屏,由1个MotionEvent ACTION_DOWN触发       
        public boolean onDown(MotionEvent e) {    
            Log.i("MyGesture", "onDown");       
            Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();       
            return false;    
        }    

        /\*    
          用户轻触触摸屏,尚未松开或拖动,由一个1个MotionEvent ACTION_DOWN触发    
          注意和onDown()的区别,强调的是没有松开或者拖动的状态    
            
          而onDown也是由一个MotionEventACTION_DOWN触发的,但是他没有任何限制,  
          也就是说当用户点击的时候,首先MotionEventACTION_DOWN,onDown就会执行,  
          如果在按下的瞬间没有松开或者是拖动的时候onShowPress就会执行,如果是按下的时间超过瞬间  
          拖动了,就不执行onShowPress。  
         \*/    
        public void onShowPress(MotionEvent e) {    
            Log.i("MyGesture", "onShowPress");       
            Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT).show();       
        }    

        // 用户(轻触触摸屏后)松开,由一个1个MotionEvent ACTION_UP触发       
        ///轻击一下屏幕,立刻抬起来,才会有这个触发    
        //从名子也可以看出,一次单独的轻击抬起操作,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以这个事件 就不再响应    
        public boolean onSingleTapUp(MotionEvent e) {    
            Log.i("MyGesture", "onSingleTapUp");       
            Toast.makeText(MainActivity.this, "onSingleTapUp", Toast.LENGTH_SHORT).show();       
            return true;       
        }    

        // 用户按下触摸屏,并拖动,由1个ACTION_DOWN, 多个ACTION_MOVE触发       
        public boolean onScroll(MotionEvent e1, MotionEvent e2,    
                float distanceX, float distanceY) {    
            Log.i("MyGesture22", "onScroll:"+(e2.getX()-e1.getX()) +"   "+distanceX);       
            Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG).show();       
            return true;       
        }    

        // 用户长按触摸屏       
        public void onLongPress(MotionEvent e) {    
             Log.i("MyGesture", "onLongPress");       
             Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG).show();       
        }    
  
        // 用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发,这是快速滑动行为       
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,    
                float velocityY) {    
            Log.i("MyGesture", "onFling");       
            Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG).show();       
            return true;    
        }   
 

OnDoubleTapListener:

       /\* 
          用来判定该次点击是SingleTap而不是DoubleTap,如果连续点击两次就是DoubleTap手势,如果只点击一次, 
         系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap, 
         然后触发SingleTapConfirmed事件。只要触发该事件,那么当前事件一定是单击而不是双击中的一次。 
         \*/  
        @Override  
        public boolean onSingleTapConfirmed(MotionEvent e) {  
            // TODO Auto-generated method stub  
            return false;  
        }  
  
        /\*双击事件 
         \*/  
        @Override  
        public boolean onDoubleTap(MotionEvent e) {  
            // TODO Auto-generated method stub  
            return false;  
        }  
  
        /\*  
         双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作,包含down、up和move事件 
          @MotionEvent e中包含了双击直接发生的其他动作. 
         \*/  
        @Override  
        public boolean onDoubleTapEvent(MotionEvent e) {  
            // TODO Auto-generated method stub  
            return false;  
        }  

除此之外,GestureDetector还提供了一个简单的内部类,SimpleOnGestureListener,基本就是将两个listener拼到了一起。
GestureDetector使用起来也非常简单,首先要获取GestureDetector对象,然后在View的onTouchEvent方法中接管Event。代码如下:

GestureDetector mGestureDetector = new GestureDetector(this);//当前class继承了GestureDetector的接口
@Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.setIsLongpressEnabled(false);//解决长按后屏幕无法拖动
        return mGestureDetector.onTouchEvent(event);
        
    }

View的滑动

View的滑动是各种控件的基础,比如ViewPager,SlidingMenu,下拉刷新等等,他们的基础都是滑动。通常来说,View的滑动有以下三种方式:

  • scrollTo/By(int x, int y);
    scrollBy是相对上一个位置的滑动,x,y为滑动的像素距离,负值表示向下或右,scrollTo表示绝对滑动。简单来说,scrollTo表示相对于View的初始位置滑动x,y个像素,而scrollBy是相对于上一个位置滑动x,y个像素。

  • 使用动画滑动
    举个简单例子:

ObjectAnimator.ofFloat(imageView,"translationX",-30f).setDuration(1000).start(); 

表示在1s内,将imageView沿X方向向左滑动30个像素。

  • 改变布局参数
    这个很好理解,直接上代码:
  //MarginLayoutParams params = (MarginLayoutParams) mButton.getLayoutParams();
  LayoutParams params = (LayoutParams) mButton.getLayoutParams();
  params.width += 100;
  mButton.requestLayout();
  //mButton.setLayoutParams(params);    

注意:scrollTo/By(int x, int y),采用该方法进行滑动,滑动的只是View的内容,而View的位置不发生变化。比如将一个button通过scrollTo/By(int x, int y)移动到新的位置之后,点击当前button所处的位置是没有反应的,因为View实际上仍然处于原始位置。
这里只是简单介绍了View的滑动方法,关于View的弹性滑动,可以参考这篇博文.

你可能感兴趣的:(View的坐标系,滑动以及事件传递基础)