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(如ButtonSubClass extends Button)的事件分发 * * * 重点说明: * 1 在该示例中讨论的是View(如ButtonSubClass extends Button)的事件分发,而不是ViewGroup的 * 2 ViewGroup中事件处理的流程是: * dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent * View(如ButtonSubClass extends Button)中事件处理的流程是: * dispatchTouchEvent->onTouchEvent * 即在View(如ButtonSubClass extends Button)中是没有onInterceptTouchEvent事件拦截的. * 因为该类View已经处于事件传递的末端,对于拦截没有实际意义,也谈不上什么拦截 * * * 概要梳理: * 在View(如ButtonSubClass extends Button)的事件分发过程中主要涉及到: * dispatchTouchEvent()和onTouchEvent()以及该View的TouchListener中的onTouch() * 1 事件的分发从dispatchTouchEvent()开始. * 返回值为true时表示继续事件分发;返回值为false时表示终止事件分发. * 2 onTouchEvent()中在ACTION_UP即手指抬起时处理点击Click事件 * 3 TouchListener中的onTouch()返回 true或者false表示是事件是否被消耗 * * * dispatchTouchEvent()源码如下: * 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()中调用了onTouchEvent()!!!!!!!! * * * 从该dispatchTouchEvent()的源码也可以看出 * onTouch(this,event)和 onTouchEvent(event)的区别和关系: * 1 onTouch()与onTouchEvent()都是用户处理触摸事件的API. * 2 onTouch()是View专门提供给用户的接口,是为了方便用户处理触摸事件, * 而onTouchEvent()是Android系统自己实现的接口。 * 3 先调用TouchListener中的onTouch()后调用onTouchEvent()即onTouch()的优先级比onTouchEvent()的优先级更高。 * 4 在TouchListener中的onTouch()方法中处理了Touch事件,即处理一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP事件 * 返回false时表示事件(每个单独的ACTION_DOWN,ACTION_MOVE,ACTION_UP都叫一个事件,并不是说这三者联系在一起才是一个事件) * 未被消耗才会调用onTouchEvent(event). * 5 在onTouchEvent(event)中的ACTION_UP事件里(即手指抬起)会调用performClick()处理OnClick点击事件!!!! * 6 所以可知: * 4.1 Touch事件先于Click事件发生和处理,且注意TouchListener中的onTouch()方法默认返回为false. * 4.2 只有在TouchListener中的onTouch()返回false时(即事件未被消耗)才会调用onTouchEvent() * 4.3 在onTouchEvent()中的ACTION_UP事件里(即手指抬起)会调用performClick()处理OnClick点击事件. * 7 参见下面的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是有区别的. * 再次提醒注意:TouchListener中的onTouch()方法默认返回为false. * 1 Button做实验分析dispatchTouchEvent(). * mOnTouchListener.onTouch()返回false(默认值), * 所以dispatchTouchEvent()源码中的if不满足,于是会执行onTouchEvent(event). * 进入onTouchEvent()源码后,请留意第三个if的判断!!! * 由于Button默认情况下就是CLICKABLE和LONG_CLICKABLE的,所以满足该if,于是进入后 * 进行ACTION_DOWN,ACTION_MOVE,ACTION_UP的处理. * 请注意:在该if最后总是会返回true(事件被消费)!!!!!!!!!!!!!!!! * 即onTouchEvent()的返回值为true,该值此时继续作为返回值返回给dispatchTouchEvent(). * 因为dispatchTouchEvent()收到的返回值是true,所以继续ACTION_MOVE,ACTION_UP. * 小结: * 我们在Button控件的TouchListener的onTouch()返回了false,表示事件未消费. * 按理说只会响应ACTION_DOWN不会有后续的ACTION_MOVE,ACTION_UP. * 但是事与愿违,事实情况并非如此.这是为什么呢? * 因为mOnTouchListener.onTouch()返回false,所以dispatchTouchEvent()中会执行onTouchEvent(event)方法 * 在onTouchEvent(event)对应Button(默认情况下是CLICKABLE和LONG_CLICKABLE的)而言, * 该方法总会返回true(事件被消费)给dispatchTouchEvent(),所以会继续事件的派发. * * 所以可以捕获到一系列的:ACTION_DOWN,ACTION_MOVE,ACTION_UP. * 这就解释了为什么在Button中虽然TouchListener的onTouch()返回false(默认值)但事件分发还在继续!!!! * * 2 用ImageView做实验分析dispatchTouchEvent(). * mOnTouchListener.onTouch()返回false(默认值),所以dispatchTouchEvent() * 如上dispatchTouchEvent()源码中的if不满足,在调用onTouchEvent(event)时 * 由于ImageView不满足CLICKABLE和LONG_CLICKABLE中任何一个条件。 * 所以最后返回给dispatchTouchEvent()的是false,即终止事件的分发.所以对于ImageView只有 * ACTION_DOWN没有ACTION_MOVE和ACTION_UP * 这里就解释了为什么在ImageView中在TouchListener的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判断 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.widget.Button; public class ButtonSubClass extends Button { public ButtonSubClass(Context context) { super(context); } public ButtonSubClass(Context context, AttributeSet attrs) { super(context, attrs); } public ButtonSubClass(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } @Override public boolean dispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } }
main.xml如下:
<RelativeLayout 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" > <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="100dip" android:text="Button" /> <ImageView android:id="@+id/imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="200dip" android:src="@drawable/ic_launcher" /> </RelativeLayout>
时间: 2024-10-25 12:42:51