Android系统中有两种坐标系, Android坐标系和View坐标系.
Android坐标系
Android坐标系对应于屏幕左上角原点(0,0)坐标, X轴向右递增, Y轴向下递增. getRawX()和getRawY()用于获取屏幕坐标. getRawX()于getRawY()获得相对于屏幕的触摸事件位置坐标. 如下图:
View坐标系
其次,还有View坐标系. View坐标系与Android坐标系相辅相成. View坐标系中坐标使用getX() 和 getY()获得. 它与系统坐标系的区别在于View坐标系的以View的左上角作为坐标原点,X轴向右递增,Y轴向下递增.getX()与getY()获得触摸事件(MotionEvent)的坐标, 即相对于View的触摸位置坐标. 如下图:
View的位置
一个View的位置由View的(左上右下)四个顶点决定,分别通过getLeft(), getTop(), getRight()和getBottom()方法获得. 该方法分别返回View的mLeft, mTop, mRight, mBottom.
其中Android官方文档对以上属性的注释分别为:
mLeft: Left position of this view relative to its parent.
mTop: Top position of this view relative to its parent.
可见View的坐标位置是相对于其父View的. getLeft() 得到的是 View的左边界到父 View左边界的距离, getRight得到的是View的右边界到父 View的左边界的距离. getTop()则是View的上边界到父 View上边界的距离, getBottom()是View的底部边界到父 View上边界的距离.
触控事件
MotionEvent继承自InputEvent, 是Android系统中关于用户交互重要的类. 内部封装了常用的事件常量:
ACTION_DOWN
ACTION_UP
ACTION_MOVE
ACTION_CANCEL
ACTION_OUTSIDE
ACTION_SCROLL
......
ACTION_CANCEL
官方对ACTION_CANCEL事件的解释是当前手势被中断,你将不会收到关于它的任何点. 且你应该认为它是一个UP事件,但是不执行任何操作. Android的事件体系中 DOWN 到 UP所有的事件称为一次完整的事件序列, 当一个事件序列正在执行, 父View拦截其中一个事件时,当前View将会收到一个CANCEL事件, 当前事件之后的事件序列将不会被执行. 详细可在ViewGroup中查看关于CANCEL的处理.
ACTION_OUTSIDE
手势超出View元素边界时,将会发出该事件. 默认情况下该事件不会分发给ViewGroup的任何子 View,且应当在根布局中处理. 对于该事件可以参考Android对Dialog的处理.
ACTION_SCROLL
scroll事件不是touch事件,因此不会被分发给onTouchEvent,而是由onGenericMotionEvent()处理.此操作始终传递到指针下的窗口或视图. 和scroll事件一样,还有其他很多事件交给onGenericMotionEvent()处理.
MotionEvent通过getX()/getY() 或者 getRawX()/getRawY()获得用户输入事件的View坐标或屏幕坐标.
View坐标补充
1. View.getX()...
据官方定义getX()方法返回的是以像素为单位,View的可视x轴位置. 相当于View的translationX + mLeft 之和. 也就是说View的mLeft加当前View的X轴的偏移量. 假定一个View初始坐标(View坐标系)是(100,100), 且宽高均为100. 那么其mLeft也就是100, 而其translationX偏移量初始值是0, View.getX()返回0. 但是如果通过属性动画ObjectAnimator.ofFloat(view,translationX...使得View的坐标发生改变50个像素,那么View的getX()就是mLeft(100) + translationX(50) = 150.
2. View.getTranslationX()...
getTranslationX()返回当前View相对于mLeft属性在X轴方向的偏移量, 当然使用属性动画使View发生平移,即改变View的TranslationX属性,但并未改变View的原始坐标.
3. View.getScrollX()...
scrollX反应的是scrollTo()和scrollBy()函数的影响,它是View的内容水平移动的的偏移量. scrollTo()和scrollBy()会改变View的内容位置. 手机的屏幕可以视为一个窗口,窗口内部的内容可以无限的移动变化. 见图:
这张图来源网上,只是觉得用来描述scrollX偏移量在合适不过就拿来使用.
橙色方框可以视为屏幕窗口, 蓝色方块就是屏幕展示的内容, 蓝色区域在窗口左边界的初始x坐标是0,当向右推动,其值变为-100, getScrollX()返回-100. 当向左边推动,蓝色区域在窗口左边界的X坐标为100, getScrollX()返回100.
View的滑动方式
View的滑动主要是操作View的坐标,View的滑动方式有6种:
1. layout(left,top,right,bottom)
layout方式是通过不断更新View的mLeft,mTop等属性更新View的位置, 内部通过setFrame()方法进行位置更新.
2. offsetLeftAndRight(offsetX) 和 offsetTopAndBottom(offsetY)
offsetLeftAndRight()传入的是一个偏移量,即要平移的距离.
3. 通过layoutParams设置margin值改变View的位置实现滑动
LayoutParam中封装了View的宽高等信息,View也可以通过LayoutParam来设置margin来实现移动,如leftMargin,topMargin等.
4. View动画和属性动画
View动画底层采用Matrix(矩阵)实现,现在先不说Matrix的具体原理. 虽然View动画可以实现平移,但实际上却更像是一个假象. View的位置并不会平移,如果View有点击事件,那么只有动画开始之前的位置会触发事件.
属性动画改变了View动画的缺点, 内部通过改变View的相关属性实现View的滑动.
5. scrollTo()和scrollBy()
scrollTo(x,y) 传入的一个坐标位置,scrollBy()实际上仍然调用scrollTo()函数,scrollBy()则传入一个偏移量. 需要注意的是,无论scrollTo()还是scrollBy(),滑动的都是View的内容, 如果是ViewGroup,则会滑动ViewGroup内的全部子 View.
6. 结合Scroller实现滑动
Scroller本身并不会实现滑动, 需要结合View自身的一些方法实现滑动. 使用layout,scrollTo等方法实现滑动,效果比较生硬.使用方法如下:
1. 初始化一个Scroller
Scroller scroller = new Scroller();
2. 调用startScroller(),该方法并非要开始滑动,而是要设置View滑动的相关参数
scroller.startScroll(..); postInvalidate(); //赋值后使View重绘
3. 在View的回调方法onCompute()方法中实现滑动
if(scroller.computeScrollOffset()){
// 返回true,view的滑动未完成.该方法内部计算了View坐标随时间发生的变化
scrollTo(x,y); //使View滑动
postInvalidate(); //继续重绘
}