1,子View的事件分发机制
2,ViewGroup的事件分发机制
转载请标明出处:http://blog.csdn.net/u014800493/article/details/52047167
在分析事件分发之前,先了解一下View,ViewGroup的API层级结构
从上面的层级关系可以看出ImageView,TextView,ViewGroup继承View,属于同级关系而Button,EditText继承TextView。下面再来看看ViewGroup的子类有什么
上图可以看出平常所用到的layout基本继承于ViewGroup.
好了话不多说,进入主题。首先还是分析一下子View也就是常用的TextView,Button等等的一些View控件。当然分析要用实际用例才能更加明了。下面就是一个Activity 里面就有一个Button。先来看看布局文件
<?xml version="1.0" encoding="utf-8"?>
<com.gordon.shop.view.MyButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/button_onclick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:text="按钮点击事件" >
</com.gordon.shop.view.MyButton>
很简单就一个Button.当然这里自定义了一个Button
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.Button;
/**
* @author Gordon
* @since 2016/7/27
* do()
*/
public class MyButton extends Button {
public MyButton(Context context) {
super(context);
}
public MyButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("button","Button_onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("button","Button_dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}
}
主要就是就是在onTouchEvent和dispatchEvent中打出相应的Log,从而显示button的事件
再来看看Activity中的代码:
/**
* @author Gordon
* @since 2016/7/27
* do()
*/
public class OnClickEventActivity extends Activity {
@Bind(R.id.button_onclick)
MyButton click_button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_onclick_layout);
ButterKnife.bind(this);
intView();
}
private void intView() {
click_button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.i("button", "Button_OnClick");
}
});
click_button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.i("button", "Button_setOnTouchListener");
return false;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("activity", "Activity_onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i("activity", "Activity_dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
}
13行的ButterKnife为引用的第三方的包,其实就是方便,可以省略之前的findViewById()。使用的话就是在.gradle文件中引用:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.jakewharton:butterknife:7.0.0'
}
而在Activity中也有OnTouchEvent和dispatchEvent方法,在这里也重写了一下并且相应的log出来。
好了基本用例已经编写完成。下面就让我们来见证一下吧。先来看看跑出来的Activity图
先走的Activity 的dispatchEvent方法,然后是onTouchEvent,继续点击button按钮
为Activity_dispatchEvent----------->onTouchEvent
为Activity_dispatchEvent---------->Button_dispatchEvent---------->Button_setOnTouchListener(也就是onTouch())---------->Button_onTouchEvent
---------->Button_onClick
首先来分析一下几个疑点:
1,为什么没有走Activity的ouTouchEvent方法呢
2,为什么是dispatchEvent--->onTouch---->onTouchEvent这个走向呢
好了直接上源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
这是activity的dispatchEvent的源码。代码看似很简单。一步步分析一下第一个if语句ACTION_DOWN,也就是手指按下事件,不多说。看看onUserInteraction()方法,点进去看看是什么
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
what?怎么是空的。看一下官方的注释,大致就是可以在此方法总实现用户的一些动作。这里就不多说了 。看下第二个if语句
getWindow()点进去看看
public Window getWindow() {
return mWindow;
}
这个mWindow是什么呢。在来搜索一下
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
原来是PhoneWindow啊,而PhoneWindow又是什么呢,他的.superDispatchEvent(et)方法源码又是什么呢?
这个肯定要去看看PnoneWindow的源码咯:这里主要看下superDispatchEvent()方法:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
....................
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
................
}
这TM的mDecor又是什么鬼。原来是DecorView。是PhoneWindow的内部类
private final class DecorView extends FrameLayout {
...................
public boolean superDispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
public boolean superDispatchTrackballEvent(MotionEvent event) {
return super.dispatchTrackballEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
..............
}
只给出了DecorView的部分源码,原来DecorView父类为FrameLayout,而FrameLayout的父类是ViewGroup。
好了再回头看一下Activity的dispatchEvent方法的第二个if语句getWindow().superDispatchEvent()这里居然是ViewGroup的
事件分发机制。简单的说一下其实是被ViewGroup的子View (Button)是事件给拦截了直接return true。所以才没有执行activty的onTouchEvent()方法
至于怎么拦截的。下一章具体再说。还有人问了,这个PhoneWindow,DecorView,Activity到底是什么关系呢,大家可以网上搜一下。大致的结构图为
也就是Activity用PhoneWindow来设置RootView(DecorView)。好了继续探讨一下子VIew(Button)的点击事件。也就是前面的第二个问题
2,为什么是dispatchEvent--->onTouch---->onTouchEvent---->onClick这个走向呢.先瞅瞅Button的dispatchEvent源码:
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
// If the event should be handled by accessibility focus first.
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
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;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
先看看第10行的if语句,看注释,判断event是否能获取焦点如果不能或者不存在这个View,直接返回false跳出循环。
第21-26行的if语句处理一些手势如:action_down,up,move判断手势,手势的传递,看看stopNestedScroll()方法
/**
* Stop a nested scroll in progress.
*
* <p>Calling this method when a nested scroll is not currently in progress is harmless.</p>
*
* @see #startNestedScroll(int)
*/
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
mNestedScrollingParent.onStopNestedScroll(this);
mNestedScrollingParent = null;
}
}
当Action_Down的时候处理之前的手势问题,这些不是重点。下面看看31-39行
- 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;
- }
- }
/**
* Filter the touch event to apply security policies.
*
* @param event The motion event to be filtered.
* @return True if the event should be dispatched, false if the event should be dropped.
*
* @see #getFilterTouchesWhenObscured
*/
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
过滤一些时间比如Window隐藏或者遮挡了直接返回false,正常情况下返回true,进而去处理Event事件。
好了进入if语句内部ListenerInfo是View的静态内部类用来定义listener,也就是view添加的一些listener。
继续往下又一个if语句首先是li和lTouchistener的非空判断,因为之前的Activity里面已经设置了button
的ontouchListener事件。接着(mViewFlags & ENABLED_MASK) == ENABLED判断view是
否为Enable。当然View默认都是Enable的,接着就是 li.mOnTouchListener.onTouch(this, event)
这个了,也即是说如果ouTouch返回true,那么result为true就不会进入下面if语句
- if (!result && onTouchEvent(event)) {
- result = true;
- }
当然如果返回false,就会走onTouchEvent()方法。让我们来看看ouTouchEvent()源码:
/**
* Implement this method to handle touch screen motion events.
* <p>
* If this method is used to detect click actions, it is recommended that
* the actions be performed by implementing and calling
* {@link #performClick()}. This will ensure consistent system behavior,
* including:
* <ul>
* <li>obeying click sound preferences
* <li>dispatching OnClickListener calls
* <li>handling {@link AccessibilityNodeInfo#ACTION_CLICK ACTION_CLICK} when
* accessibility features are enabled
* </ul>
*
* @param event The motion event.
* @return True if the event was handled, false otherwise.
*/
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == 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)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
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 && !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;
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;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
源码有些长。慢慢解读先来看看24-33行的if语句。判断view是不是Enable状态。当view为disable状态时,看下return。
只要这个
View
的CLICKABLE
和LONG_CLICKABLE
或者CONTEXT_CLICKABLE
有一个为true,那么返回值就是true,
onTouchEvent()
方法会消耗当前事件。那么CLICKABLE
和LONG_CLICKABLE
或者CONTEXT_CLICKABLE。在哪设置的呢:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
public void setClickable(boolean clickable) {
setFlags(clickable ? CLICKABLE : 0, CLICKABLE);
}
这里只列出了onClickListener的源码,onLongClickListener和onContextClickListener是一样的就不一一列举了
也就是说只要你设置了这三类listener。无论View是否是Disable的此View就会在ouTouchEvent()方法中消耗此事件。
也就是返回true。也不会
交给parent的View去处理了
继续看35-39行的if语句。这个TouchDelegate类是什么鬼。看下源码:
/**
* Helper class to handle situations where you want a view to have a larger touch area than its
* actual view bounds. The view whose touch area is changed is called the delegate view. This
* class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
* instance that specifies the bounds that should be mapped to the delegate and the delegate
* view itself.
* <p>
* The ancestor should then forward all of its touch events received in its
* {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
* </p>
*/
public class TouchDelegate {............}
里面的内容就不贴上来了,主要看下官方去这个类的注释。主要就是设置view的代理touch区域,
也就是说当你的view很小时,你可以设置他的代理touch区域。好了继续往下解读。
41行if语句的判断前面已经解读过了。看下if语句里面的内容:
先看先case MotionEvent.Action_down:先把长按判断设为false。继续
if (performButtonActionOnTouchDown(event)) { break; }这个performButtonActionOnTouchDown()方法又是干啥的呢。进去看看
/**
* Performs button-related actions during a touch down event.
*
* @param event The event.
* @return True if the down was consumed.
*
* @hide
*/
protected boolean performButtonActionOnTouchDown(MotionEvent event) {
if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE &&
(event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0) {
showContextMenu(event.getX(), event.getY(), event.getMetaState());
mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
用来判断是不是鼠标或者触碰板来消耗down事件。下面的代码就不解读了,接着看case MotionEvent.Action_move:
这里面移除单击以及长按事件。主要看下case MotionEvent.Action_UP:
首先判断是不是按下boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0,然后判断是不是获取焦点,
如果没有尝试获取焦点,接着看重点:
- 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();
- }
- }
- }
private final class PerformClick implements Runnable {
@Override
public void run() {
performClick();
}
}
也就是执行performClick()方法。继续看方法源码:
/**
* Call this view's OnClickListener, if it is defined. Performs all normal
* actions associated with clicking: reporting accessibility event, playing
* a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
*/
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前面已经说过了,看下if语句,li.mOnClickListener!=null也就是Button设置了setOnClickListener()方法。执行onClick()方法到此为止 :子View (button)的事件传递为
Button_dispatchEvent---------->Button_setOnTouchListener(也就是onTouch())---------->Button_onTouchEvent
---------->Button_onClick。也被证实了。
小结:
button先执行dispatchEvent方法,接着判断有没有设置setOnTouchListener方法,如果在listener.onTouch()中return true。
就不会继续执行ouTouchEvent方法。如果返回false 继续走onTouchEvent方法。在此方法的case Action_up:中。
判断是不是点击事件以后从而执行了onClick()方法了。
总结:至此 子View的事件处理机制,已经分析完了,之间找了很多的资料。主要还是要自己动手去验证。接下来