一般来说,系统会自己处理好按键,触摸等一系列的操作,但是有些需求,可能需要自行监听一些按键行为,最常见就是很多App都有再按一次我就真的退出的选项.或者按了返回后还尝试尝试挽留一下你,或者监听物理键盘输入,比如音量增加的时候只是增加自己App音量,常见于音乐播放软件.
对EditText使用setOnKeyListener/setOnEditorActionListener可以创建监听按键的事件,如果直接重写Activity的onKeyDown可以监听在Activity发生的按键事件,简单例子.
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
// 可以拦截物理输入的,比如音量加减,可以拦截虚拟键盘上的虚拟按键中的控制按键,比如回车,删除.
if(keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
String desc = String.format("[按下]按键编码是 %d", i);
Log.d(TAG,desc);
}else{
String desc = String.format("[释放]按键编码是 %d", i);
Log.d(TAG,desc);
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 这个方法可以观察所有情况,当然也是仅限物理按键
// 最常见功能是接管退出,很多软件都有再按一次退出.
if(keyCode == KeyEvent.KEYCODE_BACK){
if(needExit){
finish(); // 真退出了
}else{
Toast.makeText(this,"再按一次就退出.",Toast.LENGTH_LONG).show();
needExit = true;
}
// 返回true是告诉系统,我处理完了,你不用继续处理.
return true;
}
return false;
}
除了按键事件,有时候也会监听触摸事件,比较常见就是短视频那种不断滑动,但是触摸拦截这个就比较复杂,因为涉及到是在哪个时刻拦截,如何处理,多点触摸时如何判断.
因为涉及的图形比较多,触摸拦截的逻辑就比较复杂,总结流程大概如下.

有3个可以重写的函数,但是不是所有情况都可以用.
方法 | 调用者 | 作用 | 返回值意义 |
---|---|---|---|
dispatchTouchEvent() | Activity ViewGroup View | 事件分发入口,决定事件如何传递 | true:消费事件,停止传递 |
onInterceptTouchEvent() | ViewGroup | 询问是否拦截子View的事件 | true:拦截事件,转为自身onTouchEvent处理 |
onTouchEvent() | Activity ViewGroup View | 事件处理的最终环节,处理具体的触摸逻辑 | true:消费事件,停止传递 |
Activity应该不用说了.
ViewGroup指的是LinearLayout,RelativeLayout,RecyclerView,ListView,ViewPager2,ScrollView,MotionLayout等等.
View指的是TextView,Button,CheckBox,RadioButton,ProgressBar,MaterialButton等等.
在onTouchEvent读取数据,如果有多点触摸,就需要循环获取.
@Override
public boolean onTouchEvent(MotionEvent event) {
int pointerIndex = event.getActionIndex(); // 触发事件的指针索引
int pointerId = event.getPointerId(pointerIndex); // 获取指针唯一 ID
int actionMasked = event.getActionMasked(); // 支持多点触控的 Action
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: { // 非第一个手指按下
PointF point = new PointF(event.getX(pointerIndex), event.getY(pointerIndex));
activePointers.put(pointerId, point);
break;
}
case MotionEvent.ACTION_MOVE: { // 所有手指移动都会触发
for (int i = 0; i < event.getPointerCount(); i++) {
pointerId = event.getPointerId(i);
PointF point = activePointers.get(pointerId);
if (point != null) {
point.x = event.getX(i);
point.y = event.getY(i);
}
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: { // 非最后一个手指抬起
activePointers.remove(pointerId);
break;
}
}
invalidate(); // 触发重绘
return true; // 消费事件
}
如果拦截事件不处理,那么这个触摸就会被忽略,就像下面这样.
private float initialX, initialY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_MOVE) {
// 根据条件决定是否拦截事件(例如水平滑动距离 > 垂直滑动距离)
float dx = Math.abs(ev.getX() - getInitialX());
float dy = Math.abs(ev.getY() - getInitialY());
if (dx > dy) {
return true; // 拦截事件,按理说要给onTouchEvent处理,如果不处理,则等于忽略了触摸.
}
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
initialX = ev.getX();
initialY = ev.getY();
}
return super.dispatchTouchEvent(ev);
}
可以通过各种坐标点,来得到各种手势(左滑,右滑,上滑,下滑,缩小,放大,右旋转,左旋转)的判断.虽然系统自带了GestureDetector,但是研究一下自己实现也是挺好的.下面给出部分代码(不完整).
public class GestureDetectorView extends FrameLayout {
private float startX, startY;
private static final int MIN_DISTANCE = 100; // 最小滑动距离阈值
private static final float MIN_SCALE_DELTA = 0.1f; // 最小缩放比例变化阈值
public GestureDetectorView(Context context) {
super(context);
}
public GestureDetectorView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 决定是否拦截事件
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
startX = event.getX();
startY = event.getY();
break;
case MotionEvent.ACTION_UP:
float endX = event.getX();
float endY = event.getY();
handleGesture(startX, startY, endX, endY);
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 多指触控开始(缩放/旋转)
if (event.getPointerCount() == 2) {
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
}
break;
case MotionEvent.ACTION_MOVE:
// 处理多指缩放/旋转
if (event.getPointerCount() >= 2) {
handleMultiTouch(event);
}
break;
}
return true;
}
private void handleGesture(float startX, float startY, float endX, float endY) {
float deltaX = endX - startX;
float deltaY = endY - startY;
float absDeltaX = Math.abs(deltaX);
float absDeltaY = Math.abs(deltaY);
if (absDeltaX > MIN_DISTANCE || absDeltaY > MIN_DISTANCE) {
if (absDeltaX > absDeltaY) {
// 水平滑动
if (deltaX > 0) {
Log.d("Gesture", "Right swipe");
} else {
Log.d("Gesture", "Left swipe");
}
} else {
// 垂直滑动
if (deltaY > 0) {
Log.d("Gesture", "Down swipe");
} else {
Log.d("Gesture", "Up swipe");
}
}
}
}
private void handleMultiTouch(MotionEvent event) {
if (event.getPointerCount() < 2) return;
// 计算两点间距离缩放
float x1 = event.getX(0);
float y1 = event.getY(0);
float x2 = event.getX(1);
float y2 = event.getY(1);
float currentDistance = (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
float initialDistance = ...;
if (Math.abs(currentDistance - initialDistance) > MIN_SCALE_DELTA) {
if (currentDistance > initialDistance) {
Log.d("Gesture", "Zoom in");
} else {
Log.d("Gesture", "Zoom out");
}
}
// 计算旋转角度
// ...
}
}