Android5.1 触摸屏事件分发机制和源码解析一 --(View篇)

前言

事件分发机制,主要是需要了解View和ViewGroup的。

其中View篇是指单个View控件的分发流程,eg:buttion,textView等,它已经是最小单位了。

而ViewGroup篇则指布局控件的分发流程,eg:LinearLayout等,它包含了很多字View控件,父控件的触摸事件会传递给子控件。在实际的开发中,会存在很多事件的冲突,了解ViewGroup的事件分发,便可以更好的解决这类问题。

一。案例分析

首先,我们写一个非常简单的项目。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout"
    tools:context=".MainActivity" >

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button"
        android:text="button" />

</LinearLayout>
package com.example.viewanalyse;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class MainActivity extends Activity implements View.OnTouchListener,View.OnClickListener {
	private LinearLayout mLayout;
    private Button mButton;
    private static final String TAG = "ViewAnalyse";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLayout =(LinearLayout)findViewById(R.id.layout);
        mButton =(Button)findViewById(R.id.button);
        
        mLayout.setOnTouchListener(this);
        mButton.setOnTouchListener(this);

        mLayout.setOnClickListener(this);
        mButton.setOnClickListener(this);

        
    }

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouch-- action="+event.getAction()+" --"+v);
		return false;
	}
	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onClick --"+v);
	}


    
}


在activity里面,有一个LinearLayout布局和一个button按钮。给这俩个控件设置了onTouch和onClick监听事件。

1.1 我们用手指点击一下button。打印出来的Log如下:

10-15 09:59:49.991: D/ViewAnalyse(15405): onTouch-- action=0 --android.widget.Button
10-15 09:59:50.021: D/ViewAnalyse(15405): onTouch-- action=2 --android.widget.Button
10-15 09:59:50.031: D/ViewAnalyse(15405): onTouch-- action=1 --android.widget.Button
10-15 09:59:50.031: D/ViewAnalyse(15405): onClick --android.widget.Button

其中ACTION_DOWN=0(按下手指)  / ACTION_UP=1(抬起手指) / ACTION_MOVE=2(滑动手指)/

由Log我们可以看出,点击一个button,首先触发View的onTouch()方法(必然包括按下/抬起俩个动作,滑动动作可有可无),然后才会触发onClick()方法。

点击button事件传递顺序如下图所示:


 1.2 修改onTouch()方法里面的返回值,false --> true

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouch-- action="+event.getAction()+" --"+v);
		return true;
	}

我们再进行用手指点击一次button的操作,打印出来的log如下:

10-15 10:32:31.521: D/ViewAnalyse(21527): onTouch-- action=0 --android.widget.Button
10-15 10:32:31.551: D/ViewAnalyse(21527): onTouch-- action=1 --android.widget.Button 

我们可以发现如果onTouch()方法返回为true,则onClick()方法不会被调用。可以先理解成onTouch方法返回true就认为这个事件被onTouch消费掉了,因而不会再继续向下传递。

二。View.java传递事件源码分析

2.1 View 和 ViewGroup的关系

它们选择的安全式的组合模式(在组合对象ViewGroup中添加/0移除/获取组件View的方法),

2.2 View.java中的dispatchTouchEvent()方法

在Android中,只要触摸控件都会先触发View.java中的dispatchTouchEvent()方法。(如果自定义View的话,则可以覆写该方法)。

    public boolean dispatchTouchEvent(MotionEvent event) {

                      ...
					  
        if (onFilterTouchEventForSecurity(event)) {
            //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;
    }

dispatchTouchEvent()方法中,我们只看上面列出来的重点。

其中:if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnTouchListener.onTouch(this, event)) 这一句是重点。

在这个实例中,ListenerInfo类li 就是我们通过mButton.setOnTouchListener(this)设置的触摸监听事件。

(mViewFlags & ENABLED_MASK) == ENABLED 表示控件是不是EBABLED的,控件默认是ENABLED的。

接着判断View.OnTouchListener中的onTouch()的返回值(重点):

a.如果onTouch()返回true, 那么接下来的onTouchEvent()方法将不会得到执行(即onClick()方法也不会得到执行),dispatchTouchEvent()返回true(即触发下一个action)。
b.如果onTouch返回false,那么接下来就执行onTouchEvent()方法,onTouchEvent()的返回值决定dispatchTouchEvent()的返回值。

2.3 View.java中的onTouchEvent()方法

	
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
  
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // 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 & 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) {
                            // 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();
                    }
                    break;

                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;
					
					...
		         }

            return true;
        }

        return false;
    }
					

首先,我们来看6 --14行,如果这个View控件是disabled的,那么onTouchEvent()返回值取决于是否是可点击的clickable.。

控件View是可点击的Clickable,则返回true,表明消费掉这个事件。控件View是不可以点击的disClickable,则返回false。

接着,我们再看22-23行和107行,只要控件View是enabled的,onTouchEvent()都会返回true。

最后,我们看25行和55-56行,在手抬起来(即ACTION_UP)的时候,只要不是长按,就会在UI线程中post一个PerformClick的Runnable,执行performClick()方法。

    private final class PerformClick implements Runnable {
        @Override
        public void run() {
            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;
    }

在这个实例中,ListenerInfo类li就是我们通过mButton.setOnClickListener(this)设置的点击监听事件。
2.3 总结结论

a. dispatchTouchEvent()方法和onTouchEvent()方法是在View.java中,自定义View的时候,可以覆写该方法。

b. onTouch()和onClick()方法是在activity中,通过控件View注册监听,并覆写该方法。


 

三. 自定义一个Button

MyButton:

package com.example.viewanalyse;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.widget.Button;

public class MyButton extends Button {

    private static final String TAG = "ViewAnalyse";
    
	public MyButton(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public MyButton(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouchEvent-- action="+event.getAction());
		return super.onTouchEvent(event);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		Log.d(TAG, "dispatchTouchEvent-- action="+event.getAction());
		// TODO Auto-generated method stub
		return super.dispatchTouchEvent(event);
	}


}

MainActivity:

package com.example.viewanalyse;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

public class MainActivity extends Activity implements View.OnTouchListener,View.OnClickListener {
	private LinearLayout mLayout;
    private Button mButton;
    private static final String TAG = "ViewAnalyse";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mLayout =(LinearLayout)findViewById(R.id.layout);
        mButton =(MyButton)findViewById(R.id.button);
        
        mLayout.setOnTouchListener(this);
        mButton.setOnTouchListener(this);

        mLayout.setOnClickListener(this);
        mButton.setOnClickListener(this);

        
    }

	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouch-- action="+event.getAction()+" --"+v);
		return false;
	}
	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onClick --"+v);
	}


    
}


3.1 还是跟上面一样,点击一下这个button,打印出来的log:

01-02 07:31:57.323: D/ViewAnalyse(13846): dispatchTouchEvent-- action=0
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouch-- action=0 --com.example.viewanalyse.MyButton
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouchEvent-- action=0
01-02 07:31:57.323: D/ViewAnalyse(13846): dispatchTouchEvent-- action=2
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouch-- action=2 --com.example.viewanalyse.MyButton
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouchEvent-- action=2
01-02 07:31:57.323: D/ViewAnalyse(13846): dispatchTouchEvent-- action=1
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouch-- action=1 --com.example.viewanalyse.MyButton
01-02 07:31:57.323: D/ViewAnalyse(13846): onTouchEvent-- action=1
01-02 07:31:57.353: D/ViewAnalyse(13846): onClick --com.example.viewanalyse.MyButton

由log我们可以看出,不管action是ACTION_DOWN=0  / ACTION_UP=1 / ACTION_MOVE=2/,一个action的派发,流程都是:

dispatchTouchEvent() --> onTouch() --> onTouchEvent()
最后UP的时候,触发onClick()方法。 这和我们上述的分析一致。

3.2 修改onTouchEvent()返回值

3.2.1 onTouchEvent()返回false

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouchEvent-- action="+event.getAction());
		super.onTouchEvent(event);
		return false;
	}

用手指点击button,打印出来的log:

01-02 07:45:34.543: D/ViewAnalyse(14452): dispatchTouchEvent-- action=0
01-02 07:45:34.543: D/ViewAnalyse(14452): onTouch-- action=0 --com.example.viewanalyse.MyButton
01-02 07:45:34.543: D/ViewAnalyse(14452): onTouchEvent-- action=0
01-02 07:45:34.543: D/ViewAnalyse(14452): onTouch-- action=0 --android.widget.LinearLayout
01-02 07:45:34.543: D/ViewAnalyse(14452): onTouch-- action=2 --android.widget.LinearLayout
01-02 07:45:34.543: D/ViewAnalyse(14452): onTouch-- action=1 --android.widget.LinearLayout
01-02 07:45:34.573: D/ViewAnalyse(14452): onClick --android.widget.LinearLayout

因为onTouchEvent()返回值决定dispatchTouchEvent()返回值,当dispatchTouchEvent()返回值为false时,不会触发下一个action。

3.2.2onTouchEvent()返回true

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouchEvent-- action="+event.getAction());
		super.onTouchEvent(event);
		return true;
	}

用手指点击button,打印出来的log:

01-02 07:48:44.783: D/ViewAnalyse(15373): dispatchTouchEvent-- action=0
01-02 07:48:44.783: D/ViewAnalyse(15373): onTouch-- action=0 --com.example.viewanalyse.MyButton
01-02 07:48:44.783: D/ViewAnalyse(15373): onTouchEvent-- action=0
01-02 07:48:44.783: D/ViewAnalyse(15373): dispatchTouchEvent-- action=1
01-02 07:48:44.783: D/ViewAnalyse(15373): onTouch-- action=1 --com.example.viewanalyse.MyButton
01-02 07:48:44.783: D/ViewAnalyse(15373): onTouchEvent-- action=1
01-02 07:48:44.813: D/ViewAnalyse(15373): onClick --com.example.viewanalyse.MyButton

可以发现,跟3.1不做任何修改,打印出来的log是一致的,即正常派发。验证了我们之前说的,onTouchEvent()当控件是不可点击的disclickable才返回false,否则返回true。

3.2.3onTouchEvent()不调用父方法,直接返回true

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		Log.d(TAG, "onTouchEvent-- action="+event.getAction());
		
		return true;
	}


用手指点击button,打印出来的log:

01-02 07:53:52.783: D/ViewAnalyse(16228): dispatchTouchEvent-- action=0
01-02 07:53:52.793: D/ViewAnalyse(16228): onTouch-- action=0 --com.example.viewanalyse.MyButton
01-02 07:53:52.793: D/ViewAnalyse(16228): onTouchEvent-- action=0
01-02 07:53:52.793: D/ViewAnalyse(16228): dispatchTouchEvent-- action=1
01-02 07:53:52.793: D/ViewAnalyse(16228): onTouch-- action=1 --com.example.viewanalyse.MyButton
01-02 07:53:52.793: D/ViewAnalyse(16228): onTouchEvent-- action=1

发现没有调用onClick(()方法。验证了我们之前在源码中分析,onClick()方法是在父类View.java中的onTouchEvent()被调用。
3.3修改dispatchTouchEvent()的返回值

3.3.1dispatchTouchEvent()返回false

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		Log.d(TAG, "dispatchTouchEvent-- action="+event.getAction());
		// TODO Auto-generated method stub
		super.dispatchTouchEvent(event);
		return false;
	}


用手指点击button,打印出来的log:

01-02 08:04:57.483: D/ViewAnalyse(17243): dispatchTouchEvent-- action=0
01-02 08:04:57.483: D/ViewAnalyse(17243): onTouch-- action=0 --com.example.viewanalyse.MyButton
01-02 08:04:57.483: D/ViewAnalyse(17243): onTouchEvent-- action=0
01-02 08:04:57.493: D/ViewAnalyse(17243): onTouch-- action=0 --android.widget.LinearLayout
01-02 08:04:57.493: D/ViewAnalyse(17243): onTouch-- action=2 --android.widget.LinearLayout
01-02 08:04:57.493: D/ViewAnalyse(17243): onTouch-- action=1 --android.widget.LinearLayout
01-02 08:04:57.513: D/ViewAnalyse(17243): onClick --android.widget.LinearLayout

与3.2.1(onTouchEvent返回false)打印出来的log一致。即当前派发Down事件,返回true就会继续派发Up/Move事件,返回false就停止派发。

猜你喜欢

转载自blog.csdn.net/genius9_9/article/details/49148691