学习的问题
首先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 你好
17、同级的view默认情况下不管是被同级的遮盖,都会触发事件 也就是说一个点击区域的所有子view都会响应点击事件
但是优先级是谁在上面谁先得到响应事件
所谓上面,就是xml声明最靠后的就是上面的view.
18、 dispatchTouchEvent的根源
从viewrootimp的inputState传递 ->然后传递给phonewindow里面的
decoreview的上层viewdispatchPointerEvent
(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);
}
}
最后
实际上代码还有很多很多,很多细节,包括嵌套滑动的兼容并没有分析,从这里的伪代码也能够让你明白,点击事件是在那个触摸方法里面触发的,是如何解决点击事件不生效的.。我是菜鸟情迁,我为自己代言