Android ScrollView监听全量触摸事件并记录rawX/rawY等参数问题
我之前也碰到过类似的需求——要记录ScrollView滑动过程中所有触摸点的rawX、rawY、pressure、orientation以及长短轴参数,同时不能破坏ScrollView原有的滚动行为。直接给内部TextView加OnTouchListener确实只能拿到ACTION_DOWN,因为后续事件会被ScrollView拦截处理了。
这里推荐通过自定义ScrollView并重写事件分发方法来实现,核心思路是在事件分发给子View或ScrollView自身处理之前,先把我们需要的参数提取出来,然后让事件正常流转,完全不影响原有功能。
步骤1:定义触摸事件回调接口
首先定义一个接口,用来传递我们需要的触摸参数,方便外部接收数据:
public interface TouchEventCallback { // 可以选择传递完整的MotionEvent,或者直接传拆解后的参数 void onTouchParamsCaptured(int pointerId, float rawX, float rawY, float pressure, float orientation, float touchMajor, float touchMinor); }
步骤2:实现自定义ScrollView
继承原生ScrollView,重写dispatchTouchEvent方法——这个方法是触摸事件分发的入口,所有触摸事件都会经过这里,我们可以在这里安全地捕获参数,然后让事件继续传递:
public class TrackableScrollView extends ScrollView { private TouchEventCallback mCallback; public TrackableScrollView(Context context) { super(context); } public TrackableScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public TrackableScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } // 设置外部回调 public void setTouchEventCallback(TouchEventCallback callback) { mCallback = callback; } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 只有当外部设置了回调时才处理参数 if (mCallback != null) { int pointerCount = ev.getPointerCount(); // 遍历所有触摸指针,捕获每个指针的参数 for (int i = 0; i < pointerCount; i++) { int pointerId = ev.getPointerId(i); // 计算对应指针的绝对坐标(rawX/rawY) float rawX = ev.getRawX() + (ev.getX(i) - ev.getX(0)); float rawY = ev.getRawY() + (ev.getY(i) - ev.getY(0)); // 获取压力、方向、长短轴参数 float pressure = ev.getPressure(i); float orientation = ev.getOrientation(i); float touchMajor = ev.getTouchMajor(i); float touchMinor = ev.getTouchMinor(i); // 把参数传递给外部回调 mCallback.onTouchParamsCaptured(pointerId, rawX, rawY, pressure, orientation, touchMajor, touchMinor); } } // 调用父类方法,保证ScrollView的滚动等原有功能不受影响 return super.dispatchTouchEvent(ev); } }
步骤3:在布局和代码中使用自定义ScrollView
布局文件替换原生ScrollView
<com.your.package.name.TrackableScrollView android:id="@+id/trackable_scroll" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 你的原有内容,比如TextView、LinearLayout等 --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="这里是很长的测试文本,用来触发ScrollView滚动..." /> </com.your.package.name.TrackableScrollView>
在Activity/Fragment中设置回调
TrackableScrollView scrollView = findViewById(R.id.trackable_scroll); scrollView.setTouchEventCallback(new TouchEventCallback() { @Override public void onTouchParamsCaptured(int pointerId, float rawX, float rawY, float pressure, float orientation, float touchMajor, float touchMinor) { // 在这里处理捕获到的参数,比如打印日志、保存到数据库等 Log.d("TouchTrack", String.format( "Pointer %d | rawX: %.2f, rawY: %.2f | Pressure: %.2f | Orientation: %.2f | Major: %.2f, Minor: %.2f", pointerId, rawX, rawY, pressure, orientation, touchMajor, touchMinor )); } });
关键说明
为什么用dispatchTouchEvent?
这个方法是触摸事件进入View体系的第一个方法,不管ScrollView是否拦截事件(比如滚动时会拦截ACTION_MOVE),所有事件都会经过这里,所以我们能捕获到完整的事件流(ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_POINTER_DOWN等)。为什么不影响ScrollView功能?
我们在处理完参数后调用了super.dispatchTouchEvent(ev),相当于把事件原封不动地传递给后续流程,ScrollView该滚动还是会滚动,完全不会被干扰。多点触控的处理
通过遍历ev.getPointerCount(),我们能捕获到每个触摸指针的参数,包括多点触控时的新增/移除指针事件,满足复杂场景的需求。
内容的提问来源于stack exchange,提问作者kriti

