Android 事件分发介绍

这篇具有很好参考价值的文章主要介绍了Android 事件分发介绍。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录
  • 一、目的
  • 二、环境
  • 三、相关概念
    • 3.1 事件分发
  • 四、详细设计
    • 4.1应用布局
      • 4.1.1 应用布局结构
      • 4.1.2 LayoutInspector
    • 4.2 关键View&方法
      • 4.2.1 相关View
      • 4.2.2 相关方法
      • 4.2.3 View与方法关系
    • 4.3 事件分发概念图
      • 4.3.1 事件分发类图
      • 4.3.2 事件分发模型图
    • 4.4 Activity组件
      • 4.4.1 Activity->dispatchTouchEvent()
      • 4.4.2 Activity->getWindow()
      • 4.4.3 Activity->onTouchEvent()
    • 4.5 ViewGroup组件
      • 4.5.1 ViewGroup->dispatchTouchEvent()
      • 4.5.2 ViewGroup->dispatchTransformedTouchEvent()
    • 4.6 View组件
      • 4.6.1 View->dispatchTouchEvent()
      • 4.6.2 OnTouchListener->onTouch()
      • 4.6.3 View->onTouchEvent()
    • 4.7 例子-点击事件时序图
  • 五、小结&问题点
  • 六、代码仓库地址
  • 七、参考资料

一、目的

        最开始接触Android时,仅仅是知道Android系统存在的点击事件、触摸事件,但是并不清楚这些事件的由来。
        之后,在面试Oppo和美图时,皆有问到Android的事件分发机制,但是都被问得很懵逼,归根到底都是对于其实现逻辑的不理解。
        随后,想去弥补该模块的不足,浏览很多关于Android事件分发的博文,但仍存在一些疑惑,就想着去阅读下源码,整理下笔记,希望对同学们有帮助。

二、环境

  1. 版本:Android 11
  2. 平台:展锐 SPRD8541E

三、相关概念

3.1 事件分发

        Android 中 View 的布局是一个树形结构,各个 ViewGroup 和 View 是按树形结构嵌套布局的,从而会出现用户触摸的位置坐标可能会落在多个 View 的范围内,这样就不知道哪个 View 来响应这个事件,为了解决这一问题,就出现了事件分发机制。

四、详细设计

4.1应用布局

4.1.1 应用布局结构

        如下为一个Activity打开后,其对应视图的层级结构。

Android 事件分发介绍

4.1.2 LayoutInspector

        Layout Inspector是google提供给我们进行布局分析的一个工具,也是目前google在弃用Hierarchy View后推荐使用的一款布局分析工具。

Android 事件分发介绍

4.2 关键View&方法

4.2.1 相关View

组件 描述
Activity Android事件分发的起始端,其为一个window窗口,内部持有Decorder视图,该视图为当前窗体的根节点,同时,它也是一个ViewGroup容器。
ViewGroup Android中ViewGroup是一个布局容器,可以嵌套多个 ViewGroup 和 View,事件传递和拦截都由 ViewGroup 完成。
View 事件传递的最末端,要么消费事件,要么不消费把事件传递给父容器

4.2.2 相关方法

方法 描述
dispatchTouchEvent 分发事件
onInterceptTouchEvent 拦截事件
onTouchEvent 触摸事件

4.2.3 View与方法关系

组件 dispatchTouchEvent onInterceptTouchEvent onTouchEvent
Activity
ViewGroup
View

4.3 事件分发概念图

4.3.1 事件分发类图

Android 事件分发介绍

4.3.2 事件分发模型图

Android 事件分发介绍

        Android的ACTION_DOWN事件分发如图,从1-9步骤,描述一个down事件的分发过程,如果大家能懂,就不用看下面文字描述了(写完这个篇幅,感觉文字好多,不好理解!)

  1. ACTION_DOWN事件触发。 当我们手指触摸屏幕,tp驱动会响应中断,通过ims输入系统,将down事件的相关信息发送到当前的窗口,即当前的Activity。
  2. Activity事件分发。 会引用dispatchTouchEvent()方法,对down事件分发。Activity本身会持有一个window对象,window对象的实现类PhoneWindow会持有一个DecorView对象,DecorView是一个ViewGroup对象,即我们可以理解为,Activity最终会将事件分发给下一个节点——ViewGroup。
  3. ViewGroup事件拦截。 ViewGroup接收到事件后,会先引用onInterceptTouchEvent(),查看当前的视图容器是否做事件拦截。
  4. ViewGroup消费事件。 如当前的ViewGroup对事件进行拦截,即会调用onTouchEvent(),对事件消费。
  5. ViewGroup事件不拦截。 则ViewGroup会继续遍历自身的子节点,并且当事件的坐标位于子节点上,则继续下发到下一个节点。ViewGroup的子节点有可能是View,也可能是ViewGroup(当然,ViewGroup最后也是继承于View的,突然感觉有点废话)。
  6. ViewGroup事件分发。 目标视图如果是ViewGroup,会引用其super类的dispatchTouchEvent()方法,即事件下发,不管目标视图是View或者ViewGroup最终引用的是View类的分发方法。
  7. View事件消费。 在View的dispatchTouchEvent()方法中会根据当前View是否可以点击、onTouch()是否消费、onTouchEvent()是否消费等条件,来判断当前是否为目标View。
  8. View事件未消费。 View事件未消费,则其父节点,即ViewGroup会调用onTouchEvent()方法,并根据返回值来决定是否消费事件。
  9. ViewGroup事件未消费。 ViewGroup事件未消费,择其父节点,即Actviity会调用onTouchEvent()方法

PS:
(1) ACTION_MOVEACTION_UP事件,流程与ACTION_DOWN的分发过程基本一致,MOVE和UP事件也是通过Activity开始,借助DOWN事件产生的目标View,逐级分发。
(2) ACTION_CANCEL事件,是在down与up、move事件切换过程中,事件被拦截,两次的touchTarget目标view不一致,而产生的事件。用于对之前的目标View做恢复处理,避免down与up/move事件不对称。

4.4 Activity组件

4.4.1 Activity->dispatchTouchEvent()

        底层上报的事件信息,最终会引用到该方法。Activity会持有一个根视图DecordView,事件最终会往该ViewGroup分发,如所有的View都未消费该事件,则最终由Activity的onTouchEvent()
来兜底处理。

@frameworks\base\core\java\android\app\Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (getWindow().superDispatchTouchEvent(ev)) {//Step 1. 查看Window对应的View是否分发该事件
        return true;
    }
    return onTouchEvent(ev);//Step 2. 如果没有组件消费事件,则由Activity兜底处理
}

4.4.2 Activity->getWindow()

        我们每次启动一个Activity的组件,会先打开一个window窗口,而PhoneWindow是Window唯一的实现类。

@frameworks\base\core\java\android\app\Activity.java
public Window getWindow() {
    return mWindow;
}

final void attach(Context context, ActivityThread aThread...) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);//PhoneWindow是Window窗口唯一的实现类
    ...
}

        PhoneWindow对象内部持有DecorView对象,而该View正是该窗口对应的视图容器,也是根节点。(此部分不具体分析)

@frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.     Callback {
    ...
    private DecorView mDecor;//
    ...
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);//往View的根节点分发事件
    }
}

4.4.3 Activity->onTouchEvent()

        Activity的onTouchEvent方法,是在没有任何组件消费事件的情况下,触发的方法。

@frameworks\base\core\java\android\app\Activity.java
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}

4.5 ViewGroup组件

        ViewGroup组件在整个事件分发的模型中,既有分发事件的责任,又要具备处理事件的能力,真的典型的当爹又当妈。
        当Activity调用superDispatchTouchEvent,即最终会使用到DecorView的superDispatchTouchEvent方法,而DecorView是继承于ViewGroup,即最终会引用ViewGroup的dispatchTouchEvent方法。

4.5.1 ViewGroup->dispatchTouchEvent()

此方法为事件分发最核心的代码。其主要处理如下四件事情:
Setp 1. 重置事件。 一次完整触摸的事件:DOWN -> MOVE -> UP,即我们可以理解为DOWN是所有触摸事件的起始事件。当输入事件是ACTION_DOWN时,重置触摸事件状态信息,避免产生干扰。
Step 2. 拦截事件。 拦截事件是ViewGroup特有的方法,用于拦截事件,并将该事件分发给自己消费,防止事件继续下发。
Step 3.查找目标View。 查找目标View主要针对于Down事件。当ViewGroup未拦截事件,且输入事件是ACTION_DOWN时,会遍历该ViewGroup的所有子节点,并根据触摸位置的坐标,来决定当前子节点是否是下一级目标View。当找到目标View节点后,会分发Down事件,并记录该节点信息。
Step 4.下发事件。 如果目标View未找到的话,则会将事件交由自己的onTouchEvent()处理;如果目标View已经找到,则Down事件就此结束(此处暂不考虑多指场景);Move和Up事件将继续下发(默认情况下Move、Up和Down事件是成对出现的,如果目标View已经存在,则Down事件已经下发,即意味着Move和Up事件也需要下发给对应的目标View)。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (actionMasked == MotionEvent.ACTION_DOWN) {//Step 1.重置事件信息,避免影响下一次事件
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);//Step 2.拦截事件
            ev.setAction(action); // restore action in case it was changed
        }
    } 
    ...
    if (!canceled && !intercepted) {//Step 3.查找目标View
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            ...
            if (newTouchTarget == null && childrenCount != 0) {
                ...
                for (int i = childrenCount - 1; i >= 0; i--) {//遍历所有的子节点
                    ...
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {// 子节点不可以接收事件,或者触摸位置不在子节点的范围上
                        continue;
                    }
                    ...
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//找到目标View
                        ...
                        break;
                    }
                }
                ...
            }
            ...
        }
    }
    //Step 4.根据找到的目标View情况,继续下发事件
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);//没有找到目标View或者事件被拦截,事件下发给自己
    } else {
        ...
        while (target != null) {//多组数据,一般是指多指场景
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//此场景一般是down事件
                handled = true
            } else {
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {//此场景一般是move、up事件
                    handled = true;
                }
                ...
            }
            predecessor = target;
            target = next;
        }
        ...
    }
    ...
    return handled;
}

4.5.2 ViewGroup->dispatchTransformedTouchEvent()

事件分发关键方法,主要用于向目标View分发事件,具体逻辑如下:
Step 1.Cancel事件分发。 之前我们提过Down和Up事件是成对存在的,如果Down事件已经下发的情况下,Up事件却因为事件拦截等原因,未能下发给目标View,目标View未收到Up事件,此时就可能产生一些按压状态的异常问题,故,在当前场景下,将会分发一个ACTION_CANCEL事件给目标View。
Step 2.事件处理。 如果事件未找到目标View,则child会为null,此时的事件将由自身处理。
Step 3.事件分发。 如果事件还存在目标View,则此时的事件会再分发。

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        ...
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {//Step 1.下发取消事件
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
        if (child == null) {//Step 2.如果事件未找到目标View,则触摸事件会发给自己
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            handled = child.dispatchTouchEvent(transformedEvent);//Step 3.找到目标View,事件下发给子节点
        }
        ...
        return handled;
    }

4.6 View组件

        View组件在事件处理模型中,主要是处理事件。我们知道ViewGroup,也是继承于View,所以ViewGroup也是同样具备View的处理事件能力。

4.6.1 View->dispatchTouchEvent()

Step 1.触发onTouch()方法。 如果当前的View是可点击的,且配置了onTouch事件监听,则触发该View的onTouch()方法。
Step 2.触发onTouchEvent()方法。 如果该事件在上一步的onTouch()函数中未被消费,则触发onTouchEvent()方法。

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean result = false;
    ...
    if (onFilterTouchEventForSecurity(event)) {
        ...
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {//Step 1.触发onTouch事件
            result = true;
        }

        if (!result && onTouchEvent(event)) {//Step 2.如onTouch未消费,触发onTouchEvent事件
            result = true;
        }
    }
    ...
    return result;
}

4.6.2 OnTouchListener->onTouch()

        View可以设置事件监听,用于监听onTouch事件的回调,当然,像我们常见的onClick()、onLongClick()等事件也可监听,其相关源码如下:

@frameworks\base\core\java\android\view\View.java
public void setOnTouchListener(OnTouchListener l) {//设置onTouch监听
    getListenerInfo().mOnTouchListener = l;
}

ListenerInfo getListenerInfo() {
    if (mListenerInfo != null) {
        return mListenerInfo;
    }
    mListenerInfo = new ListenerInfo();
    return mListenerInfo;
}

public interface OnTouchListener {//Touch接口,用于回调onTouch事件
    boolean onTouch(View v, MotionEvent event);
}

4.6.3 View->onTouchEvent()

        事件如未被onTouch消费掉,则会引用到onTouchEvent()方法,该方法会涉及ACTION_UP、ACTION_DOWN、ACTION_CANCEL、ACTION_MOVE事件的处理,View的onClick()、onLongClick()也是由该方法触发。此外,如果当前的View是可点击的话,则直接消费该事件。

public boolean onTouchEvent(MotionEvent event) {
    ...
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
        || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
        || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;//当前View是否可点击
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP://抬起
                ...
                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    if (!focusTaken) {
                        removeLongPressCallback();//若有长按事件未处理,则移除长按事件
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
                        if (!post(mPerformClick)) {//通过Hanlder将点击事件发送到主线程执行
                            performClickInternal();//如果不成功,则直接引用点击事件
                        }
                    }
                }
                if (mUnsetPressedState == null) {
                    mUnsetPressedState = new UnsetPressedState();//更新按钮的按压事件
                }
                ...
                break;
            case MotionEvent.ACTION_DOWN://按下
                ...
                if (isInScrollingContainer) {//在可滚动的容器内,为了容错,延迟点击
                    ...
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    setPressed(true, x, y);//设置按下的状态
                    checkForLongClick(
                            ViewConfiguration.getLongPressTimeout(),
                            x,
                            y,
                            TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);//开启一个长按延时事件
                }
                break;

            case MotionEvent.ACTION_CANCEL://取消
                ...
                break;
            case MotionEvent.ACTION_MOVE://移动
                ...
                break;
        }
        return true;//如果是可点击的View,即消费事件
    }
    ...
    return false;
}

4.7 例子-点击事件时序图

        如下是Android的点击事件时序图,如果能够理解单击事件的由来,对于整个事件分发的知识要点已大体掌握。

Android 事件分发介绍

五、小结&问题点

  1. 事件分发流程?包括ACTION_DWON、ACTION_UP、ACTION_MOVE事件的处理过程;
  2. ACTION_CANCEL事件的使用场景?父控件对move事件拦截场景?
  3. 单击、长按、触摸事件的产生过程?
  4. 点击一个View未抬起,同时move该事件直至离开当前View的范围,处理过程如何?
  5. 如果所有View都未消费事件,流程如何?
  6. ViewPage+ListView,左右滑动和上下滑动冲突的解决问题?即事件拦截过程?
  7. 普通的View是根据什么来决定是否消费事件,例如Button?
    =>答:如无重写onTouchEvent事件,根据当前的View是否可点击,来决定是否消费事件。

        我最开始没有看源码,直接去看博客上的内容,弯弯绕绕,似懂非懂。在面试的过程中,面试官举个场景分析流程,我都懵逼,分析不出来,现场很尴尬。之后看源码,整体流程代码量很少,感叹于Android事件分发流程的设计,很少的代码量,却承载了很重要的功能,而没有见过该模块发生过异常。
        多读书,多看报,少吃零食,多睡觉!

六、代码仓库地址

Demo地址:  https://gitee.com/linzhiqin/custom-demo

七、参考资料

https://zhuanlan.zhihu.com/p/623664769?utm_id=0
事件分发视频(总结很好,但是得先理解基本概念,才方便学习)
https://www.bilibili.com/video/BV1sy4y1W7az?p=1&vd_source=f222e3bf3083cad8d9f660629bc47c16文章来源地址https://www.toymoban.com/news/detail-779624.html

到了这里,关于Android 事件分发介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android事件分发机制五:面试官你坐啊

    学过事件分发吗,聊聊什么是事件分发 事件分发是将屏幕触控信息分发给控件树的一个套机制。 当我们触摸屏幕时,会产生一些列的MotionEvent事件对象,经过控件树的管理者ViewRootImpl,调用view的dispatchPointerEvnet方法进行分发。 那主要的分发流程是什么: 在程序的主界面情况

    2024年04月15日
    浏览(43)
  • Android进阶 View事件体系(二):从源码解析View的事件分发

    本篇文章为总结View事件体系的第二篇文章,前一篇文章的在这里:Android进阶 View事件体系(一):概要介绍和实现View的滑动 本篇文章将专注于介绍View的点击事件的分发,介绍的内容主要有: 点击事件的传递原则 解析Activity的构成 源码解析View的事件分发 源码解析View对点击

    2024年02月06日
    浏览(46)
  • 高级UI之Android事件分发机制原理及源码分析

    在 Android 中, 事件分发机制是一块很重要的知识点, 掌握这个机制能帮你在平时的开发中解决掉很多的 View 事件冲突问题,这个问题也是面试中问的比较多的一个问题了, 本篇就来总结下这个知识点。 Android 中页面上的 View 是以树型结构显示的,View 会重叠在一起,当我们

    2024年02月08日
    浏览(43)
  • [Android 13]Input系列--触摸事件在应用进程的分发和处理

    hongxi.zhu 2023-7-21 Android 13 前面我们已经梳理了input事件在native层的传递,这一篇我们接着探索input事件在应用中的传递与处理,我们将按键事件和触摸事件分开梳理,这一篇就只涉及触摸事件。 一、事件的接收 从前面的篇幅我们知道,framework native层 InputDispatcher 向应用通过s

    2024年02月15日
    浏览(38)
  • Flutter中为控件添加交互,带你一起探究Android事件分发机制

    ), ); } } 代码运行效果如图: 2.父widget管理widget的状态 对于父widget来说,管理状态并告诉其子widget何时更新通常是最有意义的。 例如,IconButton允许您将图标视为可点按的按钮。 IconButton是一个无状态的小部件,因为我们认为父widget需要知道该按钮是否被点击来采取相应的处理

    2024年04月11日
    浏览(44)
  • Android-高级-UI-进阶之路-(二)-深入理解-Android-8-0-View-触摸事件分发机制,查漏补缺

    我们看到内部又调用了父类 dispatchTouchEvent 方法, 所以最终是交给 ViewGroup 顶级 View 来处理分发了。 顶级 View 对点击事件的分发过程 在上一小节中我们知道了一个事件的传递流程,这里我们就大致在回顾一下。首先点击事件到达顶级 ViewGroup 之后,会调用自身的 dispatchTouchE

    2024年04月14日
    浏览(71)
  • Android 关于View事件分发(onTouch、onTouchEvent、onClick、onLongClick的关系及原理)(一)

    事件 含义 ACTION_DOWN 手指初次碰到屏幕时触发 ACTION_MOVE 手指在屏幕上滑动时触发(ps:会多次触发,看源码时同一块代码应该看多次去理解) ACTION_UP 手指离开屏幕时触发 ACTION_CANCEL 事件被上层拦截时触发   关于ACTION_MOVE事件,手指在屏幕上滑动时会触发多次,对于这个点,看源码

    2024年01月24日
    浏览(40)
  • 【Android】Touch 事件分发逻辑梳理和避坑逻辑(上层设置了setOnTouchListener的事件监听但是没有起作用的原因)

    在项目中发现我明明在最上层的 activity 中的一个 DrawerLayout 对象设置了如下代码: 该代码的作用的在点击的时候显示一下 TopBar 一个自定义的UI组件。 但是发现点击超级白板(你可以理解为一个画板组件)部分上述代码就没有触发。这是怎么回事呢? 首先针对Touch事件的分发机

    2024年02月11日
    浏览(37)
  • Jmeter系列-环境部署、详细介绍、安装目录介绍(1)

    http://jmeter.apache.org/下载最新版本的 JMeter,解压文件到任意目录 1、下载(注意选择操作系统对应的位数32/64) 官网 :http://www.oracle.com 2、安装(一键式) ,所有步骤选择项默认选择项。 3、配置环境变量 JAVA_HOME=JDK完整安装路径 环境变量Path添加:%JAVA_HOME%bin;%JAVA_HOME%jrebin;

    2024年02月09日
    浏览(44)
  • 安卓之事件分发机制

    事件分发的”事件“是指什么? 答:点击事件(Touch事件)。 当用户触摸屏幕(VIew或ViewGroup)时,将产生点击事件,即Touch事件。Touch事件的细节(如:触摸位置、事件等)会被封装成MotionEvent对象。 常见的事件: MotionEvent.ACTION_DOWN:按下。 MotionEvent.ACTION_UP:抬起。 MotionE

    2024年02月15日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包