[Android速通]事件交互

/ 0评 / 0

一般来说,系统会自己处理好按键,触摸等一系列的操作,但是有些需求,可能需要自行监听一些按键行为,最常见就是很多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");
            }
        }

        // 计算旋转角度
        // ...
    }
}

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注