您的位置:首页 > 移动开发 > Android开发

Android事件分发机制

2016-06-04 01:25 429 查看

Android笔记-Android事件分发机制

最近看了不少大牛的博客并结合源码,在这里记录一下自己对Android事件分发的理解。

先来一个简单的例子,定义一个类MyLinearLayout继承LinearLayout,定义一个类MyButton继承Button,修改一下布局文件activity_main

MyButton

public class MyButton extends Button{
public MyButton(Context context) {
this(context, null);
}
public MyButton (Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyButton (Context context, AttributeSet attrs, int defStyleAttr) {
super (context, attrs, defStyleAttr);
}
}


MyLinearLayout

public class MyLinearLayout extends LinearLayout{

public MyLinearLayout(Context context) {
this(context, null);
}

public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs, 0);
}
}


activity_main

<?xml version="1.0" encoding="utf-8"?>
<com.tyz.motioneventdemo001.view.MyLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.tyz.motioneventdemo001.MainActivity">

<com.tyz.motioneventdemo001.view.MyButton
android:id="@+id/my_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/text_onclick"/>

</com.tyz.motioneventdemo001.view.MyLinearLayout>


MainActivity的initView()

public void initView() {
mLlMyLayout = (MyLinearLayout) findViewById(R.id.my_layout);
mBtnMyButton = (MyButton) findViewById(R.id.my_button);
mLlMyLayout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d("MotionEvent", "mLlMyLayout onTouch" + motionEvent.getAction());
return false;
}
});
mLlMyLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MotionEvent", "mLlMyLayout onClick");
}
});
mBtnMyButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d("MotionEvent", "mBtnMyButton onTouch" + motionEvent.getAction());
return false;
}
});
mBtnMyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d("MotionEvent", "mBtnMyButton onClick");
}
});
}


先运行一下,点击MyButton,查看打印日志


" title="">


" title="">

这里可以发现两点:

1. 一次点击OnTouch事件执行了多次

2. onClick事件在onTouch1事件之后执行

第2点之后在具体分析,第1点日志结合MotionEvent的源码,getAction()的值0对应ACTION_DOWN,1对应ACTION_UP,2对应ACTION_MOVE。这里可以得到

结论1:每次从手指接触到抬起,产生了DOWN事件,UP事件各一次,N次MOVE事件(N大于等于0)。

观察一下MyButton的OnTouchListener中onTouch方法,它的返回是个boolean类型。现在是返回false,我们改为true试一下。

public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d("MotionEvent", "mBtnMyButton onTouch" + motionEvent.getAction());
return true;
}


再次查看日志


" title="">

onClick没有被执行,改变onTouch的返回值就能影响onClick!!!OK,先查看MyButton的setOnTouchListener方法,直接跳转进入了View。

public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}


其实就是一个赋值语句,这里我们继续看getListenerInfo().mOnTouchListener。

private OnTouchListener mOnTouchListener;
public interface OnTouchListener {
/**
* Called when a touch event is dispatched to a view. This allows listeners to
* get a chance to respond before the target view.
*
* @param v The view the touch event has been dispatched to.
* @param event The MotionEvent object containing full information about
*        the event.
* @return True if the listener has consumed the event, false otherwise.
*/
boolean onTouch(View v, MotionEvent event);
}


然后再找onTouch在哪里被调用了,只有一处在View源码9290行dispatchTouchEvent中

if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}


看到这里,dispatchTouchEvent从名字上看就知道了事件分发它说了算。先看代码

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;
}


在MyButton中重写方法dispatchTouchEvent()

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("MotionEvent", "MyButton dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}


运行,查看日志


" title="">

先执行dispatchTouchEvent(),在运行到li.mOnTouchListener.onTouch(this, event)执行onTouch方法,由于onTouch方法返回true,可以理解为事件被处理了,而不再向下执行。再将onTouch()的返回值改为false。

mBtnMyButton.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
Log.d("MotionEvent", "mBtnMyButton onTouch" + motionEvent.getAction());
return false;
}
});


再查看日志


" title="">

由于onTouch()返回值为false,所以!result为true。这时result = !result && onTouchEvent(),dispatchTouchEvent()的返回值就等于onTouchEvent()的值。

结论2:事件如果被onTouch”消费”了,即设置了onTouch事件且返回true,则事件不再往下传递给onTouchEvent()

现在来看onTouchEvent的源码

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;
}


这里可以看到在MotionEvent.ACTION_UP分支里面调用了performClick(),在MotionEvent.ACTION_DOWN分支里面调用了postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout())其实也就是延时调用了performLongClick()

为mBtnMyButton设置OnLongClickListener

mBtnMyButton.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Log.d("MotionEvent", "mBtnMyButton onLongClick");
return false;
}
});



" title="">

这里当onLongClick返回false,onClick也得到了执行。将onLongClick返回值改为true试试。


" title="">

onClick并没有执行,仔细看代码发现在View的CheckForLongPress中

@Override
public void run() {
if (isPressed() && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
if (performLongClick()) {
mHasPerformedLongPress = true;
}
}
}


performLongClick()为true时,mHasPerformedLongPress = true;而在View的onTouchEvent的ACTION_UP中

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();
}
}
}


结论3:在onTouchEvent中,如果设置了OnLongClickListener,且onLongClick返回true,则onClick将不再执行

第一次写博客,这篇算是Android事件分发机制不完全分析,还有许多地方没有讲到,下次再继续,先去看球,勇士赢啦!呵呵!

感谢郭神洋神工匠若水任玉刚warmor的分享。在他们那里学到许多关于Android事件分发的知识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: