触摸辅助类与 View 滑动
2026/9/14大约 3 分钟
触摸辅助类与 View 滑动
VelocityTracker 追踪滑动速度、GestureDetector 检测手势、Scroller 实现弹性滑动,三者是自定义 View 交互的常用辅助类。
一、VelocityTracker 速度追踪
VelocityTracker 用于追踪手指在滑动中的速度:
Java
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000); // 单位:像素/秒
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
velocityTracker.clear();
velocityTracker.recycle();
return false;
}
});Kotlin
view.setOnTouchListener { _, event ->
val velocityTracker = VelocityTracker.obtain()
velocityTracker.addMovement(event)
velocityTracker.computeCurrentVelocity(1000) // 单位:像素/秒
val xVelocity = velocityTracker.xVelocity.toInt()
val yVelocity = velocityTracker.yVelocity.toInt()
velocityTracker.clear()
velocityTracker.recycle()
false
}二、GestureDetector 手势检测
GestureDetector 辅助检测单击、滑动、长按、双击等行为:
Java
final GestureDetector mGestureDetector = new GestureDetector(this,
new GestureDetector.OnGestureListener() {
@Override public boolean onDown(MotionEvent e) { return false; }
@Override public void onShowPress(MotionEvent e) { }
@Override public boolean onSingleTapUp(MotionEvent e) { return false; }
@Override public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) { return false; }
@Override public void onLongPress(MotionEvent e) { }
@Override public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) { return false; }
});
mGestureDetector.setOnDoubleTapListener(new OnDoubleTapListener() {
@Override public boolean onSingleTapConfirmed(MotionEvent e) { return false; }
@Override public boolean onDoubleTap(MotionEvent e) { return false; }
@Override public boolean onDoubleTapEvent(MotionEvent e) { return false; }
});
// 解决长按屏幕后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false);
imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return mGestureDetector.onTouchEvent(event);
}
});Kotlin
val mGestureDetector = GestureDetector(this,
object : GestureDetector.OnGestureListener {
override fun onDown(e: MotionEvent): Boolean = false
override fun onShowPress(e: MotionEvent) { }
override fun onSingleTapUp(e: MotionEvent): Boolean = false
override fun onScroll(e1: MotionEvent, e2: MotionEvent,
distanceX: Float, distanceY: Float): Boolean = false
override fun onLongPress(e: MotionEvent) { }
override fun onFling(e1: MotionEvent, e2: MotionEvent,
velocityX: Float, velocityY: Float): Boolean = false
})
mGestureDetector.setOnDoubleTapListener(object : OnDoubleTapListener {
override fun onSingleTapConfirmed(e: MotionEvent): Boolean = false
override fun onDoubleTap(e: MotionEvent): Boolean = false
override fun onDoubleTapEvent(e: MotionEvent): Boolean = false
})
// 解决长按屏幕后无法拖动的问题
mGestureDetector.setIsLongpressEnabled(false)
imageView.setOnTouchListener { _, event ->
mGestureDetector.onTouchEvent(event)
}选择建议
监听滑动相关建议在 onTouchEvent 中实现;监听双击等复杂手势使用 GestureDetector。
三、Scroller 弹性滑动
Scroller 本身无法让 View 弹性滑动,需要和 View 的 computeScroll 方法配合使用:
Java
Scroller mScroller = new Scroller(mContext);
private void smoothScrollTo(int destX) {
int scrollX = getScrollX();
int delta = destX - scrollX;
// 1000ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0, delta, 0, 1000);
invalidate();
}
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}Kotlin
val mScroller = Scroller(mContext)
private fun smoothScrollTo(destX: Int) {
val scrollX = scrollX
val delta = destX - scrollX
// 1000ms 内滑向 destX,效果就是慢慢滑动
mScroller.startScroll(scrollX, 0, delta, 0, 1000)
invalidate()
}
override fun computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.currX, mScroller.currY)
postInvalidate()
}
}原理: startScroll 本身无法让 View 滑动;invalidate 导致 View 重绘,重绘时在 draw 方法中调用 computeScroll,computeScroll 向 Scroller 获取当前 scrollX/scrollY,通过 scrollTo 滑动,再调用 postInvalidate 如此反复,直到滑动结束。
四、View 的滑动方式
1. scrollTo / scrollBy
适合对 View 内容的滑动,scrollBy 实际上也是调用了 scrollTo:
Java
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}Kotlin
fun scrollTo(x: Int, y: Int) {
if (mScrollX != x || mScrollY != y) {
val oldX = mScrollX
val oldY = mScrollY
mScrollX = x
mScrollY = y
invalidateParentCaches()
onScrollChanged(mScrollX, mScrollY, oldX, oldY)
if (!awakenScrollBars()) {
postInvalidateOnAnimation()
}
}
}
fun scrollBy(x: Int, y: Int) {
scrollTo(mScrollX + x, mScrollY + y)
}要点:
mScrollX等于 View 左边缘与 View 内容左边缘在水平方向的距离;mScrollY等于 View 上边缘与 View 内容上边缘在竖直方向的距离;scrollTo/scrollBy只能改变 View 内容的位置,不能改变 View 在布局中的位置。
2. 使用动画
操作简单,主要适用于没有交互的 View 和实现复杂动画效果。
3. 改变布局参数
操作稍微复杂,适用于有交互的 View:
Java
ViewGroup.MarginLayoutParams params =
(ViewGroup.MarginLayoutParams) view.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
view.requestLayout();
// 或者 view.setLayoutParams(params);Kotlin
val params = view.layoutParams as ViewGroup.MarginLayoutParams
params.width += 100
params.leftMargin += 100
view.requestLayout()
// 或者 view.setLayoutParams(params)五、方式对比
四种滑动方式的取舍对比如下:
| 滑动方式 | 特点 | 适用场景 |
|---|---|---|
| scrollTo/scrollBy | 简单,只能滑动内容 | 内容滚动 |
| 动画 | 简单,无交互 | 无交互 View、复杂动画 |
| 改变布局参数 | 灵活,可交互 | 有交互的 View |