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

Android 触屏事件 OnTouch onClick onTouchEvent对于触屏事件的处理和分发

2015-05-06 14:31 525 查看

Android 触屏事件 OnTouch onClick onTouchEvent对于触屏事件的处理和分发

做项目的时候经常遇到需要事件分发,很多时候我们发现当我们触发了onTouch却触发不了onClick。或者触发了View的事件却触发不了ViewGroup的事件。那么他们之间到底是什么关系呢,其实最终他们涉及的只是两个问题

OnTouch 、onClick 、onTouchEvent 之间的关系
OnTouch 、onClick 、onTouchEvent 之间的处理顺序

这里,我做了简单的例子来看看他们之间的关系。



<FrameLayout 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"
android:background="#FFFFFF">
<com.example.empty.MyFrameLayout
android:id="@+id/mFrame"
android:layout_width="300dp"
android:layout_gravity="center"
android:layout_height="300dp"
android:background="#00FFFF" >

<com.example.empty.MyImageView
android:id="@+id/mImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/image_128"
android:background="#00FF00"
android:layout_gravity="center" />
</com.example.empty.MyFrameLayout>

</FrameLayout>


package com.example.empty;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.MotionEvent;
import android.widget.FrameLayout;

public class MyFrameLayout extends FrameLayout implements OnClickListener,OnTouchListener{

private static final String TAG = "Event";
public MyFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
Log.d(TAG,"MyFrameLayout init");
setOnClickListener(this);
setOnTouchListener(this);
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG,"MyFrameLayout dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"MyFrameLayout onTouchEvent");
return super.onTouchEvent(event);
}

@Override
public void onClick(View view) {
// TODO Auto-generated method stub
Log.d(TAG,"MyFrameLayout onClick");
}

@Override
public boolean onTouch(View view, MotionEvent event) {
// TODO Auto-generated method stub
Log.d(TAG,"MyFrameLayout onTouch");
return false;
}
}


package com.example.empty;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;

public class MyImageView extends ImageView implements OnClickListener,OnTouchListener{
private static final String TAG = "Event";
public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
Log.d(TAG,"MyImageView init");
setOnClickListener(this);
setOnTouchListener(this);
}

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG,"MyImageView dispatchTouchEvent");
return super.dispatchTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG,"MyImageView onTouchEvent");
return super.onTouchEvent(event);
}

@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
// TODO Auto-generated method stub
Log.d(TAG,"MyImageView onTouch");
return false;
}

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Log.d(TAG,"MyImageView onClick");
}

}


例子很简单,下面我们让Log来告诉我们一些东西
当我们点击我们ImageView时



当我们再点击我们FrameLayout



从这个Log 我们可以得出2点结论
一、执行顺序来讲
dispatchTouchEvent > onTouch > onTouchEvent > onClick
二、事件分发顺序
点击图片时 我们触发了而且还是首先触发了FrameLayout的dispatchTouchEvent

这里我们知道在Android中我们所有的控件都源自View甚至
public abstract class ViewGroup extends View


所以从底层来看我们的所有事件最终都是交给我们的View
目录(源码目录/frameworks/base\core\java\android\view/View.java)
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
......
public interface OnClickListener {
/**
* Called when a view has been clicked.
*
* @param v The view that was clicked.
*/
void onClick(View v);
}

......
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);
}
......
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);
}

if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}
}

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}

/**
* 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;
}
.....
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
/// M: we need to reset the pressed state or remove prepressed callback either up or cancel event happens.
final int action = event.getAction();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
} else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
Xlog.d(VIEW_LOG_TAG, "View onTouch event, if view is DISABLED & PFLAG_PREPRESSED, remove callback mPrivateFlags = "
+ mPrivateFlags + ", this = " + this);
removeTapCallback();
}
}
// 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 & PFLAG_PREPRESSED) != 0;
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch up: prepressed = " + prepressed + ",this = " + this);
}

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

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) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
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();
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch down: isInScrollingContainer = "
+ isInScrollingContainer + ",this = " + this);
}

// 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();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);
checkForLongClick(0);
}
break;

case MotionEvent.ACTION_CANCEL:
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch cancel: this = " + this);
}

setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;

case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)Touch move: x = " + x + ",y = " + y
+ ",mTouchSlop = " + mTouchSlop + ",this = " + this);
}

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

}


有了源码,其他自然一目了然。在这里我们发现onTouchListener 和onClickListener接口
很明显他们事件确定之后的回调(我自定义的View都实现了这两个接口)。
那么很明显他们和我们dispatchTouchEvent不会具有可比性
那么接下来我们把目光对准dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}

if (DBG_MOTION) {
Xlog.d(VIEW_LOG_TAG, "(View)dispatchTouchEvent: event = " + event + ",this = " + this);
}

if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}

if (onTouchEvent(event)) {
return true;
}
}

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}

/**
* 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;
}
这里重点说明一下onFilterTouchEventForSecurity这个函数
它通过判断我们的窗口Window是否被遮蔽来判断是否舍弃本次事件
所以这就是为什么我们点击我们的ImageView时我们的FrameLayout也执行了dispatchTouchEvent但是却没有向下走了
那么我们继续回归dispatchTouchEvent中不是ViewGroup的情形
接下来,系统会自动判断我们是否实现了onTouchListener 这里就开始有分支了
当我们实现了onTouchListener
那么下一步我们的事件叫交给了onTouchListener .onTouch来处理
这里就又开始了分支
如果我们在onTouch中返回了true,那么就表明我们的onTouchListener
已经消化掉了本次的事件,本次事件完结。这就是为什么我们在onTouch中返回去就永运不会执行onClick,onLongClick了

如果我们在onTouch中返回了false,那么很明显了我们的事件就会被onTouchEvent处理
同理,当我们没有实现了onTouchListener,很明显了我们的事件就会被onTouchEvent处理

殊途同归,最终如果我们的事件没有被干掉,最终都交给了onTouchEvent
那么接下来我们继续来看onTouchEvent
那么我们的onTouchEvent又是用来干什么的呢(这里既然已经有onTouchListener了,他们似乎一模一样啊)
其实不然,说白了我们的onTouchEvent最终会用来分发onClick和onLongClick事件
如果你个人在这里还是不能理解onTouchEvent函数
推荐阅读
Android中onTouch方法的执行过程以及和onClick执行发生冲突的解决办法

希望对你有帮助
这里我需要提到的是注意onTouchEvent对于View的State的判断和处理
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐