0
点赞
收藏
分享

微信扫一扫

Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆


学习的问题

首先view触摸体系逻辑非常多,因此我这里只记录关键的信息。

疑问1:

2、​​boolean onInterceptrEvent​​​方法viewgroup有那么view有没有??
3、​​​down和up​​什么区别?

总结

1、viewgroup 的​​dispatchTouchEvent()​​​默认​​ACTION_DOWN​​返回true

3、哪个viewgroup被调用了​​requestDisallowInterceptTouchEvent​​表示哪个viewgroup就不会拦截触摸事件了,那个谁的父亲也会 被设置

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果设置requestDisallowInterceptTouchEvent 为true,则表示 禁止拦截触摸事件,而且通知viewgroup以及viewgroup父容器都禁止拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}

4、外部设置的onTouchListener优先级要比自身的的onTouch优先级高如果返回true就不会再调用​​onTouchEvent​​​ 在​​View​​的​​dispatchTouchEvent​​方法可以看到下面这句话,如果外部设置的​​mOnTouchListener​​返回true 那么就不会再触发onTouchEvent

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

5、view的​​dispatchTouchEvent​​​ 逻辑比较少,主要处理自身的​​mOnTouchListener.onTouch​​​触摸事件和自身的​​onTouchEvent​​​回调
6、viewgroup的​​​dispatchTouchEvent​​​ 逻辑比较多 ,在按下的时候如果是down 进行 ​​onInterceptTouchEvent​​询问是否需要拦截,返回true表示拦截

if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down so this view group continues to intercept touches.
intercepted = true;
}

7、LinearLayout等常见布局都 没有复写​​dispatchTouchEvent()​​​没有为什么,因为viewgroup已经写好了。
8、默认逻辑是viewgroup的​​​onInterceptTouchEvent​​​在收到down事件会直接返回true,
9、在viewgroup的​​​dispatchTouchEvent​​​中如果 被设置禁用拦截也就是​​disallowIntercept​​​ 为true,那么就不会触发​​onInterceptTouchEvent​​​onInterceptTouchEvent方法
10、view的 ​​​onTouchEvent​​​默认是被view的​​dispatchTouchEvent​​​方法 【​​mOnTouchListener​​​为空或者​​mOnTouchListener​​返回 false时候】触发,

public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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;
}
}

11、​​onInterceptTouchEvent​​​或者​​mOnTouchListener.onTouch​​​ 返回false通常代表自己不拦截这个事件了.
12、​​​onInterceptTouchEvent​​​一旦返回true,那么之后是就不会再调用​​onInterceptTouchEvent​​​,然后也不交给子view,而是触发自己的的​​mOnTouchListener onTouch​​​或​​onTouchEvent​

13、viewgroup的事件分发伪代码的摘要和view的触摸机制代码概要

viewgroup

public boolean dispatchTouchEvent(MotionEvent ev) {
boolean intercepted = false;
if (mDISALLOW_INTERCEIT) {
intercepted = false;
} else {

intercepted = onInterceptTouchEvent(ev);
}

if (!intercepted) {

final int childrenCount = mChildrenCount;
for (int i = childrenCount - 1; i >= 0; i--) {


preorderedList = null;//TODO 这里的意思不太理解
final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled();
int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
PseudocCodeView[] children = mChildren;
final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent();
alreadyDispatchedToNewTouchTarget = true;
break;
}
}


}

if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view.
boolean canceled = true;
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//也会调用 dispatchTransformedTouchEvent 暂时不太明白是做了什么交叉

}
return intercepted;
}



private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) {

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

MotionEvent transformedEvent = MotionEvent.obtain(event);
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;

}

view

public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& ENABLE
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}

return false;
}

protected boolean onTouchEvent(MotionEvent event) {

// if(DISABLED){reutn false;}

if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) {
int action = event.getAction();

switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = true;
if (PFLAG_PRESSED || prepressed) {


if (!mHasPerformedLongPress) {
performClick();
}

}
break;

case MotionEvent.ACTION_DOWN:

break;

case MotionEvent.ACTION_CANCEL:
break;

case MotionEvent.ACTION_MOVE:
break;
}

return true;
}
return false;


}

14、​​requestDisallowInterceptTouchEvent​​​方法​​viewgroup​​​声明的,设置为true 会导致viewgroup不会触发自己的​​onInterceptTouchEvent​​ 15、childview只有在自己所在区域才会被触发 当childview不需要的时候会传递回调parent的onTouchEvent

:27:13.069 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start
05-02 18:27:13.069 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent false
05-02 18:27:13.070 14594-14594/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start
你好onTouchEvent false
你好dispatchTouchEvent end false
05-02 18:27:13.071 14594-14594/cn.qssq666.interrview W/FrameLayoutTouch: onTouchEvent false
05-02 18:27:13.071 14594-14594/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endfalse

上面是全false代码,执行流程是viewgroup的

完整的点击事件代码

05-02 18:48:42.508 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent start
05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/FrameLayoutTouch: onInterceptTouchEvent start
onInterceptTouchEvent end false
05-02 18:48:42.508 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好dispatchTouchEvent start
05-02 18:48:42.509 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好onTouchEvent start
你好post runnable android.view.View$PerformClick
你好post runnable android.view.View$UnsetPressedState //执行post
你好onTouchEvent true
你好dispatchTouchEvent end true
05-02 18:48:42.509 25196-25196/cn.qssq666.interrview E/FrameLayoutTouch: dispatchTouchEvent endtrue
05-02 18:48:42.510 25196-25196/cn.qssq666.interrview W/TextViewTest: 你好performClick
05-02 18:48:42.511 25196-25196/cn.qssq666.interrview W/MainActivity: onClick 你好




Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆_event


image.png



Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆_java_02


image.png


17、同级的view默认情况下不管是被同级的遮盖,都会触发事件 也就是说一个点击区域的所有子view都会响应点击事件
但是优先级是谁在上面谁先得到响应事件



Android触摸事件机制从疑问到总结到分析证明并手写伪代码方便记忆_java_03


image.png


所谓上面,就是xml声明最靠后的就是上面的view.

18、 dispatchTouchEvent的根源
从viewrootimp的inputState传递 ->然后传递给phonewindow里面的
decoreview的上层view​​​dispatchPointerEvent​​​(decorview->(appcompatActivity的windowCallbackWrap)->activity-》->PhonewWindow->​​dispatchTransformedTouchEvent​​​ DecorView->ViewGroup...然后各种​​dispatchTransformedTouchEvent​

19、最后贡献我写的完整 viewgroup到view的伪代码看完伪代码整个思路绝对会清晰很多,因为不会被多余代码干扰

​viewgroup​​的伪代码

package cn.qssq666.interrview;

import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.ViewGroup;

import java.util.ArrayList;

/**
* Created by qssq on 2018/5/2 qssq666@foxmail.com
*/
public class PseudocCodeViewGroup extends PseudocCodeView {

/**
* requestDisallowInterceptTouchEvent =true=mDISALLOW_INTERCEIT禁止拦截
*/
private boolean mDISALLOW_INTERCEIT = false;//是否调用

ViewGroup viewGroup;
private int mChildrenCount = 10;
private TouchTarget newTouchTarget;

private PseudocCodeView[] mChildren;
private TouchTarget mFirstTouchTarget;
private boolean alreadyDispatchedToNewTouchTarget;
private ArrayList<PseudocCodeView> preorderedList;

public boolean dispatchTouchEvent(MotionEvent ev) {
boolean intercepted = false;
if (mDISALLOW_INTERCEIT) {
intercepted = false;
} else {

intercepted = onInterceptTouchEvent(ev);
}

boolean handled = false;
if (!intercepted) {

final int childrenCount = mChildrenCount;
for (int i = childrenCount - 1; i >= 0; i--) {


preorderedList = null;//TODO 这里的意思不太理解
final boolean customOrder = preorderedList == null;// && isChildrenDrawingOrderEnabled();
int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
PseudocCodeView[] children = mChildren;
final PseudocCodeView child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
break;
}
if (dispatchTransformedTouchEvent(ev, false, child, 0)) {//调用子view的dispatchTouchEvent();
alreadyDispatchedToNewTouchTarget = true;
break;
}
}


}

if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view.
boolean canceled = true;
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
TouchTarget predecessor = target;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target = next;
continue;
}
}
predecessor = target;
target = next;
}


}
return handled;
}


protected boolean onInterceptTouchEvent(MotionEvent ev) {
return ev.getAction() == MotionEvent.ACTION_DOWN;
}

/**
* 调用自己的child view
*
* @param event
* @param cancel
* @param child
* @param desiredPointerIdBits
* @return
*/

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, PseudocCodeView child, int desiredPointerIdBits) {

final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

MotionEvent transformedEvent = MotionEvent.obtain(event);
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {

handled = child.dispatchTouchEvent(transformedEvent);
}

// Done.
transformedEvent.recycle();
return handled;

}


private TouchTarget getTouchTarget(@NonNull PseudocCodeView child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}

/**
* 获取以及验证预览视图,
*
* @param preorderedList
* @param children
* @param childIndex
* @return
*/

private static PseudocCodeView getAndVerifyPreorderedView(ArrayList<PseudocCodeView> preorderedList, PseudocCodeView[] children,
int childIndex) {
final PseudocCodeView child;
if (preorderedList != null) {
child = preorderedList.get(childIndex);
if (child == null) {
throw new RuntimeException("Invalid preorderedList contained null child at index "
+ childIndex);
}
} else {
child = children[childIndex];
}
return child;
}


private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
final int childIndex;
if (customOrder) {
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
if (childIndex1 >= childrenCount) {
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
+ "returned invalid index " + childIndex1
+ " (child count is " + childrenCount + ")");
}
childIndex = childIndex1;
} else {
childIndex = i;
}
return childIndex;
}

private int getChildDrawingOrder(int childrenCount, int i) {
return 0;//省略大量代码
}

private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
public PseudocCodeView child;
public int pointerIdBits;
public TouchTarget next;

private TouchTarget() {
}

public static TouchTarget obtain(@NonNull PseudocCodeView child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}

final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}


}
}

​view​​的伪代码

package cn.qssq666.interrview;

import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;

import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;

/**
* Created by qssq on 2018/5/2 qssq666@foxmail.com
*/
public class PseudocCodeView {


private ListenerInfo mListenerInfo;
private boolean ENABLE;
private boolean result;
private boolean CLICKABLE;
private boolean LONG_CLICKABLE;
private boolean CONTEXT_CLICKABLE;
private boolean mHasPerformedLongPress;
private boolean PFLAG_PRESSED;

public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& ENABLE
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

if (!result && onTouchEvent(event)) {
result = true;
}

return false;
}

protected boolean onTouchEvent(MotionEvent event) {

final float x = event.getX(); final float y = event.getY();
// if(DISABLED){reutn false;}

if (CLICKABLE || LONG_CLICKABLE || CONTEXT_CLICKABLE) {
int action = event.getAction();

switch (action) {
case MotionEvent.ACTION_UP:
boolean prepressed = true;
if (PFLAG_PRESSED || prepressed) {


if (!mHasPerformedLongPress) {

if (!mHasPerformedLongPress ) {
removeLongPressCallback();

}
performClick();
}


}
break;

case MotionEvent.ACTION_DOWN:

checkForLongClick(0, x, y);

break;

case MotionEvent.ACTION_CANCEL:
break;

case MotionEvent.ACTION_MOVE:
break;
}

return true;
}
return false;


}

public void performClick() {
//点击点击事件

}
CheckForLongPress mPendingCheckForLongPress=null;
public void checkForLongClick(int delayOffset,float x,float y){

if(mPendingCheckForLongPress!=null) {
mPendingCheckForLongPress = new CheckForLongPress();

}
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}





private final class CheckForLongPress implements Runnable {
private float mX;
private float mY;

@Override
public void run() {
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}

/**
* 长按事件触发
* @param mX
* @param mY
* @return
*/
private boolean performLongClick(float mX, float mY) {
return false;
}

public void setAnchor(float x, float y) {


}



public void postDelayed(Runnable runnable,int delayTime){
//TODO
}


private void removeLongPressCallback() {
if (mPendingCheckForLongPress != null) {
removeCallbacks(mPendingCheckForLongPress);
}
}

private void removeCallbacks(Runnable mPendingCheckForLongPress) {
//TOOD
}


static class ListenerInfo {
/**
* Listener used to dispatch focus change events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnFocusChangeListener mOnFocusChangeListener;

/**
* Listeners for layout change events.
*/
private ArrayList<View.OnLayoutChangeListener> mOnLayoutChangeListeners;

protected View.OnScrollChangeListener mOnScrollChangeListener;

/**
* Listeners for attach events.
*/
private CopyOnWriteArrayList<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;

/**
* Listener used to dispatch click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
public View.OnClickListener mOnClickListener;

/**
* Listener used to dispatch long click events.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnLongClickListener mOnLongClickListener;

/**
* Listener used to dispatch context click events. This field should be made private, so it
* is hidden from the SDK.
* {@hide}
*/
protected View.OnContextClickListener mOnContextClickListener;

/**
* Listener used to build the context menu.
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
protected View.OnCreateContextMenuListener mOnCreateContextMenuListener;

private View.OnKeyListener mOnKeyListener;

private PseudocCodeView.OnTouchListener mOnTouchListener;
// private View.OnTouchListener mOnTouchListener;

private View.OnHoverListener mOnHoverListener;

private View.OnGenericMotionListener mOnGenericMotionListener;

private View.OnDragListener mOnDragListener;

private View.OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;

View.OnApplyWindowInsetsListener mOnApplyWindowInsetsListener;
}


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(PseudocCodeView v, MotionEvent event);
}
}

最后

实际上代码还有很多很多,很多细节,包括嵌套滑动的兼容并没有分析,从这里的伪代码也能够让你明白,点击事件是在那个触摸方法里面触发的,是如何解决点击事件不生效的.。我是菜鸟情迁,我为自己代言

举报

相关推荐

0 条评论