1–1:例子
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/layout"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Click" />
</RelativeLayout>
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
Button btn;
RelativeLayout layout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn = (Button) findViewById(R.id.button);
layout = (RelativeLayout) findViewById(R.id.layout);
btn.setOnClickListener(this);
btn.setOnTouchListener(this);
layout.setOnTouchListener(this);
layout.setOnClickListener(this);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("----", "onTuch");
return false;
}
@Override
public void onClick(View v) {
Log.d("----", "onClick");
}
}
1–2:代码分析:1– 布局文件中有RelativeLayout 和Button<属于ViewGroup的子类,ViewGroup属于View的子类>
2–对两个控件设置事件OnTuch(),OnClick()事件
3–稳稳点击Button;稳稳点击除Button之外的区域;点击Button时,晃动一下
可以看出:
1–OnTuch()与OnClick()相比,多一个MotionEvent类型的event参数,这样,可以在此处理 ACTION_DOWN、ACTION_UP、ACTION_MOVE等的触摸事件,
2–OnTuch()返回一个false;
1–3:修改OnTuch(),让返回为true——>可以看出,无法进行执行OnClick()事件!
1–4:总结
1—–Android控件的Listener事件触发顺序是先触发onTouch,其次onClick!
2—–如果控件的onTouch返回true将会阻止事件继续传递,返回false事件会继续传递!
2–1:dispatchTouchEvent()
package android.app----->Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
package android.view;---->View
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.isTargetAccessibilityFocus()) {
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
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);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
分析:
1–
if (onFilterTouchEventForSecurity(event))语句判断是否被遮盖等
2–
ListenerInfo li = mListenerInfo;
ListenerInfo是View的静态内部类,用来定义一下监听方法;
3–
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//对mOnTouchListener 进行赋值
public void setOnTouchListener(OnTouchListener l) {
getListenerInfo().mOnTouchListener = l;
}
可以看出mOnTouchListener 的判断取决于View(控件)是否设置
setOnTouchListener()事件;通过位与运算判断控件(View)是否为ENABLED,所以控件显示状态默认为ENABLED!;判断onTouch()事件返回值是否为TRUE;综合—>以上判断若都返回true,则
if (!result && onTouchEvent(event)) {
result = true;
}
不会执行,
result变量为true; 则dispatchTouchEvent()也会返回;
2- -1在dispatchTouchEvent方法中如果onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运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);
}
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) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
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)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
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);
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
前面几行代码可以看出如果控件(View)是disenable状态,同时是可以clickable的则onTouchEvent直接消费事件返回true,反之如果控件(View)是disenable状态,同时是disclickable的则onTouchEvent直接false。多说一句,关于控件的enable或者clickable属性可以通过java或者xml直接设置,每个view都有这些属性。
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE)
这个条件如果不成立,则直接返回false,则进入Action事件判断
<不成立:控件是enable且disclickable>
switch的ACTION_DOWN与ACTION_MOVE都进行了一些必要的设置与置位,接着到手抬起来ACTION_UP时你会发现,首先判断了是否按下过,同时是不是可以得到焦点,然后尝试获取焦点,然后判断如果不是longPressed则通过post在UI Thread中执行一个PerformClick的Runnable,也就是performClick方法
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
这个方法定义了一个类型为ListenerInfo 的li变量;判断li.
mOnClickListener ()是否为NULL,根据判断,确定是否执行OnClick()
可以看出:mOnClickListener的赋值:
public boolean hasOnClickListeners() {
ListenerInfo li = mListenerInfo;
return (li != null && li.mOnClickListener != null);
}
可以总结出:控件只要监听了OnClick()方法,则mOnClickListener() 就不会为NULL!
总结:
onTouchEvent方法中会在ACTION_UP分支中触发onClick的监听。
当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action!
三:例子
public class CustomButton extends Button {
public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomButton(Context context) {
super(context);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("----","dispatchTouchEvent"+event.getAction()+" ");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("----","onTouchEvent"+event.getAction()+" ");
return super.onTouchEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i("----","dispatchTouchEvent"+event.getAction()+" ");
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("----","onTouchEvent"+event.getAction()+" ");
return super.onTouchEvent(event);
}
ACTION_DOWN ==0;
ACTION_UP ==1;
ACTION_MOVE ==2;
1–1:正常情况
dispatchTouchEvent方法先派发down事件返回true,之后调用onTouch的down()事件,返回true,然后调运onTouchEvent的down()返回true,同时dispatchTouchEvent返回true,然后dispatchTouchEvent继续派发move或者up事件,循环,直到onTouchEvent处理up事件时调运onClick事件,完事返回true,同时dispatchTouchEvent返回true;一次完整的View事件派发流程结束。
2–1
修改onTouchEvent的返回值,直接为true,
直接不使用:return super.onTouchEvent(event);
当自定义了控件(View)的onTouchEvent直接返回true而不调运super方法时,事件派发机制类似,只是最后up事件没有触发onClick而已(因为没有调用super),就是说OnClick()无效
3–1
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i("----", "onTouchEvent--action " + event.getAction() + " ");
super.onTouchEvent(event);
return true;
}
修改代码,使用super(),然后保证返回值为true,则View的事件派发机制正常!
4–1:
修改onTouchEvent()返回值为false的时候,可以看到只派发的down()事件就停止了,所以需要理解的就是当返回false的时候,会阻止事件的派发
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return false;
}
5–1:
修改dispatchTouchEvent()直接返回为true,不调用super(),则任何事件都得不到触发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return true;
}
当调用super方法的时候,返回true,则事件可以正常派发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return true;
}
6–1:
修改dispatchTouchEvent()直接返回为false,任何事件都不触发
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
return false;
}
使用时,
dispatchTouchEvent(),onTuch(),onTuchEvent()的down()事件
可以执行,因为返回为false,则其他事件无法继续!
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return false;
}
7–1
修改dispatchTouchEvent返回值为true,onTouchEvent为false:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return true;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return false;
}
8–1
修改dispatchTouchEvent返回值为false,onTouchEvent为true:
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
super.dispatchTouchEvent(event);
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
return true;
}
dispatchTouchEvent事件派发是传递的,如果返回值为false将停止下次事件派发,如果返回true将继续下次派发。譬如,当前派发down事件,如果返回true则继续派发up,如果返回false派发完down就停止了!
总结View触摸屏事件传递机制
综合得出Android View的触摸屏事件传递机制有如下特征:
1–触摸控件(View)首先执行dispatchTouchEvent方法。
2–在dispatchTouchEvent方法中先执行onTouch方法,后执行onClick方法!
3–如果控件(View)的onTouch返回false或者mOnTouchListener为null(控件没有设置setOnTouchListener方法)或者控件不是enable的情况下会调运onTouchEvent,dispatchTouchEvent返回值与onTouchEvent返回一样。
4–如果控件不是enable的设置了onTouch方法也不会执行,只能通过重写控件的onTouchEvent方法处理(上面已经处理分析了),dispatchTouchEvent返回值与onTouchEvent返回一样。
5–如果控件(View)是enable且onTouch返回true情况下,dispatchTouchEvent直接返回true,不会调用onTouchEvent方法。
6–当dispatchTouchEvent在进行事件分发的时候,只有前一个action返回true,才会触发下一个action(也就是说dispatchTouchEvent返回true才会进行下一次action派发)。