一个跟随手指移动的 View 产生的抖动问题
Posted 亦枫Blog
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个跟随手指移动的 View 产生的抖动问题相关的知识,希望对你有一定的参考价值。
比如有这样一个常见的 app 功能,一个跟随手指移动的按钮,常见如屏幕悬浮球、拖拽手柄等。
实现这样的需求,必然要处理 view 的 onTouch 事件,大概的一个代码模板就是这样:
private float panelLastY;
private float panelStartX;
private float panelStartY;
@SuppressWarnings("ClickableViewAccessibility")
private void initTouchEvent()
panelView.setOnTouchListener(new View.OnTouchListener()
@Override
public boolean onTouch(View v, MotionEvent event)
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
panelLastY = event.getRawY();
panelStartX = event.getX();
panelStartY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
Log.e("yifeng", "onTouchY: " + event.getY());
Log.e("yifeng", "onTouchRawY: " + event.getRawY());
int deltaY = (int) (event.getRawY() - panelLastY);
panelLastY = event.getRawY();
//TODO 通过 margin 或者 translationY 控制 view 移动
break;
case MotionEvent.ACTION_UP:
float panelEndX = event.getX();
float panelEndY = event.getRawY();
if (Math.abs(panelEndX - panelStartX) < 10 && Math.abs(panelStartY - panelEndY) < 10)
//TODO 处理 Click 事件
break;
//TODO 处理离开事件
break;
// 返回 true 拦截后续事件传递
return true;
);
借助这个例子说明几点问题,这段代码主要是获取并处理 event 事件的 Y 值。其中有几点需要注意的是:
第一,onTouch 方法返回值必须为 true,表示当前触摸事件已被消费,拦截后续事件传递;
第二,click 事件。由于第一点中 onTouch 返回值为 true,view 的 click 事件自然也被拦截,那么就需要手动处理 click 事件。可以通过判断 event 偏移量来实现;
第三,一个完善的跟随手指移动的功能,通常少不了快速滑动的功能,判定滑动速度,需要用到专业的 GestureDetector 类。当然这里没有写,因为使用这个类后,相关逻辑都要搬出 onTouch 事件,到 Gesture 中处理。桥接代码类似:
private GestureDetector.SimpleOnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener()
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
Log.e("yifeng", "onFling: " + velocityX + " " + velocityY);
if (Math.abs(velocityX) > 100 || Math.abs(velocityY) > 100)
//TODO 处理快速滑动事件,通常与 ACTION_UP 处理结果相同
return super.onFling(e1, e2, velocityX, velocityY);
;
private GestureDetector gestureDetector = new GestureDetector(gestureListener);
public boolean onTouch(View v, MotionEvent event)
return gestureDetector.onTouchEvent(event);
第四,使用 getRawX 与 getRawY 方法而不是 getX 与 getY 方法。这才是我想强调的最重要的一点。前者表示获取 touch event 事件产生的屏幕坐标值,对应于屏幕坐标系;后者表示相对于父容器 View 的坐标值,对应于 View 所处父容器的位置。看似这两种坐标系函数都可以使用,计算偏差而已,实际上后者的使用会有问题。
还拿最开始那段代码示例,同一段手势向下的操作,这是 getY 的打印日志:
E/yifeng: onTouchY: 34.89081
E/yifeng: onTouchY: 88.31476
E/yifeng: onTouchY: 60.086853
E/yifeng: onTouchY: 67.28284
E/yifeng: onTouchY: 64.049866
E/yifeng: onTouchY: 69.03436
E/yifeng: onTouchY: 75.53076
E/yifeng: onTouchY: 73.07654
E/yifeng: onTouchY: 67.24097
E/yifeng: onTouchY: 66.67041
E/yifeng: onTouchY: 60.636597
E/yifeng: onTouchY: 63.618164
E/yifeng: onTouchY: 60.124634
E/yifeng: onTouchY: 58.584595
E/yifeng: onTouchY: 55.103027
这是 getRawY 的打印日志:
E/yifeng: onTouchRawY: 793.8908
E/yifeng: onTouchRawY: 854.31476
E/yifeng: onTouchRawY: 886.08685
E/yifeng: onTouchRawY: 924.28284
E/yifeng: onTouchRawY: 959.04987
E/yifeng: onTouchRawY: 998.03436
E/yifeng: onTouchRawY: 1042.5308
E/yifeng: onTouchRawY: 1084.0765
E/yifeng: onTouchRawY: 1119.241
E/yifeng: onTouchRawY: 1153.6704
E/yifeng: onTouchRawY: 1181.6366
E/yifeng: onTouchRawY: 1211.6182
E/yifeng: onTouchRawY: 1237.1246
E/yifeng: onTouchRawY: 1260.5846
E/yifeng: onTouchRawY: 1280.103
E/yifeng: onTouchRawY: 1315.1056
发现区别了吗?getY 值不稳定,相邻值前后大小变来变去;getRawY 值则是线性的一个方向上的变化,符合实际手势的操作行为。
具体反应在跟随手指移动的 View 上面,会发现使用 getY 函数产生 View 抖动特别明显,效果很差。而使用 getRawY 方法不会导致 View 抖动,比较顺畅。
所以,理论上使用 motionEvent 获取 touch 事件的坐标值,不论使用什么方法都能计算正确,但是由于 getX 与 getY 函数结果的不稳定性,只能选择基于屏幕坐标系的 getRawX 与 getRawY 函数。这是实践中特别需要的一个细节问题。
推荐阅读:1024 程序员节,是时候薅羊毛屯书了如何像 IDE 一样浏览 GitHub 网站的项目?简直是搜索引擎界的新起之秀,你值得拥有!
长按识别二维码,关注我,一名爱叨叨的程序员
以上是关于一个跟随手指移动的 View 产生的抖动问题的主要内容,如果未能解决你的问题,请参考以下文章