全部笔记All notes

Android事件处理机制完全指南

阅读 6m 56s6m 56s read

系列导航

概述

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事件处理是构建交互式应用的基础。掌握事件处理机制需要:

  1. 理解事件分发流程:掌握dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent的调用顺序
  2. 选择合适的处理方式:根据需求选择监听器或回调方式
  3. 处理手势识别:使用GestureDetector简化手势处理
  4. 解决滑动冲突:使用外部拦截或内部拦截法
  5. 优化性能:避免在事件处理中创建对象,合理使用批处理
  6. 注重用户体验:提供适当的触摸反馈,确保响应及时