Android 坐标系和 MotionEvent 分析、滑动

1.Android坐标系

在Android中,屏幕最左上角的顶点作为Android坐标系的原点,这个点向左是X轴正方向,这个点向下是Y轴正方向。

系统提供了getLocationOnScreen(int location[])这样的方法来获得Android坐标系中中点的位置(即该图的左上角在Android坐标系中的坐标)。另外,触控事件中使用getRawX() 、getRawY()方法所获得的坐标同样是Android坐标系中的坐标

2.视图坐标系
Android除了上面说的坐标系之外,还有一个视图坐标系,它描述了子视图在父视图中的位置关系。这两种坐标系并不矛盾也不复杂,他们的作用是相辅相成的,正方向的指向是相同的,只是原点的位置不再是Android坐标系中的屏幕最左上角,而是以父视图左上角为坐标原点。在触控事件中,通过getX()、getY()坐标就是视图坐标系中的坐标

3.触控事件—MotionEvent
触控事件MotionEvent在用户交互中,占据着举足轻重的地位,学好触控事件是掌握后续内容的基础,首先我们先看看MotionEvent中封装的一些常用的常量,它定义了触控事件的不同类型。


    /** 单点触摸按下操作*/
    public static final int ACTION_DOWN             = 0;
    
    /** 单点触摸离开操作*/
    public static final int ACTION_UP               = 1;
    
    /** 触摸点移动操作*/
    public static final int ACTION_MOVE             = 2;
    
    /** 触摸操作取消*/
    public static final int ACTION_CANCEL           = 3;
    
    /** 触摸操作超出边界 */
    public static final int ACTION_OUTSIDE          = 4;

    /** 多点触摸按下操作*/
    public static final int ACTION_POINTER_DOWN     = 5;
    
    /** 多点离开操作*/
    public static final int ACTION_POINTER_UP       = 6;

通常情况下,我们会在onTouchEvent(MotionEvent event)方法中通过event.getAction() 方法来获取触控事件的类型并通过switch-case方法来筛选,这代码的模式基本固定,如下:

@Overrride
public boolean onTouchEvent(MotionEvent event) {

    // 获取当前输入点的X,Y坐标(视图坐标)    
    int x = (int) event.getX();
    int y = (int) event.getY();

    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            // 处理输入的按下事件
            break; 
     
        case MotionEvent.ACTION_MOVE:
            // 处理输入的移动事件
            break; 

        case MotionEvent.ACTION_UP:
            // 处理输入的离开事件
            break;        

    }
    return true;
}

这个代码模板是不涉及多点操作的,基本上可以帮助完成常见的触控操作的监听。

下面图示可以帮助你了解测量时的API如何使用它们,并帮助对MotionEvent和Android坐标系有了一个比较清楚的认识。

4.七种滑动方法
(1).layout方法
简单的来说就是,记录触摸的初始值和偏移量,然后通过layout方法移动控件。
(2).offsetLeftAndRight()与offsetTopAndBottom()
这个是系统提供的一个对上下、左右移动的API的封装。当计算出来偏移量后,需要使用下面的代码就可以重新布局,效果和使用Layout一样,代码如下。

// 同时对left和right进行偏移 
offsetLeftAndRight(offsetX);
// 同时对top和bottom进行偏移
offsetTopAndBottom(offsetY);

(3).LayoutParams
LayoutParams保存了一个View的布局参数。我们可以改变LayoutParams来动态修改一个布局的位置参数,从而来达到改变View位置的效果。我们可以很方便的在程序中使用getLayoutParams()来获取一个View的LayoutParams。获取到偏移量后就可以通过setLayoutParams()来改变其LayoutParams。不过,需要注意的是LayoutParams的类型问题,要根据父容器的类型来设置不同的类型的LayoutParams。如果不清楚父类容器是什么,可以使用ViewGroup.MarginLayoutParams。

(4).scrollTo 与 scrollBy
这两个方法是view提供的方法来改变一个View的位置,scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(dx, dy)表示移动的增量为dx,dy。需要注意的是,在View上面执行这两个方法,移动的并不是View本身而是View的内容,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移动的是所有的子View,如果在View中使用,那么移动的将是View的内容,例如TextView,内容就是它的文本,ImageView,内容就是它的drawable对象。

通过上面的分析,我们就应该知道为什么不能在View中使用这两个方法来拖动这个View了。那么我们就要在View所在的ViewGroup中来使用scrollBy方法,移动它的子View,代码如下所示:

((View) getParent.getScrollBy(offsetX, offsetY);

但是因为参考系的问题,在真正操作的时候,如果需要根据拖动来滑动,则需要做的事情就是在计算参数时需要做转负值操作:

int offsetX = x - lastX;
int offsetY = y - lastY;
((View)getParent.scrollBy(-offsetX, -offsetY);

通过查看下面的图就可以了解到为何要做这个处理。

(5).Scroller

Scroller类提供了自然过度动画来实现移动效果,即实现平滑移动的效果,而不再是瞬间完成的移动。下面我们会演示一下如何使用Scroller类实现平滑移动。需求是:子View跟随手指的滑动而滑动,但是在手指离开屏幕时,让子View平滑的移动到初始位置,即屏幕左上角。

使用Scroller类需要如下三个步骤:

  • 初始化Scroll
    首先要通过它的构造方法来创建一个Scroller对象:
// 初始化Scroller
mScroller = new Scroller(context);
  • 重写computeScroll()方法,实现模拟滑动
    下面我们需要重写computeScroll(),它是使用Scroller的核心,系统在绘制view的时候回在draw()方法中调用该方法。这个方法实际上就是使用的ScrollTo方法。再结合Scroller对象,帮助获取到当前的滚动值。我们可以通过不断地瞬间移动一个小的距离来实现整体上的平滑移动效果。computeScroll的代码可以利用如下模版代码来实现:
 @Override
    public void computeScroll() {
        super.computeScroll();
        // 判断Scroller是否执行完毕
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(
                    mScroller.getCurrX(),
                    mScroller.getCurrY());
            // 通过重绘来不断调用computeScroll
            invalidate();
        }
    }

Scroller 类提供了computeScrollOffset()方法来判断是否完成了整个滑动,同时也提供了getCurrX(),getCurrY()方法来获得当前的滑动坐标。在上面的代码中,唯一需要注意的是invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX和scrollY坐标,但是computeScroll是不会自动调用的,只能通过invalidate() --》draw()--》computeScroll()来间接调用computeScroll方法,实现循环获取scrollX和scrollY的目的。当模拟过程结束后,scroll.computeScrollOffset()方法会返回fasle,从而中断循环,完成整个平滑移动过程。

  • startScroll开启模拟过程

startScroll()方法具有两个重载方法:

 public void startScroll(int startX, int startY, int dx, int dy) {
        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
    }

 public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        
    }

在使用的时候,dx 和 dy的正负和上面scrollTo 与 scrollBy的方法的一样。代码如下:

case MotionEvent.ACTION_UP:
    // 手指离开时,执行滑动过程
    View viewGroup = ((View) getParent());
    mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY());
    invalidate();
    break;

6.属性动画
这个目前不做过多的介绍。

7.ViewDragHelper
这个功能很强大,可以先看一下QQ的侧边栏滑动,然后再总结这块的内容。

posted @ 2016-07-21 13:53  灰色飘零  阅读(2510)  评论(0编辑  收藏  举报