在最近的项目中碰到需要用手指控制View移动的需求,实现的过程中发现View会随着手指的移动而抖动,并且抖动程度随着拖动距离的增大而增大。简化代码片段如下
view.setOnTouchListener(new OnTouchListener() {
float lastY;
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN)
lastY = event.getY();//获得起始纵坐标
if(event.getAction() == MotionEvent.ACTION_MOVE ){
//执行这一步时view会发生Y轴上的抖动并且抖动程度会随着ACTION_MOVE的执行次数而加剧
view.setTranslationY(event.getY()-lastY);
//这一步用来探明抖动的原因来自于event.getY()异常
Log.i("Y", event.getY()+"");
}
}
return false;
});
笔者将view匀速向上拖移,Log输出如下:
03-28 08:32:26.050: I/Y(2234): 76.75454
03-28 08:32:26.210: I/Y(2234): 73.17617
03-28 08:32:26.340: I/Y(2234): 75.82344
03-28 08:32:26.650: I/Y(2234): 71.12374
03-28 08:32:27.100: I/Y(2234): 74.87508
03-28 08:32:27.250: I/Y(2234): 70.071075
03-28 08:32:27.510: I/Y(2234): 73.92541
03-28 08:32:27.650: I/Y(2234): 67.97551
03-28 08:32:27.800: I/Y(2234): 74.02449
03-28 08:32:27.890: I/Y(2234): 66.935455
03-28 08:32:28.530: I/Y(2234): 73.063614
03-28 08:32:28.620: I/Y(2234): 65.8301
03-28 08:32:28.920: I/Y(2234): 71.153694
03-28 08:32:29.120: I/Y(2234): 64.841194
03-28 08:32:29.830: I/Y(2234): 70.1468
03-28 08:32:30.070: I/Y(2234): 63.8322
可以看出确实为event.getY()出了问题,于是翻阅getY()的源码如下:
/**
* {@link #getY(int)} for the first pointer index (may be an
* arbitrary pointer identifier).
*
* @see #AXIS_Y
*/
public final float getY() {
return nativeGetAxisValue(mNativePtr, AXIS_Y, 0, HISTORY_CURRENT);
}
由于nativeGetAxisValue()是native方法所以我们无法直接在在eclipse中看到具体的方法定义,不过至少我们知道getY()返回的值是和AXIS_Y有关的,所以继续查阅AXIS_Y的说明,如下:
public static final int AXIS_Y
Added in API level 12
Axis constant: Y axis of a motion event.
·For a touch screen, reports the absolute Y screen position of the center of the touch contact area. The units are display pixels.
·For a touch pad, reports the absolute Y surface position of the center of the touch contact area. The units are device-dependent; use getMotionRange(int) to query the effective range of values.
·For a mouse, reports the absolute Y screen position of the mouse pointer. The units are display pixels.
·or a trackball, reports the relative vertical displacement of the trackball. The value is normalized to a range from -1.0 (up) to 1.0 (down).
·For a joystick, reports the absolute Y position of the joystick. The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
从"For a touch screen, reports the absolute Y screen position of the center of the touch contact area"中得知AXIS_Y表示的是触摸点相对于触摸区域的绝对Y轴坐标,也就是pointer相对于所触摸View的absoluteY,读到这里大概就明白了为什么会出现View的抖动问题:
因为View是移动的,在获取相对坐标的过程中,getY()=AXIS_Y=absoluteY+viewTranslationY(这就是为什么抖动程度会随着拖动距离的增加而增加),从Log日志中可以发现,抖动是逐帧间隔形式的,即一次 AXIS_Y=absoluteY之后接着便是AXIS_Y=absoluteY+viewTranslationY。由于无法看到nativeGetAxisValue()的具体定义,所以暂时并不清楚造成这种结果的具体原因。
解决办法很简单,只要把getY()换成getRawY()即可,后者的源码获取的是pointer相对于整个screen的绝对坐标,这里就不贴出来了。
分析告诉我们,getX/Y()不适用于绑定在动态组件上的onTouchListener。
1.getRawX、getRawY与getX、getY的区别
在编写android的自定义控件,或者判断用户手势操作时,往往需要使用MotionEvent中的getRawX()、getRawY()与getX()、getY()取得触摸点在X轴与Y轴上的距离,这四个方法都返回一个float类型的参数,单位为像素(Pixel)。getRawX()、getRawY()返回的是触摸点相对于屏幕的位置,而getX()、getY()返回的则是触摸点相对于View的位置。
以下两张图直观的表现了这几个方法的区别,在屏幕中央放置了一个Button,并为它注册了OnTouchListener,图中绿圆点为触摸点位置。
imageimage
2.View中的getScrollX、getScrollY
getScrollX()与getScrollY()的值由调用View的scrollTo(int x, int y)或者scrollBy(int x, int y)产生,其中scrollTo是将View中的内容移动到指定的坐标x、y处,此x、y是相对于
View的左上角,而不是屏幕的左上角。scrollBy(int x, int y)则是改变View中的相对位置,参数x、y为距离上一次的相对位置。
文字解释总是不好理解的,那么我们就直接上图吧,直观一些。
image image image (图1) (图2) (图3)
1.图1中,屏幕中心放置了一个button,而button的内容被放置在了它的左上角。
2.调用button的scrollTo(-100, -100)方法,结果如图2所示,button内的内容被移至相对button左上角(-100,-100)的位置
3.对图2的button调用scrollBy(-100,-100)方法,结果如图3所示,button内的内容被移至相对于图2的(-100,-100)位置
这时的getScrollX()与getScrollY()的值为:
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">06-15 15:44:56.072 20471-20471/com.test.yangy.studiotest V/ScrollActivity﹕ btn scroll X=-200
06-15 15:44:56.072 20471-20471/com.test.yangy.studiotest V/ScrollActivity﹕ btn scroll Y=-200</pre>
值得注意的是,当View中的内容向右移动时,getScrollX()的值为负数,同理,向scrollTo与scrollBy的x中传入负数,view中的内容向右移动,反之向左。
当View中的内容向下移动时,getScrollY()的值为负数,同理,向scrollTo与scrollBy的y中传入负数,view中的内容向下移动,反之向上。