android 事件分发机制

Posted DemanMath

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 事件分发机制相关的知识,希望对你有一定的参考价值。

1.View的事件分发机制

一个button,简单一点就是onTouch,还有onclick事件,我们一个一个来分析

首先响应的是dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {  
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
            mOnTouchListener.onTouch(this, event)) {  
        return true;  
    }  
    return onTouchEvent(event);  
}

其实,在android源码的命名还是很有规律的,dispatchXXX,也就是分发机制,往往就是第一个需要响应的地方。

我们来分析下:touchlistener不为空,也就是view的使用者设置了回调。

第二个条件就是View必须是enable的。第三:onTouch返回false,就说明onTouch不消费该事件,由OnTouchEvent响应。

如果返回True,那么就会直接return。

所以onClick事件一定会被调到。

public boolean onTouchEvent(MotionEvent event) {  
    final int viewFlags = mViewFlags;  
    if ((viewFlags & ENABLED_MASK) == DISABLED) {  
        // A disabled view that is clickable still consumes the touch  
        // events, it just doesn\'t respond to them.  
        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;  
}  
onTouchEvent

最终会走到performClick这个方法。

    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);
        return result;
    }

可以看到,如果setOnClickListener, onClick 就会走到。

2.ViewGroup的事件分发机制

<com.joyfulmath.frameworksample.viewdemo.MyLayout
        android:id="@+id/my_layout"
        android:background="#99000044"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/button_id"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"/>
        <Button
            android:id="@+id/imageId"
            android:layout_centerInParent="true"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/ic_lock_power_off"/>
    </com.joyfulmath.frameworksample.viewdemo.MyLayout>

一个layout里面有2个button,

package com.joyfulmath.frameworksample.viewdemo;

import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;

import com.joyfulmath.frameworksample.R;

/**
 * Created by Administrator on 2016/8/27 0027.
 */
public class TestViewAction extends Activity implements View.OnClickListener,View.OnTouchListener
{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.content_main);
        Button button = (Button) findViewById(R.id.button_id);
        button.setOnClickListener(this);
        button.setOnTouchListener(this);
        Button imageView = (Button) findViewById(R.id.imageId);
        imageView.setOnClickListener(this);
        imageView.setOnTouchListener(this);
        MyLayout myLayout = (MyLayout) findViewById(R.id.my_layout);
        myLayout.setOnTouchListener(this);
        myLayout.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
        {
            case R.id.button_id:
                TraceLog.i("button_id");
                break;
            case R.id.imageId:
                TraceLog.i("imageId");
                break;
            case R.id.my_layout:
                TraceLog.i("my_layout");
                break;
        }

    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (v.getId())
        {
            case R.id.button_id:
                TraceLog.i("button_id");
                break;
            case R.id.imageId:
                TraceLog.i("imageId");
                break;
            case R.id.my_layout:
                TraceLog.i("my_layout");
                break;
        }
        return false;
    }
}
TestViewAction

分别点击button1 & button2 & 灰色部分

等到log如下:

08-27 10:19:26.799 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: button_id [at (TestViewAction.java:55)]
08-27 10:19:26.880 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: button_id [at (TestViewAction.java:55)]
08-27 10:19:26.896 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: button_id [at (TestViewAction.java:55)]
08-27 10:19:26.913 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: button_id [at (TestViewAction.java:55)]
08-27 10:19:26.926 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: button_id [at (TestViewAction.java:55)]
08-27 10:19:26.926 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onClick: button_id [at (TestViewAction.java:38)]
08-27 10:19:27.434 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: imageId [at (TestViewAction.java:58)]
08-27 10:19:27.535 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: imageId [at (TestViewAction.java:58)]
08-27 10:19:27.543 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: imageId [at (TestViewAction.java:58)]
08-27 10:19:27.544 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onClick: imageId [at (TestViewAction.java:41)]
08-27 10:19:28.111 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: my_layout [at (TestViewAction.java:61)]
08-27 10:19:28.156 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: my_layout [at (TestViewAction.java:61)]
08-27 10:19:28.173 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: my_layout [at (TestViewAction.java:61)]
08-27 10:19:28.190 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: my_layout [at (TestViewAction.java:61)]
08-27 10:19:28.237 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onTouch: my_layout [at (TestViewAction.java:61)]
08-27 10:19:28.237 2120-2120/com.joyfulmath.frameworksample I/TestViewAction: onClick: my_layout [at (TestViewAction.java:44)]

也就是点击button1以后,不会传递都layout

But,如果layout里面有一个函数

public boolean onInterceptTouchEvent(MotionEvent ev)

这个函数就是截断对button的分发处理,默认是return false。

至此,我们有了一个大概的流程。

Activtiy->ViewGroup->View 

如果仔细分析就会发现,在Activity里面有一个getDocView。所以Activity里面有个RootView的概念。

言归正传,ViewGroup本质上也是一个View,所以,可以把模型简单的定性为Activtiy->ViewGroup->View 三层。

首先Activity里面有2个函数,我们分析看看:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        TraceLog.i();
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        TraceLog.i();
        return super.onTouchEvent(event);
    }

所以大体流程如下:

1.@Activty.diapatchTouchEvent ->@Layout.dispatchTouchEvent->@layout.onInterceptTouchEvent return true/false

2.return true->@layout.onTouchEvent 后面部分同view

3.return false->@view.dispatchTouchEvent View的分发见上一片流程。

 

上面是2016年的文章分析,最新的java层的分析可以参考如下文章

demanmath.com

关注公共号:

公众号

 

参考:

《深入理解android设计思想》   林学森

以上是关于android 事件分发机制的主要内容,如果未能解决你的问题,请参考以下文章

Android ViewGroup事件分发机制

android 事件分发机制

Android View 事件分发机制

Android 事件分发事件分发源码分析 ( ViewGroup 事件传递机制 四 | View 事件传递机制 )

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

Android源码分析:View的事件分发机制探析