关于android:Android中View事件的分发第一篇

9次阅读

共计 9059 个字符,预计需要花费 23 分钟才能阅读完成。

本文系转载文章,浏览原文可获取源码,文章开端有原文链接

ps:源码是基于 android api 26 来剖析的

点击事件实际上就是 MotionEvent,对于 MotionEvent 事件的这个过程,实际上就是点击事件的这个事件产生,如果一个 MotionEvent 产生了当前,零碎须要把这个过程事件传递给一个具体的视图,而传递的就是传递过程;散发过程由 ViewGroup 中的 3 个重要办法组成,别离是 dispatchTouchEvent、onIntercepTouchEvent 和 onTouchEvent 办法。

咱们来看看 ViewGroup 中的 dispatchTouchEvent 办法的源码;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ......
    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {intercepted = false;}
    } else {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }
    ......
    // Dispatch to touch targets.
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {......}
    ......
    return handled;
}

发现 ViewGroup 中的 dispatchTouchEvent 办法调用了 ViewGroup 的 dispatchTransformedTouchEvent 办法,咱们点击该办法持续查看;

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,

                                              View child, int desiredPointerIdBits) {
    final boolean handled;

    // Canceling motions is a special case.  We don't need to perform any transformations
    // or filtering.  The important part is the action, not the contents.
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {handled = super.dispatchTouchEvent(event);
        } else {handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    ......
    return handled;

}

因为小孩这个参数传过来的时候是空的,所以调用的是 super.dispatchTouchEvent 这个办法,super 是 View,咱们点击 View 中的 dispatchTouchEvent 办法查看;

public boolean dispatchTouchEvent(MotionEvent event) {

    ......
    if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {result = true;}

        if (!result && onTouchEvent(event)) {result = true;}
    }
    ......
    return result;

}

这里调用了 onTouchEvent 办法,能够看到 ViewGroup 的 dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent 办法的关系就进去了,用以下伪代码示意;

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean isExpense = false;
    if (onInterceptTouchEvent(ev)) {isExpense = onTouchEvent(event);
    } else {isExpense = child.dispatchTouchEvent(ev);
    }
    return isExpense;
}

首先事件会传递给 ViewGroup,它的 dispatchTouchEvent 办法会被先调用,如果这个 ViewGroup 的 onInterceptTouchEvent 办法返回 true 就示意它要拦挡以后事件,这个 ViewGroup 就会调用 onTouchEvent 办法;如果这个 ViewGroup 的 onInterceptTouchEvent 办法返回 false 就示意可能以后事件会持续传递给它的子元素,接下来子元素的触发事件办法会被调用,这样的触发事件被生产。从上述源中事件产生当事件由 ViewGroup 的子元素胜利解决时,mFirstTouchTarget 会也被调用办法并当解决子元素,不拦挡事件触发事件交由子元素时 mFirstTouchTarget != null;所以当 mFirstTouchTarget == null 时会调用 dispatchTransformedTouchEvent,最终会调用 View 的 onTouchEvent,拦挡事件的 ViewGroup 本人的生产事件。

事件的传递程序是这样的,Activity->Window(实现类 PhoneWindow)–>DecorView(以后界面组件容器 FrameLayout)–> 组件文件 View(setContentView 办法中的文件),好,咱们先看看 Activity 中的 dispatchTouchEvent 办法的源码;

public boolean dispatchTouchEvent(MotionEvent ev) {

    if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {return true;}
    return onTouchEvent(ev);

}

从下面的代码能够事件产生,事件总是先传递给 Activity,Activity 再传递给 Window,Window 再传递给 DecorView。DecorView 接管到事件后,就会依照事件流传机制去流传事件;如果一个 View 的 onTouchEvent 返回 false,那么它的父容器的 onTouchEvent 就会被调用,依此推导。如果所有的视图都没有处理事件,那么 Activity 就会解决它,也就是 Activity 的 onTouchEvent 办法会被调用。咱们来验证一下窗口的实现类是不是 PhoneWindow,咱们点击查看 Activity 的 getWindow 源码办法;

public Window getWindow() {

    return mWindow;

}

发现 getWindow 办法返回的只是一个 Window 对象,发现了 Activity,发现在 attach 办法中实例化了 mWindow;

final void attach(Context context, ActivityThread aThread,

                  Instrumentation instr, IBinder token, int ident,
                  Application application, Intent intent, ActivityInfo info,
                  CharSequence title, Activity parent, String id,
                  NonConfigurationInstances lastNonConfigurationInstances,
                  Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                  Window window, ActivityConfigCallback activityConfigCallback) {
    ......
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {mWindow.setUiOptions(info.uiOptions);
    }
    ......

}

Window Window 的实现类是 PhoneWindow;“DecorView 是以后的界面组件 FrameLayout”不会在查源码中证实了,吸引的读者能够本人浏览源码。

咱们回顾 View 中的 dispatchTouchEvent 办法的源码;

public boolean dispatchTouchEvent(MotionEvent event) {

    ......
    if (onFilterTouchEventForSecurity(event)) {if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {result = true;}
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {result = true;}

        if (!result && onTouchEvent(event)) {result = true;}
    }
    ......
    return result;

}

从 View 中的 dispatchTouchEvent 办法,咱们不能得出一些论断,如果一个 View 须要处理事件时,如果它应用了 setOnTouchListener 语句,那么 OnTouchListener 中的 onTouch 会被调用;如果 onTouch 办法的返回值返回 false,那么这个 View 的 onTouchEvent 办法会被调用;如果返回 true,那么 onTouchEvent 办法将不会被调用,所以给 View 应用 setOnTouchListener 语句,它的优先级比 onTouchEvent 办法要高;在以后的 View 中,如果有 OnClickListener 语句,它的 onClick 办法会被调用,前提是 onTouchEvent 返回值为 super.onTouchEvent(event),所以 setOnClickEvent(event),所以 setOnClickEventListener 的优先级,它是高那么高后才触发。

上面咱们用 demo 验证一下下面的论断;

(1)新建一个 kt 语言的类 MyView 并继承于 View:

class MyView: View {

companion object {var TAG:String = "Activity"}
constructor(context: Context): super(context){
}

constructor(context: Context, attrs: AttributeSet): super(context, attrs){

}

constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr){

}

override fun onTouchEvent(event: MotionEvent?): Boolean {Log.d(TAG,"------------onTouchEvent--")
    return super.onTouchEvent(event)
}

}

(2)新建一个组件文件 activity_main.xml:

<?xml version=”1.0″ encoding=”utf-8″?>
<com.xe.eventdemo.MyView

android:id="@+id/btn"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FF00"
xmlns:android="http://schemas.android.com/apk/res/android" />

(3)新建一个 kt 语言的类 MainActivity 并继承于 AppCompatActivity:

class MainActivity: AppCompatActivity() {

var mMyView: View? = null ;
companion object {var TAG: String = "Activity"}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    mMyView = findViewById(R.id.btn)
    mMyView!!.setOnTouchListener(object : View.OnTouchListener {override fun onTouch(v: View?, event: MotionEvent?): Boolean {Log.d(TAG,"------------onTouch--")
            return true
        }
    })
    mMyView!!.setOnClickListener(object : View.OnClickListener {override fun onClick(v: View?) {Log.d(TAG,"------------onClick--")
        }
    })

}

}

程序一开始运行的界面如下所示:

图片

当我点击一下时,日志打印如下所示(阐明没有调用 onTouchEvent 办法和 onClick 办法):

图片

当我把 onTouch 办法的返回值改成假,再运行程序微微点击一下时,日志打印如下所示(阐明调用了的 onTouchEvent 方行业释义法律的 onClick 状语从句:办法):

图片

当我把 的 onTouchEvent 方行业释义法律的报道查看值改成真,运行再程序微微点击一下时,日志打印如下所示(阐明没有调用 的 onClick 办法)

图片

为什么 onTouchEvent 办法的返回值只有超级。onTouchEvent(event) 时会调用 onClick 办法呢,咱们点击 super. onTouchEvent(event) 语句的代码,发现是 View 的 onTouchEvent 办法,它的实现如下所示:

public boolean onTouchEvent(MotionEvent event) {

    ......
    switch (action) {
        case MotionEvent.ACTION_UP:
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            if ((viewFlags & TOOLTIP) == TOOLTIP) {handleTooltipUp();
            }
            if (!clickable) {removeTapCallback();
                removeLongPressCallback();
                mInContextButtonPress = false;
                mHasPerformedLongPress = false;
                mIgnoreNextUpEvent = false;
                break;
            }
            boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
            if ((mPrivateFlags & PFLAG_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 (prepressed) {
                    // The button is being released before we actually
                    // showed it as pressed.  Make it show the pressed
                    // state now (before scheduling the click) to ensure
                    // the user sees it.
                    setPressed(true, x, y);
                }

                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                    // 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) {
                    postDelayed(mUnsetPressedState,
                            ViewConfiguration.getPressedStateDuration());
                } else if (!post(mUnsetPressedState)) {
                    // If the post failed, unpress right now
                    mUnsetPressedState.run();}

                removeTapCallback();}
            mIgnoreNextUpEvent = false;
            break;
        ......
    }
    ......
    return false;

}

从这里能够收回,当手指进步来的时候,查看办法的 onTouchEvent 办法又会调用 performClick 办法,performClick 办法接口又会调用 OnClickListener 的 onClick 办法:

public boolean performClick() {

    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {result = false;}

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    notifyEnterOrExitForAutoFillIfNeeded(true);

    return result;

}

正文完
 0