AndroidView的事件分发机制-源码解析

AndroidView的事件分发机制-源码解析,第1张

概述为了更好的研究View的事件转发,我们自定以一个MyButton继承Button,然后把跟事件传播有关的方法进行复写,然后添加上日志:然后把我们自定义的按钮加到主布局文件中:<?xmlversion="1.0"encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apkes/android"

为了更好的研究VIEw的事件转发,我们自定以一个Mybutton继承button,然后把跟事件传播有关的方法进行复写,然后添加上日志:

然后把我们自定义的按钮加到主布局文件中:

<?xml version="1.0" enCoding="utf-8"?><linearLayout xmlns:androID="http://schemas.androID.com/apk/res/androID"    androID:layout_wIDth="match_parent"    androID:layout_height="match_parent"    androID:orIEntation="vertical">        <myvIEw.MylinerLayout        androID:ID="@+ID/ll"        androID:layout_wIDth="match_parent"        androID:layout_height="match_parent"        androID:gravity="center">        <myvIEw.Mybutton            androID:ID="@+ID/btn"            androID:layout_wIDth="100dp"            androID:layout_height="100dp"            androID:text="button" />    </myvIEw.MylinerLayout></linearLayout>

 

在Activity中设置button的touch事件:

public class MainActivity extends Activity{    protected static final String TAG = "Mybutton";    private button mbutton ;    @OverrIDe    protected voID onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentVIEw(R.layout.activity_main);                mbutton = (button) findVIEwByID(R.ID.ID_btn);        mbutton.setontouchListener(new OntouchListener()        {            @OverrIDe            public boolean ontouch(VIEw v, MotionEvent event)            {                int action = event.getAction();                 switch (action)                {                case MotionEvent.ACTION_DOWN:                    Log.e(TAG, "ontouch ACTION_DOWN");                    break;                case MotionEvent.ACTION_MOVE:                    Log.e(TAG, "ontouch ACTION_MOVE");                    break;                case MotionEvent.ACTION_UP:                    Log.e(TAG, "ontouch ACTION_UP");                    break;                default:                    break;                }                                return false;            }        });    }    }

 点击按钮之后查看打印结果:

E/Mybutton(879): dispatchtouchEvent ACTION_DOWN
 E/Mybutton(879): ontouch ACTION_DOWN
 E/Mybutton(879): ontouchEvent ACTION_DOWN
 E/Mybutton(879): dispatchtouchEvent ACTION_MOVE
 E/Mybutton(879): ontouch ACTION_MOVE
 E/Mybutton(879): ontouchEvent ACTION_MOVE
 E/Mybutton(879): dispatchtouchEvent ACTION_UP
 E/Mybutton(879): ontouch ACTION_UP
 E/Mybutton(879): ontouchEvent ACTION_UP


可以看到,不管是DOWN,MOVE,UP都会按照下面的顺序执行

1、dispatchtouchEvent

2、 setontouchListener的ontouch

3、ontouchEvent

 

先来看触摸事件的入口函数:

public boolean dispatchtouchEvent(MotionEvent event) {        if (!onFiltertouchEventForSecurity(event)) {            return false;        }         if (mOntouchListener != null && (mVIEwFlags & ENABLED_MASK) == ENABLED &&                mOntouchListener.ontouch(this, event)) {            return true;        }        return ontouchEvent(event);    }
可以看出如果mOntouchListener不为空,并且vIEw是ENABLED的,并且mOntouchListener.ontouch(this, event)的返回值==true,那么
dispatchtouchEvent的返回值就是true,ontouchEvent方法就得不到执行。
public voID setontouchListener(OntouchListener l) {        mOntouchListener = l;    }
如果vIEw设置了setontouchListener的监听,那么mOntouchListener!=null就成立,一般vIEw是ENABLED的,所以ontouch函数的返回值就决定了ontouchEvent函数能否得到执行。

来看下ontouchEvent函数的源码

public boolean ontouchEvent(MotionEvent event) {        final int vIEwFlags = mVIEwFlags;        //如果当前VIEw是Disabled状态且是可点击则会消费掉事件(return true);可以忽略,不是我们的重点        if ((vIEwFlags & ENABLED_MASK) == Disabled) {                   return (((vIEwFlags & CliCKABLE) == CliCKABLE ||                    (vIEwFlags & LONG_CliCKABLE) == LONG_CliCKABLE));        }         if (mtouchDelegate != null) {            if (mtouchDelegate.ontouchEvent(event)) {                return true;            }        }         //        if (((vIEwFlags & CliCKABLE) == CliCKABLE ||                (vIEwFlags & LONG_CliCKABLE) == LONG_CliCKABLE)) {            switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PREpressed) != 0;                    if ((mPrivateFlags & pressed) != 0 || prepressed) {                        // take focus if we don't have it already and we should in                        // touch mode.                        boolean focusTaken = false;                        if (isFocusable() && isFocusableIntouchMode() && !isFocused()) {                            focusTaken = requestFocus();                        }                         if (!mHasPerformedLongPress) {                            // This is a tap, so remove the longpress check                            removeLongPressCallback();                             // Only perform take click actions if we were in the pressed state                            if (!focusTaken) {                                // Use a Runnable and post this rather than calling                                // performClick directly. This lets other visual state                                // of the vIEw update before click actions start.                                if (mPerformClick == null) {                                    mPerformClick = new PerformClick();                                }                                if (!post(mPerformClick)) {                                    performClick();                                }                            }                        }                         if (mUnsetpressedState == null) {                            mUnsetpressedState = new UnsetpressedState();                        }                         if (prepressed) {                            mPrivateFlags |= pressed;                            refreshDrawableState();                            postDelayed(mUnsetpressedState,                                    VIEwConfiguration.getpressedStateDuration());                        } else if (!post(mUnsetpressedState)) {                            // If the post Failed, unpress right Now                            mUnsetpressedState.run();                        }                        removeTapCallback();                    }                    break;                 case MotionEvent.ACTION_DOWN:                    if (mPendingCheckForTap == null) {                        mPendingCheckForTap = new CheckForTap();                    }                    mPrivateFlags |= PREpressed;                    mHasPerformedLongPress = false;                    postDelayed(mPendingCheckForTap, VIEwConfiguration.getTapTimeout());                    break;                 case MotionEvent.ACTION_CANCEL:                    mPrivateFlags &= ~pressed;                    refreshDrawableState();                    removeTapCallback();                    break;                 case MotionEvent.ACTION_MOVE:                    final int x = (int) event.getX();                    final int y = (int) event.getY();                     // Be lenIEnt about moving outsIDe of buttons                    int slop = mtouchSlop;                    if ((x < 0 - slop) || (x >= getWIDth() + slop) ||                            (y < 0 - slop) || (y >= getHeight() + slop)) {                        // OutsIDe button                        removeTapCallback();                        if ((mPrivateFlags & pressed) != 0) {                            // Remove any future long press/tap checks                            removeLongPressCallback();                             // Need to switch from pressed to not pressed                            mPrivateFlags &= ~pressed;                            refreshDrawableState();                        }                    }                    break;            }            return true;        }         return false;    }
首先分析down事件:
可以看到down的时候会将mPrivateFlags设置一个PREpressed的表示,设置mHasPerformedLongPress=false;表示长按事件还未触发
这是一个延迟115ms的异步任务,也就是115ms之后就会执行CheckForTap类中的run()方法。
postDelayed(mPendingCheckForTap, VIEwConfiguration.getTapTimeout());
private final class CheckForTap implements Runnable {        public voID run() {            mPrivateFlags &= ~PREpressed;            mPrivateFlags |= pressed;            refreshDrawableState();            if ((mVIEwFlags & LONG_CliCKABLE) == LONG_CliCKABLE) {                postCheckForLongClick(VIEwConfiguration.getTapTimeout());            }        }    }
将mPrivateFlags设置为pressed里面还包含了一个postCheckForLongClick()的方法,来看下postCheckForLongClick()方法:
 private voID postCheckForLongClick(int delayOffset) {        mHasPerformedLongPress = false;         if (mPendingCheckForLongPress == null) {            mPendingCheckForLongPress = new CheckForLongPress();        }        mPendingCheckForLongPress.rememberWindowAttachCount();
//执行一个延迟500-115ms异步任务 postDelayed(mPendingCheckForLongPress, VIEwConfiguration.getLongPresstimeout() - delayOffset); }

class CheckForLongPress implements Runnable {         private int mOriginalWindowAttachCount;         public voID run() {            if (ispressed() && (mParent != null)                    && mOriginalWindowAttachCount == mWindowAttachCount) {                if (performlongClick()) {                    mHasPerformedLongPress = true;                }            }        }
如果performlongClick()返回true那么mHasPerformedLongPress=true。
public boolean performlongClick() {      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CliCKED);        boolean handled = false;      if (mOnLongClickListener != null) {          // 回调用户实现的长按 *** 作监听函数(OnLongClickListener)           handled = mOnLongClickListener.onLongClick(VIEw.this);      }      if (!handled) {          // 如果OnLongClickListener的onLongClick返回false           // 则需要继续处理该长按事件,这里是显示上下文菜单           handled = showContextMenu();      }      if (handled) {          // 长按 *** 作事件被处理了,此时应该给用户触觉上的反馈           performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);      }      return handled;  }
可以看到用户的长按事件是在这个方法里面实现的回调,并且返回一个boolean值。

总结下就是:

当用户按下,首先会设置标识为PREpressed,如果115后,没有抬起,会将VIEw的标识设置为pressed且去掉PREpressed标识,然后发出一个检测长按的延迟任务,延时为:VIEwConfiguration.getLongPresstimeout() - delayOffset(500ms -115ms),这个115ms刚好是检测到PREpressed时间;也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件。

 

在来看下up事件:

如果包含pressed或者PREpressed则进入执行体,也就是无论是115ms内或者之后抬起都会进入执行体,如果mHasPerformedLongPress==false,进入IF,否则直接跳出。

如果mPerformClick为null,初始化一个实例,然后立即通过handler添加到消息队列尾部,如果添加失败则直接执行 performClick();添加成功,在mPerformClick的run方法中就是执行performClick()方法。

 public boolean performClick() {        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CliCKED);         if (mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CliCK);            mOnClickListener.onClick(this);            return true;        }         return false;    }
可以看到这个方法里面会执行用户的onClick()的回调,并且返回true,表示消费了这个事件。

总结下up事件:如果用户设置了长按回调并且返回了true,那么performClick()方法就不会执行,那么我们的onClick()方法也就不会执行。

 

总结下整个的ontouchEvent方法,只要vIEw具有onClick或者onLongClick那么都会返回true,否则返回false。这也就是button可以不用设置点击或者长按的回调,而TextVIEw需要设置回调之后

才会进入里面的if()体的原因:

    public voID setonClickListener(@Nullable OnClickListener l) {        if (!isClickable()) {            setClickable(true);        }        getListenerInfo().mOnClickListener = l;    }
调用这个方法之后,如果不可点击就会设置成可以点击。

整个VIEw的事件转发流程是:

VIEw.dispatchEvent->VIEw.setontouchListener->VIEw.ontouchEvent

在dispatchtouchEvent中会进行OntouchListener的判断,如果OntouchListener不为null且返回true,则表示事件被消费,ontouchEvent不会被执行;否则执行ontouchEvent。

 

总结

以上是内存溢出为你收集整理的Android View的事件分发机制-源码解析全部内容,希望文章能够帮你解决Android View的事件分发机制-源码解析所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1112438.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-29
下一篇2022-05-29

发表评论

登录后才能评论

评论列表(0条)

    保存