Android事件分发详解(一)——View的事件分发
发布日期:2021-06-30 11:16:29 浏览次数:2 分类:技术文章

本文共 11146 字,大约阅读时间需要 37 分钟。

PS:

该系列博客已更新,详情请参见:

http://blog.csdn.net/lfdfhl/article/details/50707742

http://blog.csdn.net/lfdfhl/article/details/50707731

http://blog.csdn.net/lfdfhl/article/details/50707724

http://blog.csdn.net/lfdfhl/article/details/50707721

http://blog.csdn.net/lfdfhl/article/details/50707714

http://blog.csdn.net/lfdfhl/article/details/50707713

http://blog.csdn.net/lfdfhl/article/details/50707700

MainActivity如下:

package cc.cv;import android.os.Bundle;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.ImageView;import android.app.Activity;/** * Demo描述: * View的事件分发 *  * 在View的事件分发过程中主要涉及到dispatchTouchEvent()和onTouch()以及onTouchEvent() *  * dispatchTouchEvent()返回true或者false表示是否继续事件分发 * onTouch()返回 true或者false表示是事件是否被消耗 * onTouchEvent()中主要处理点击Click事件 *  * 事件的分发从dispatchTouchEvent()开始. * 方法dispatchTouchEvent()返回值为true时表示继续事件分发;返回值为false时表示终止事件分发. * 源码如下: *  * public boolean dispatchTouchEvent(MotionEvent event) { *   if (mOnTouchListener!= null&&(mViewFlags & ENABLED_MASK)==ENABLED&&mOnTouchListener.onTouch(this,event)){ *        return true; *     } *   return onTouchEvent(event); * } *  * 该方法的返回值有两种情况: *  * 1 满足if条件时,返回true.注意该if条件的三个判断. *     1.1 mOnTouchListener不等于null *     1.2 当前控件是enable的 *     1.3 调用mOnTouchListener.onTouch(this,event)返回的结果 *     前两个条件没啥可多说的,主要看看第三个条件: *     在 onTouch(View v, MotionEvent event)中会处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP. *     该onTouch()方法返回true表示事件已经消耗,返回false表示事件未消耗. *     比如在处理ACTION_DOWN时返回true才会继续分发ACTION_MOVE事件 *     比如在处理ACTION_MOVE时返回true才会继续分发ACTION_UP事件 *     比如在处理ACTION_DOWN时返回false,那么后续的ACTION_MOVE,ACTION_UP就不会再继续分发. *     我们在代码中也就无法捕捉到ACTION_MOVE,ACTION_UP这两个Action了. *     * 2 假如该if条件不满足,那么就继续执行,返回 onTouchEvent(event)的执行结果. *  *  * 从该dispatchTouchEvent()的源码也可以看出 * onTouch(this,event)和 onTouchEvent(event)的区别和关系: * 1 先调用onTouch()后调用onTouchEvent() * 2 在onTouch()方法中处理了Touch事件,即处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件 *   返回false时表示事件(每个单独的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一个事件,并不是说这三者联系在一起才是一个事件) *   未被消耗才会调用onTouchEvent(event). * 3 在onTouchEvent(event)中的ACTION_UP事件里会调用performClick()处理OnClick点击事件!!!! * 4 所以可知: *   4.1 Touch事件先于Click事件发生和处理,且注意onTouch()方法默认返回为false. *   4.2 只有在onTouch()返回false时(即事件未被消耗)才会调用onTouchEvent() *   4.3 在onTouchEvent()中的ACTION_UP事件会调用performClick()处理OnClick点击事件. * 5 参见下面的onTouchEvent()源码,请注意第三个if判断,这个if判断很重要!!!!!!! *   5.1 在该if条件中判断该控件是否是可点击的(CLICKABLE)或者是否是可以长按的(LONG_CLICKABLE). *   5.2 如果满足CLICKABLE和LONG_CLICKABLE中任一条件则始终会返回true给onTouchEvent()方法 *   5.3 如果CLICKABLE和LONG_CLICKABLE这两个条件都不满足则返回false给onTouchEvent()方法 *    *   请注意: *   Button默认情况下就是CLICKABLE和LONG_CLICKABLE的,但是ImageView在 *   默认情况下CLICKABLE和LONG_CLICKABL均为不可用的. *    *   所以在用Button和ImageView分别实验OnTouchListener和OnClickListener是有区别的. *   再次提醒注意:onTouch()方法默认返回为false. *   1 Button做实验分析dispatchTouchEvent(). *     mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent() *     如上源码中的if不满足,于是继续调用onTouchEvent(event)时由于Button满足CLICKABLE和LONG_CLICKABLE *     所以最后返回给dispatchTouchEvent()的是true,即继续事件的分发. *     所以可以捕获到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP. *     这里就解释了为什么在Button中虽然onTouch()返回false(默认值)但是事件分发还在继续!!!!!!!!!!!!! *      *   2 用ImageView做实验分析dispatchTouchEvent(). *     mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent() *     如上源码中的if不满足,在调用onTouchEvent(event)时由于ImageView不满足CLICKABLE和LONG_CLICKABLE *     中任何一个所以最后返回给dispatchTouchEvent()的是false,即终止事件的分发.所以对于ImageView只有 *     ACTION_DOWN没有ACTION_MOVE和ACTION_UP *     这里就解释了为什么在ImageView中在onTouch()返回里false(默认值)就终止了事件分发!!!!!!!!!!!!! *      *   如何才可以使ImageView像Button那样"正规的"事件分发,有如下两个方法: *   1 为ImageView设置setOnTouchListener,且在其onTouch()方法中返回true而不是默认的false. *   2 为ImageView设置android:clickable="true"或者ImageView设置OnClickListener. *      就是说让ImageView变得可点击. *   3 详情可见以下代码中的例子,关于这一点在下面的例子中有体现. *    *    *  * 参考资料: * http://blog.csdn.net/guolin_blog/article/details/9097463 * Thank you very much *  * 代码随笔: * 以前也看过事件分发,也自己总结了;可理解得不够. * 最近打算结合以前的东西和郭大婶的博客重新整理事件分发. * 由于理解上的差异郭大婶的这边博客,我看得比较吃力;后来也和他沟通了一下. * 所以这里是自己的理解,是正确的;只是在表述上和他的博客有所不同. */public class MainActivity extends Activity {    private Button mButton;    private ImageView mImageView;	@Override	protected void onCreate(Bundle savedInstanceState) {		super.onCreate(savedInstanceState);		setContentView(R.layout.main);		initButton();		initImageView();	}			private void initButton(){		mButton=(Button) findViewById(R.id.button);		mButton.setOnTouchListener(new OnTouchListener() {			@Override			public boolean onTouch(View v, MotionEvent event) {				switch (event.getAction()) {				case MotionEvent.ACTION_DOWN:					System.out.println("Button ACTION_DOWN");					break;				case MotionEvent.ACTION_MOVE:					System.out.println("Button ACTION_MOVE");					break;				case MotionEvent.ACTION_UP:					System.out.println("Button ACTION_UP");					break;				default:					break;				}				return false;			}		});						mButton.setOnClickListener(new OnClickListener() {			@Override			public void onClick(View v) {				System.out.println("Button Clicked");			}		});			}		private void initImageView(){		mImageView=(ImageView) findViewById(R.id.imageView);		mImageView.setOnTouchListener(new OnTouchListener() {			@Override			public boolean onTouch(View v, MotionEvent event) {				switch (event.getAction()) {				case MotionEvent.ACTION_DOWN:					System.out.println("ImageView ACTION_DOWN");					break;				case MotionEvent.ACTION_MOVE:					System.out.println("ImageView ACTION_MOVE");					break;				case MotionEvent.ACTION_UP:					System.out.println("ImageView ACTION_UP");					break;				default:					break;				}				return false;			}		});							    //因为ImageView默认是不可点击的,所以如果屏蔽掉以下的代码,则只有		//ImageView的ACTION_DOWN没有ACTION_MOVE和ACTION_UP		mImageView.setOnClickListener(new OnClickListener() {			@Override			public void onClick(View v) {				System.out.println("ImageView Clicked");			}		});			}}/* * 此处为onTouchEvent源码: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;} */

ButtonSubClass如下:

package cc.cv;import android.content.Context;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.ViewGroup;import android.widget.Button;public class ButtonSubClass extends Button {	public ButtonSubClass(Context context) {		super(context);		// TODO Auto-generated constructor stub	}	public ButtonSubClass(Context context, AttributeSet attrs) {		super(context, attrs);		// TODO Auto-generated constructor stub	}	public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) {		super(context, attrs, defStyle);		// TODO Auto-generated constructor stub	}		@Override	public boolean onTouchEvent(MotionEvent event) {		// TODO Auto-generated method stub		return super.onTouchEvent(event);	}		@Override	public boolean dispatchTouchEvent(MotionEvent event) {		// TODO Auto-generated method stub		return super.dispatchTouchEvent(event);	}		}
main.xml如下:

PS:

以前也整理过事件分发,不过当时太肤浅。

最近重新整理了该部分;月底发出来,算是对今年的一个告别。

其实这部分的核心就在于ViewGroup的dispatchTouchEvent()源码部分。

这次整理,对于该部分还是没有完全看懂;期待以后有机会继续。

学习是一个过程,这就是体现。

转载地址:https://it9527.blog.csdn.net/article/details/42241321 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:利用temp文件实现原子操作
下一篇:Android事件分发详解(二)——Touch事件传入到Activity的流程

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月16日 13时31分03秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章