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坐标系

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的弹性滑动,可以参考这篇博文.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容