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

Android事件分发详解(二)——View的事件分发

2016-02-21 12:06 597 查看
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>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: