系列导航
概述
Android事件处理是用户与应用交互的核心机制。理解事件处理机制对于开发流畅、响应迅速的用户界面至关重要。本指南深入介绍Android平台的事件处理系统,包括触摸事件、键盘事件、手势识别等内容。
学习目标
- 理解Android事件处理的两种方式
- 掌握触摸事件的分发机制
- 学习手势识别和多点触控
- 处理滑动冲突问题
- 实现自定义手势功能
- 优化事件处理性能
事件处理基础
事件类型
Android中的事件主要分为:
- 触摸事件:用户触摸屏幕产生的事件
- 键盘事件:物理按键或软键盘产生的事件
- 轨迹球事件:轨迹球设备产生的事件(较少使用)
事件处理方式
1. 基于监听器的事件处理
// Java实现
public class ListenerEventActivity extends AppCompatActivity {
private Button button;
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listener_event);
button = findViewById(R.id.button);
editText = findViewById(R.id.editText);
// 点击事件监听器
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(ListenerEventActivity.this,
"按钮被点击", Toast.LENGTH_SHORT).show();
}
});
// 长按事件监听器
button.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(ListenerEventActivity.this,
"按钮被长按", Toast.LENGTH_SHORT).show();
return true; // 返回true表示事件已处理
}
});
// 触摸事件监听器
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("Touch", "手指按下");
break;
case MotionEvent.ACTION_MOVE:
Log.d("Touch", "手指移动");
break;
case MotionEvent.ACTION_UP:
Log.d("Touch", "手指抬起");
break;
}
return false; // 返回false允许其他事件继续处理
}
});
// 文本变化监听器
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 文本改变前
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 文本改变时
Log.d("TextChange", "当前文本: " + s);
}
@Override
public void afterTextChanged(Editable s) {
// 文本改变后
}
});
}
}
// Kotlin实现
class ListenerEventActivity : AppCompatActivity() {
private lateinit var button: Button
private lateinit var editText: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_listener_event)
button = findViewById(R.id.button)
editText = findViewById(R.id.editText)
// 点击事件监听器(Lambda表达式)
button.setOnClickListener {
Toast.makeText(this, "按钮被点击", Toast.LENGTH_SHORT).show()
}
// 长按事件监听器
button.setOnLongClickListener {
Toast.makeText(this, "按钮被长按", Toast.LENGTH_SHORT).show()
true // 返回true表示事件已处理
}
// 触摸事件监听器
button.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> Log.d("Touch", "手指按下")
MotionEvent.ACTION_MOVE -> Log.d("Touch", "手指移动")
MotionEvent.ACTION_UP -> Log.d("Touch", "手指抬起")
}
false // 返回false允许其他事件继续处理
}
// 文本变化监听器
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// 文本改变前
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
// 文本改变时
Log.d("TextChange", "当前文本: $s")
}
override fun afterTextChanged(s: Editable?) {
// 文本改变后
}
})
}
}
2. 基于回调的事件处理
// Java实现 - 自定义View处理事件
public class CustomEventView extends View {
private static final String TAG = "CustomEventView";
public CustomEventView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, String.format("触摸按下: (%.1f, %.1f)", x, y));
return true; // 消费事件
case MotionEvent.ACTION_MOVE:
Log.d(TAG, String.format("触摸移动: (%.1f, %.1f)", x, y));
return true;
case MotionEvent.ACTION_UP:
Log.d(TAG, String.format("触摸抬起: (%.1f, %.1f)", x, y));
return true;
case MotionEvent.ACTION_CANCEL:
Log.d(TAG, "触摸取消");
return true;
}
return super.onTouchEvent(event);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
Log.d(TAG, "确认键按下");
return true;
case KeyEvent.KEYCODE_BACK:
Log.d(TAG, "返回键按下");
return true;
}
return super.onKeyDown(keyCode, event);
}
}
触摸事件处理
MotionEvent详解
public class TouchEventActivity extends AppCompatActivity {
private TextView infoText;
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取触摸事件的详细信息
int action = event.getAction();
float x = event.getX();
float y = event.getY();
float pressure = event.getPressure(); // 压力
float size = event.getSize(); // 触摸面积
long eventTime = event.getEventTime(); // 事件时间
long downTime = event.getDownTime(); // 按下时间
StringBuilder info = new StringBuilder();
info.append("Action: ").append(getActionName(action)).append("\n");
info.append("位置: (").append(x).append(", ").append(y).append(")\n");
info.append("压力: ").append(pressure).append("\n");
info.append("面积: ").append(size).append("\n");
info.append("时间: ").append(eventTime - downTime).append("ms\n");
// 处理多点触控
int pointerCount = event.getPointerCount();
if (pointerCount > 1) {
info.append("\n多点触控 - 触摸点数: ").append(pointerCount).append("\n");
for (int i = 0; i < pointerCount; i++) {
int pointerId = event.getPointerId(i);
float pointerX = event.getX(i);
float pointerY = event.getY(i);
info.append("触摸点").append(pointerId)
.append(": (").append(pointerX)
.append(", ").append(pointerY).append(")\n");
}
}
infoText.setText(info.toString());
return true;
}
private String getActionName(int action) {
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return "ACTION_DOWN";
case MotionEvent.ACTION_UP: return "ACTION_UP";
case MotionEvent.ACTION_MOVE: return "ACTION_MOVE";
case MotionEvent.ACTION_CANCEL: return "ACTION_CANCEL";
case MotionEvent.ACTION_POINTER_DOWN: return "ACTION_POINTER_DOWN";
case MotionEvent.ACTION_POINTER_UP: return "ACTION_POINTER_UP";
default: return "Unknown";
}
}
}
速度追踪器(VelocityTracker)
public class VelocityTrackerView extends View {
private VelocityTracker velocityTracker;
private Paint paint;
private String velocityInfo = "";
public VelocityTrackerView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
paint.setTextSize(40);
paint.setColor(Color.BLACK);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
// 创建速度追踪器
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
velocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
velocityTracker.addMovement(event);
// 计算速度
velocityTracker.computeCurrentVelocity(1000); // 单位:像素/秒
float xVelocity = velocityTracker.getXVelocity();
float yVelocity = velocityTracker.getYVelocity();
velocityInfo = String.format("速度: X=%.1f, Y=%.1f px/s",
xVelocity, yVelocity);
invalidate(); // 重绘
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 回收速度追踪器
if (velocityTracker != null) {
velocityTracker.recycle();
velocityTracker = null;
}
velocityInfo = "";
invalidate();
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawText(velocityInfo, 50, 100, paint);
}
}
事件分发机制
事件分发流程
事件分发涉及三个重要方法:
dispatchTouchEvent():分发事件onInterceptTouchEvent():拦截事件(仅ViewGroup有)onTouchEvent():处理事件
// 自定义ViewGroup演示事件分发
public class CustomViewGroup extends LinearLayout {
private static final String TAG = "CustomViewGroup";
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "dispatchTouchEvent: " + getActionName(ev.getAction()));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "onInterceptTouchEvent: " + getActionName(ev.getAction()));
// 返回true表示拦截事件,不传递给子View
// 返回false表示不拦截,继续传递
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: " + getActionName(event.getAction()));
return super.onTouchEvent(event);
}
private String getActionName(int action) {
switch (action) {
case MotionEvent.ACTION_DOWN: return "DOWN";
case MotionEvent.ACTION_MOVE: return "MOVE";
case MotionEvent.ACTION_UP: return "UP";
default: return String.valueOf(action);
}
}
}
// 自定义View
public class CustomView extends View {
private static final String TAG = "CustomView";
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG, "dispatchTouchEvent: " + getActionName(event.getAction()));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "onTouchEvent: " + getActionName(event.getAction()));
// 返回true表示消费事件
return true;
}
}
事件分发规则
public class EventDispatchActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 创建视图层级
CustomViewGroup viewGroup = new CustomViewGroup(this, null);
CustomView childView = new CustomView(this, null);
viewGroup.addView(childView, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
));
setContentView(viewGroup);
// 事件分发流程:
// 1. Activity.dispatchTouchEvent()
// 2. ViewGroup.dispatchTouchEvent()
// 3. ViewGroup.onInterceptTouchEvent()
// 4. View.dispatchTouchEvent()
// 5. View.onTouchEvent()
// 6. 如果子View不消费,事件回传给ViewGroup.onTouchEvent()
// 7. 最后回传给Activity.onTouchEvent()
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("Activity", "dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("Activity", "onTouchEvent");
return super.onTouchEvent(event);
}
}
键盘事件处理
处理物理按键
public class KeyEventActivity extends AppCompatActivity {
private long lastBackPressTime = 0;
private static final long DOUBLE_BACK_PRESS_INTERVAL = 2000;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
// 音量增加键
adjustVolume(AudioManager.ADJUST_RAISE);
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// 音量减少键
adjustVolume(AudioManager.ADJUST_LOWER);
return true;
case KeyEvent.KEYCODE_BACK:
// 双击返回键退出
if (handleDoubleBackPress()) {
return true;
}
break;
case KeyEvent.KEYCODE_MENU:
// 菜单键
showOptionsMenu();
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CAMERA:
// 相机键释放
takePicture();
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_SEARCH:
// 长按搜索键
startVoiceSearch();
return true;
}
return super.onKeyLongPress(keyCode, event);
}
private boolean handleDoubleBackPress() {
long currentTime = System.currentTimeMillis();
if (currentTime - lastBackPressTime < DOUBLE_BACK_PRESS_INTERVAL) {
finish();
return true;
} else {
lastBackPressTime = currentTime;
Toast.makeText(this, "再按一次退出应用", Toast.LENGTH_SHORT).show();
return true;
}
}
private void adjustVolume(int direction) {
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, direction,
AudioManager.FLAG_SHOW_UI);
}
}
软键盘处理
public class SoftKeyboardActivity extends AppCompatActivity {
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_soft_keyboard);
editText = findViewById(R.id.editText);
// 监听软键盘动作
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
switch (actionId) {
case EditorInfo.IME_ACTION_DONE:
// 完成动作
handleDoneAction();
return true;
case EditorInfo.IME_ACTION_SEARCH:
// 搜索动作
performSearch(v.getText().toString());
return true;
case EditorInfo.IME_ACTION_SEND:
// 发送动作
sendMessage(v.getText().toString());
return true;
}
return false;
}
});
// 监听软键盘状态
final View rootView = findViewById(android.R.id.content);
rootView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
if (heightDiff > 200) { // 软键盘显示
onSoftKeyboardShown();
} else { // 软键盘隐藏
onSoftKeyboardHidden();
}
}
});
}
// 显示软键盘
private void showSoftKeyboard() {
editText.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
}
// 隐藏软键盘
private void hideSoftKeyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
}
}
手势识别
使用GestureDetector
public class GestureActivity extends AppCompatActivity
implements GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener {
private GestureDetector gestureDetector;
private TextView gestureText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gesture);
gestureText = findViewById(R.id.gestureText);
// 创建手势检测器
gestureDetector = new GestureDetector(this, this);
gestureDetector.setOnDoubleTapListener(this);
// 设置触摸监听
findViewById(R.id.gestureView).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
});
}
// OnGestureListener方法
@Override
public boolean onDown(MotionEvent e) {
showGesture("onDown");
return true;
}
@Override
public void onShowPress(MotionEvent e) {
showGesture("onShowPress");
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
showGesture("onSingleTapUp");
return true;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
showGesture(String.format("onScroll: dx=%.1f, dy=%.1f", distanceX, distanceY));
return true;
}
@Override
public void onLongPress(MotionEvent e) {
showGesture("onLongPress");
// 触发震动反馈
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (vibrator != null) {
vibrator.vibrate(50);
}
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
float velocityX, float velocityY) {
float deltaX = e2.getX() - e1.getX();
float deltaY = e2.getY() - e1.getY();
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX > 0) {
showGesture("向右滑动");
} else {
showGesture("向左滑动");
}
} else {
if (deltaY > 0) {
showGesture("向下滑动");
} else {
showGesture("向上滑动");
}
}
return true;
}
// OnDoubleTapListener方法
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
showGesture("onSingleTapConfirmed");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
showGesture("onDoubleTap");
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return true;
}
private void showGesture(String gesture) {
gestureText.setText(gesture);
Log.d("Gesture", gesture);
}
}
ScaleGestureDetector缩放手势
public class ScaleGestureView extends View {
private ScaleGestureDetector scaleDetector;
private float scaleFactor = 1.0f;
private Paint paint;
private Bitmap bitmap;
public ScaleGestureView(Context context, AttributeSet attrs) {
super(context, attrs);
// 初始化缩放检测器
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
paint = new Paint();
paint.setAntiAlias(true);
// 加载图片
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.sample_image);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将事件传递给缩放检测器
scaleDetector.onTouchEvent(event);
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 保存画布状态
canvas.save();
// 缩放画布
canvas.scale(scaleFactor, scaleFactor, getWidth() / 2f, getHeight() / 2f);
// 绘制图片
if (bitmap != null) {
float left = (getWidth() - bitmap.getWidth()) / 2f;
float top = (getHeight() - bitmap.getHeight()) / 2f;
canvas.drawBitmap(bitmap, left, top, paint);
}
// 恢复画布状态
canvas.restore();
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
scaleFactor *= detector.getScaleFactor();
// 限制缩放范围
scaleFactor = Math.max(0.1f, Math.min(scaleFactor, 5.0f));
// 重绘视图
invalidate();
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
Log.d("Scale", "开始缩放");
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
Log.d("Scale", "结束缩放");
}
}
}
多点触控
处理多个触摸点
public class MultiTouchView extends View {
private static final int MAX_TOUCHES = 10;
private Paint[] paints;
private float[] touchX;
private float[] touchY;
private boolean[] isTouching;
public MultiTouchView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaints();
touchX = new float[MAX_TOUCHES];
touchY = new float[MAX_TOUCHES];
isTouching = new boolean[MAX_TOUCHES];
}
private void initPaints() {
paints = new Paint[MAX_TOUCHES];
int[] colors = {Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW,
Color.CYAN, Color.MAGENTA, Color.BLACK, Color.GRAY,
Color.LTGRAY, Color.DKGRAY};
for (int i = 0; i < MAX_TOUCHES; i++) {
paints[i] = new Paint();
paints[i].setColor(colors[i]);
paints[i].setStyle(Paint.Style.FILL);
paints[i].setAntiAlias(true);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getActionMasked();
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
if (pointerId < MAX_TOUCHES) {
touchX[pointerId] = event.getX(pointerIndex);
touchY[pointerId] = event.getY(pointerIndex);
isTouching[pointerId] = true;
}
break;
case MotionEvent.ACTION_MOVE:
for (int i = 0; i < event.getPointerCount(); i++) {
int id = event.getPointerId(i);
if (id < MAX_TOUCHES) {
touchX[id] = event.getX(i);
touchY[id] = event.getY(i);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
if (pointerId < MAX_TOUCHES) {
isTouching[pointerId] = false;
}
break;
case MotionEvent.ACTION_CANCEL:
Arrays.fill(isTouching, false);
break;
}
invalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < MAX_TOUCHES; i++) {
if (isTouching[i]) {
canvas.drawCircle(touchX[i], touchY[i], 100, paints[i]);
// 绘制触摸点ID
Paint textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(50);
textPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(String.valueOf(i), touchX[i], touchY[i] + 15, textPaint);
}
}
}
}
自定义手势
创建和保存手势
public class CreateGestureActivity extends AppCompatActivity {
private GestureOverlayView gestureOverlayView;
private GestureLibrary gestureLibrary;
private EditText gestureNameEdit;
private String gestureName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_create_gesture);
gestureOverlayView = findViewById(R.id.gestureOverlayView);
gestureNameEdit = findViewById(R.id.gestureNameEdit);
// 初始化手势库
File gestureFile = new File(getExternalFilesDir(null), "gestures");
gestureLibrary = GestureLibraries.fromFile(gestureFile);
// 设置手势绘制参数
gestureOverlayView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
gestureOverlayView.setFadeOffset(1000);
gestureOverlayView.setGestureColor(Color.BLUE);
gestureOverlayView.setGestureStrokeWidth(10);
// 监听手势绘制
gestureOverlayView.addOnGesturePerformedListener(
new GestureOverlayView.OnGesturePerformedListener() {
@Override
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
gestureName = gestureNameEdit.getText().toString();
if (TextUtils.isEmpty(gestureName)) {
Toast.makeText(CreateGestureActivity.this,
"请输入手势名称", Toast.LENGTH_SHORT).show();
return;
}
// 保存手势
gestureLibrary.addGesture(gestureName, gesture);
if (gestureLibrary.save()) {
Toast.makeText(CreateGestureActivity.this,
"手势保存成功", Toast.LENGTH_SHORT).show();
gestureNameEdit.setText("");
overlay.clear(false);
} else {
Toast.makeText(CreateGestureActivity.this,
"手势保存失败", Toast.LENGTH_SHORT).show();
}
}
});
}
// 清除手势
public void clearGesture(View view) {
gestureOverlayView.clear(false);
}
// 撤销最后一笔
public void undoGesture(View view) {
gestureOverlayView.cancelClearAnimation();
gestureOverlayView.clear(true);
}
}
识别手势
public class RecognizeGestureActivity extends AppCompatActivity {
private GestureOverlayView gestureOverlayView;
private GestureLibrary gestureLibrary;
private TextView resultText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recognize_gesture);
gestureOverlayView = findViewById(R.id.gestureOverlayView);
resultText = findViewById(R.id.resultText);
// 加载手势库
File gestureFile = new File(getExternalFilesDir(null), "gestures");
gestureLibrary = GestureLibraries.fromFile(gestureFile);
if (!gestureLibrary.load()) {
Toast.makeText(this, "手势库加载失败", Toast.LENGTH_SHORT).show();
finish();
return;
}
// 设置手势识别参数
gestureOverlayView.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
gestureOverlayView.addOnGesturePerformedListener(
new GestureOverlayView.OnGesturePerformedListener() {
@Override
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
// 识别手势
ArrayList<Prediction> predictions = gestureLibrary.recognize(gesture);
if (predictions.size() > 0) {
StringBuilder result = new StringBuilder("识别结果:\n");
// 显示前3个最可能的结果
int count = Math.min(3, predictions.size());
for (int i = 0; i < count; i++) {
Prediction prediction = predictions.get(i);
result.append(String.format("%s (置信度: %.2f)\n",
prediction.name, prediction.score));
}
resultText.setText(result.toString());
// 如果最高置信度大于阈值,执行对应操作
Prediction bestPrediction = predictions.get(0);
if (bestPrediction.score > 2.0) {
handleGestureAction(bestPrediction.name);
}
} else {
resultText.setText("无法识别该手势");
}
// 清除手势
overlay.clear(true);
}
});
}
private void handleGestureAction(String gestureName) {
switch (gestureName) {
case "circle":
Toast.makeText(this, "执行圆形手势动作", Toast.LENGTH_SHORT).show();
break;
case "triangle":
Toast.makeText(this, "执行三角形手势动作", Toast.LENGTH_SHORT).show();
break;
case "check":
Toast.makeText(this, "执行勾选手势动作", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(this, "执行 " + gestureName + " 动作", Toast.LENGTH_SHORT).show();
}
}
}
滑动冲突处理
外部拦截法
public class OuterInterceptViewGroup extends ViewGroup {
private int lastX, lastY;
private int lastInterceptX, lastInterceptY;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// DOWN事件不拦截,否则后续事件都无法传递给子View
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastInterceptX;
int deltaY = y - lastInterceptY;
// 判断滑动方向,决定是否拦截
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动,父容器拦截
intercepted = true;
} else {
// 垂直滑动,不拦截,交给子View处理
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
lastX = x;
lastY = y;
lastInterceptX = x;
lastInterceptY = y;
return intercepted;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 处理滑动逻辑
return true;
}
}
内部拦截法
public class InnerInterceptView extends View {
private int lastX, lastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 请求父容器不要拦截DOWN事件
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - lastX;
int deltaY = y - lastY;
// 判断是否需要父容器拦截
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 水平滑动,让父容器拦截
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = x;
lastY = y;
return super.dispatchTouchEvent(event);
}
}
性能优化
1. 避免在onTouchEvent中创建对象
public class OptimizedTouchView extends View {
// 预先创建对象,避免在触摸事件中频繁创建
private final Rect touchRect = new Rect();
private final Paint paint = new Paint();
private final Path path = new Path();
@Override
public boolean onTouchEvent(MotionEvent event) {
// 复用已有对象
touchRect.set((int)event.getX() - 50, (int)event.getY() - 50,
(int)event.getX() + 50, (int)event.getY() + 50);
// 避免在这里创建新对象
// Rect rect = new Rect(...); // 错误做法
invalidate(touchRect); // 只刷新触摸区域
return true;
}
}
2. 使用ViewConfiguration获取系统配置
public class SystemConfigView extends View {
private final ViewConfiguration viewConfig;
private final int touchSlop;
private final int longPressTimeout;
private final int tapTimeout;
public SystemConfigView(Context context) {
super(context);
// 获取系统配置
viewConfig = ViewConfiguration.get(context);
touchSlop = viewConfig.getScaledTouchSlop();
longPressTimeout = ViewConfiguration.getLongPressTimeout();
tapTimeout = ViewConfiguration.getTapTimeout();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 使用系统配置值进行判断
// 例如:判断是否是滑动
if (Math.abs(deltaX) > touchSlop || Math.abs(deltaY) > touchSlop) {
// 开始滑动
}
return true;
}
}
3. 批量处理事件
public class BatchEventView extends View {
private final List<MotionEvent> eventBatch = new ArrayList<>();
private static final int BATCH_SIZE = 10;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_MOVE) {
// 批量收集MOVE事件
eventBatch.add(MotionEvent.obtain(event));
if (eventBatch.size() >= BATCH_SIZE) {
processBatch();
eventBatch.clear();
}
} else {
// 立即处理其他事件
processEvent(event);
}
return true;
}
private void processBatch() {
// 批量处理事件,减少绘制次数
for (MotionEvent event : eventBatch) {
// 处理事件
event.recycle(); // 回收事件对象
}
invalidate();
}
}
最佳实践
1. 正确处理事件消费
public class EventConsumptionView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// DOWN事件返回true,表示对该事件序列感兴趣
return true;
case MotionEvent.ACTION_MOVE:
// 处理移动
return true;
case MotionEvent.ACTION_UP:
// 处理抬起
return true;
default:
// 其他事件交给父类处理
return super.onTouchEvent(event);
}
}
}
2. 合理使用手势检测器
public class GestureDetectorBestPractice extends View {
private final GestureDetector gestureDetector;
private final ScaleGestureDetector scaleDetector;
public GestureDetectorBestPractice(Context context) {
super(context);
// 创建手势检测器
gestureDetector = new GestureDetector(context, new GestureListener());
scaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 先处理缩放手势
boolean handled = scaleDetector.onTouchEvent(event);
// 如果不是缩放,再处理其他手势
if (!scaleDetector.isInProgress()) {
handled |= gestureDetector.onTouchEvent(event);
}
return handled || super.onTouchEvent(event);
}
}
3. 处理触摸反馈
public class TouchFeedbackView extends View {
private final Drawable rippleDrawable;
public TouchFeedbackView(Context context) {
super(context);
// 创建水波纹效果
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(
android.R.attr.selectableItemBackground, typedValue, true);
rippleDrawable = context.getDrawable(typedValue.resourceId);
setBackground(rippleDrawable);
setClickable(true);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 触摸反馈
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 按下效果
setPressed(true);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// 释放效果
setPressed(false);
break;
}
return super.onTouchEvent(event);
}
}
调试技巧
1. 事件日志记录
public class EventLogger {
private static final String TAG = "EventLogger";
public static void logTouchEvent(String viewName, MotionEvent event) {
String action = getActionString(event.getAction());
Log.d(TAG, String.format("[%s] %s at (%.1f, %.1f)",
viewName, action, event.getX(), event.getY()));
}
public static void logKeyEvent(String viewName, KeyEvent event) {
String action = event.getAction() == KeyEvent.ACTION_DOWN ? "DOWN" : "UP";
Log.d(TAG, String.format("[%s] Key %s: %s",
viewName, action, KeyEvent.keyCodeToString(event.getKeyCode())));
}
private static String getActionString(int action) {
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: return "DOWN";
case MotionEvent.ACTION_UP: return "UP";
case MotionEvent.ACTION_MOVE: return "MOVE";
case MotionEvent.ACTION_CANCEL: return "CANCEL";
case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
default: return "ACTION_" + action;
}
}
}
2. 可视化调试
public class DebugTouchView extends View {
private final List<TouchPoint> touchPoints = new ArrayList<>();
private final Paint debugPaint;
private boolean debugMode = true;
private static class TouchPoint {
float x, y;
long timestamp;
int action;
TouchPoint(float x, float y, long timestamp, int action) {
this.x = x;
this.y = y;
this.timestamp = timestamp;
this.action = action;
}
}
public DebugTouchView(Context context) {
super(context);
debugPaint = new Paint();
debugPaint.setColor(Color.RED);
debugPaint.setStyle(Paint.Style.STROKE);
debugPaint.setStrokeWidth(2);
debugPaint.setTextSize(30);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (debugMode) {
touchPoints.add(new TouchPoint(
event.getX(), event.getY(),
event.getEventTime(), event.getAction()
));
// 保留最近50个点
if (touchPoints.size() > 50) {
touchPoints.remove(0);
}
invalidate();
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (debugMode) {
// 绘制触摸轨迹
for (int i = 0; i < touchPoints.size() - 1; i++) {
TouchPoint p1 = touchPoints.get(i);
TouchPoint p2 = touchPoints.get(i + 1);
canvas.drawLine(p1.x, p1.y, p2.x, p2.y, debugPaint);
}
// 绘制触摸点信息
if (!touchPoints.isEmpty()) {
TouchPoint last = touchPoints.get(touchPoints.size() - 1);
canvas.drawText(String.format("(%.0f, %.0f)", last.x, last.y),
last.x + 10, last.y - 10, debugPaint);
}
}
}
}
常见问题
1. 点击事件不响应
问题原因:
- View没有设置clickable
- 父View拦截了事件
- 触摸区域太小
解决方案:
// 确保View可点击
view.setClickable(true);
// 扩大触摸区域
public static void expandTouchArea(final View view, final int size) {
final View parent = (View) view.getParent();
parent.post(new Runnable() {
@Override
public void run() {
Rect rect = new Rect();
view.getHitRect(rect);
rect.top -= size;
rect.bottom += size;
rect.left -= size;
rect.right += size;
parent.setTouchDelegate(new TouchDelegate(rect, view));
}
});
}
2. 滑动冲突
问题原因:ScrollView嵌套RecyclerView等
解决方案:
// 禁用嵌套滑动
recyclerView.setNestedScrollingEnabled(false);
// 或者自定义处理
public class CustomScrollView extends ScrollView {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 根据条件决定是否拦截
if (shouldIntercept()) {
return super.onInterceptTouchEvent(ev);
}
return false;
}
}
3. 手势识别不准确
问题原因:
- 手势库训练不足
- 阈值设置不当
解决方案:
// 调整识别阈值
gestureOverlayView.setGestureStrokeAngleThreshold(30.0f);
gestureOverlayView.setGestureStrokeLengthThreshold(50.0f);
gestureOverlayView.setGestureStrokeSquarenessThreshold(0.1f);
// 增加训练样本
for (int i = 0; i < 10; i++) {
// 让用户多次绘制同一手势
}
相关文章
- Android View系统详解
- Android Activity生命周期
- Android自定义View开发
- Android动画系统
- Android界面布局
总结
Android事件处理是构建交互式应用的基础。掌握事件处理机制需要:
- 理解事件分发流程:掌握dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent的调用顺序
- 选择合适的处理方式:根据需求选择监听器或回调方式
- 处理手势识别:使用GestureDetector简化手势处理
- 解决滑动冲突:使用外部拦截或内部拦截法
- 优化性能:避免在事件处理中创建对象,合理使用批处理
- 注重用户体验:提供适当的触摸反馈,确保响应及时