如何应对Android面试官->事件分发冲突与解决方案大揭秘

Android事件冲突的解决方案 一、预备知识 1.MotionEvent事件类型有哪些呢? ACTION_DOWN 手指首次触摸到屏幕时触发 ACTION_UP 手指离开屏幕时触发 ACTION_CANCEL 事件被上层拦截时触发!!! ACTION_MOVE 手指在屏幕上滑动时触发,会多次触发 2.需特别关注的view的事件有哪些呢? dispatchTouchEvent 用于事件分发或事件处理 【对于ViewGroup要先走分发流程,再走处理流程;对于View,只能走处理流程】 onInterceptTouchEvent 用于事件 阅读详情

前言

View的继承关系

Android 中的 View 的继承关系,View 用来处理事件,ViewGroup 用来分发事件;

事件的分发流程如下:

这个在上一章(嵌套滚动大揭秘)中有简单介绍;

View 的事件处理流程

onTouch 如何消费?onClick 如何消费?

我们可以先来看一个小 demo

mTvJustWatchTitle.setOnClickListener(new View.OnClickListener() {    
    @Override    
    public void onClick(View v) {   
        //
     }
});
mTvJustWatchTitle.setOnTouchListener(new View.OnTouchListener() {    
    @Override    
    public boolean onTouch(View v, MotionEvent event) {        
        return false;    
    }
});

当我们的 onTouch 方法 return false 的时候,我们发现,onClick 方法执行了,当 return true 的时候,onClick 方法却没有执行,那么这是为什么呢? onClick 为什么会与 onTouch 发生了冲突呢?

什么是事件冲突?

事件只有一个,多个人想要处理,如果处理的对象不是我们想要的对象,那么就被称为事件冲突!

我们进入 View 的 dispatchTouchEvent 方法看下,事件是怎么消费的,谁的优先级较高

public boolean dispatchTouchEvent(MotionEvent event) {
    // 省略部分代码
    ...
    // 
    if (onFilterTouchEventForSecurity(event)) {    
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {        
            result = true;    
        }    
        //noinspection SimplifiableIfStatement 
        // 因为我们调用了 setOnTouchEventListener 并实现了这个方法,那么这个mListenerInfo就一定不为空了,
        //   
        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;    
        }
    }    // 省略部分代码
    ...
    // 

}

也就是说,如果 onTouch 方法中 return true 的时候,这个 result = true 就会成立;如果 return false,这个 result = true 就不会成立,当 onTouch return false 的时候,我们接着进入 onTouchEvent 方法看下,可以看到,这个方法里面我们进行了事件的处理操作,里面实现了 MotionEvent 的 UP、DOWN、MOVE、CANCEL 等事件;

public boolean onTouchEvent(MotionEvent event) {
    //
    ...
    // 省略部分代码
    switch (action) {    
        case MotionEvent.ACTION_UP:
            //
            ...
            // 省略部分代码
            // 可以看到,onClick 的执行,是在 UP 事件的 performClickInternal 方法中
            if (!post(mPerformClick)) {    
                performClickInternal();
            }
        break;
    }
}

我们进入 performClickInternal 方法看下:

private boolean performClickInternal() {    
    // Must notify autofill manager before performing the click actions to avoid scenarios where    
    // the app has a click listener that changes the state of views the autofill service might    
    // be interested on.    
    notifyAutofillManagerOnClick();    
    return performClick();
}

方法调用了 performClick 方法,我们进入这个方法看一下:

public boolean performClick() {    
    // We still need to call this method to handle the cases where performClick() was called    
    // externally, instead of through performClickInternal()    
    notifyAutofillManagerOnClick();    
    final boolean result;    
    final ListenerInfo li = mListenerInfo;    
    if (li != null && li.mOnClickListener != null) {        
        playSoundEffect(SoundEffectConstants.CLICK); 
        // 可以看到,最终调用了我们实现的 onClick 方法;       
        li.mOnClickListener.onClick(this);        
        result = true;    
    } else {        
        result = false;    
    }    
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);    
    notifyEnterOrExitForAutoFillIfNeeded(true);    
    return result;
}

看到这里,我们也就能理解了,为什么 onTouch 方法 return false 的时候,onClick 才会执行的原因,因为 onTouch 方法先执行,如果 return true,则不会执行 onClick 方法了;也就是我们需要根据业务需求,设置不同的处理对象,来防止事件处理冲突;

View 的事件分发流程

ACTION_DOWN 事件分发流程

我们前面有提到,事件的分发是在 ViewGroup 中进行的,我们进入 ViewGroup 的 dispatchTouchEvent 方法看一下:

我们以 ViewPager 嵌套 RecyclerView 为例子:

如果我们重写了 ViewPager 的 onInterceptTouchEvent 方法,并 return true,则表明由 ViewPager 拦截此次事件;

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 省略部分代码
    ...
    // 
    if (onFilterTouchEventForSecurity(ev)) {
        final boolean intercepted;
        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;
        }
        // 
    }
    // 
}

首先会通过 onInterceptTouchEvent 判断是不是要拦截,如果拦截这个事件,也就是 intercepted 就会等于 true;则会进入 dispatchTransformedTouchEvent

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 省略部分代 
    ...    
    // 省略部分代码 

    if (mFirstTouchTarget == null) {    
        // No touch targets so treat this as an ordinary view.    
        handled = dispatchTransformedTouchEvent(ev, canceled, null,            
            TouchTarget.ALL_POINTER_IDS);
    }

    // 省略部分代码     
    ...   
    // 省略部分代码 
}

我们进入这个 dispatchTransformedTouchEvent 方法看下:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        
        View child, int desiredPointerIdBits) {
    // 省略部分代码
    ...
    // 
    if (child == null) {    
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {    
        final float offsetX = mScrollX - child.mLeft;    
        final float offsetY = mScrollY - child.mTop;    
        transformedEvent.offsetLocation(offsetX, offsetY);    
        if (! child.hasIdentityMatrix()) {        
            transformedEvent.transform(child.getInverseMatrix());    
        }    
        handled = child.dispatchTouchEvent(transformedEvent);}
}

首次的时候,这个 child 为空,进入了super.dispatchTouchEvent(transformedEvent)这个方法,而 ViewGroup 的父类是 View,所以进入了 View 的 dispatchTouchEvent 方法,进行了事件的处理逻辑;

如果 onInterceptTouchEvent  return true,则这个 ViewGroup 消费当前事件的流程也就整体串联了起来,当前 ViewGroup 的后续 MOVE、UP、CANCEL 等就会在当前 ViewGroup 消费掉;

我们接着返回去看 不拦截 的 case:

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 
    ...
    // 省略部分代码
    if (!canceled && !intercepted) {
        //
        ...
        // 省略部分代码
        if (actionMasked == MotionEvent.ACTION_DOWN        
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)        
            || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            //
            ...
            // 省略部分代码
            if (newTouchTarget == null && childrenCount != 0) {
                // 给当前 ViewGroup 的子 View 进行一个排序
                final ArrayList<View> preorderedList = buildTouchDispatchChildList();                
            }
        }
    }
}

不拦截,也就是 onInterceptTouchEvent return false,intercepted 就会等于 false,则先判断这个 ViewGroup 有没有子 View,并获取子 View 的个数,以及调用 buildTouchDispatchChildList 对子 View 进行排序,排序规则如下:

根据 xml 中声明的顺序,倒序插入到这个 ArrayList 中;

然后获取的时候,倒序取出,也就是最终还是先写的,先取出来 ;

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

倒序取出来之后,进行相关逻辑处理:

for (int i = childrenCount - 1; i >= 0; i--) {    
    final int childIndex = getAndVerifyPreorderedIndex(            
            childrenCount, i, customOrder);    
    final View child = getAndVerifyPreorderedView(            
            preorderedList, children, childIndex);
    // 
    if (!child.canReceivePointerEvents()        
            || !isTransformedTouchPointInView(x, y, child, null)) {    
        ev.setTargetAccessibilityFocus(false);    
        continue;
    }
}

取出 View 之后,会进行能不能接受事件处理的判断,第一个 canReceivePointerEvents  方法中首先会判断当前 View 是不是可见的,然后判断当前 View 是不是有动画;

第二个 isTransformedTouchPointInView 方法中判断触摸的地方是不是在当前 View 上;

然后调用 dispatchTransformedTouchEvent 进行事件的分发

for (int i = childrenCount - 1; i >= 0; i--) {    
    final int childIndex = getAndVerifyPreorderedIndex(            
            childrenCount, i, customOrder);    
    final View child = getAndVerifyPreorderedView(            
            preorderedList, children, childIndex);
    // 
    if (!child.canReceivePointerEvents()        
            || !isTransformedTouchPointInView(x, y, child, null)) {    
        ev.setTargetAccessibilityFocus(false);    
        continue;
    }
    // 分发处理事件
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        
    }
}

这里会进行事件的分发  dispatchTransformedTouchEvent 由于我们前面已经将 child 获取到了,所以这个这个方法中的第三个参数 child 就不会为空;就会进入  dispatchTransformedTouchEvent 这个方法的 else 逻辑里面

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        
        View child, int desiredPointerIdBits) {
    if (child == null) {    
        handled = super.dispatchTouchEvent(transformedEvent);
    } else {    
        final float offsetX = mScrollX - child.mLeft;    
        final float offsetY = mScrollY - child.mTop;    
        transformedEvent.offsetLocation(offsetX, offsetY);    
        if (! child.hasIdentityMatrix()) {        
            transformedEvent.transform(child.getInverseMatrix());    
        }    
        handled = child.dispatchTouchEvent(transformedEvent);
    }
}

这里 else 中,如果子 View 还是 ViewGroup,那么就会进入下一轮的递归调用,直到分发到 View 进行事件的消费;

如果 dispatchTransformedTouchEvent 返回 false,则开始下一轮的循环;

如果 dispatchTransformedTouchEvent 返回 true,则会进入事件处理流程,同时 break 循环;

if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {    
    // Child wants to receive touch within its bounds.    
    mLastTouchDownTime = ev.getDownTime();    
    if (preorderedList != null) {        
        // childIndex points into presorted list, find original index        
        for (int j = 0; j < childrenCount; j++) {            
            if (children[childIndex] == mChildren[j]) {                
                mLastTouchDownIndex = j;                
                break;            
            }        
        }    
    } else {        
        mLastTouchDownIndex = childIndex;    
    }    
    mLastTouchDownX = ev.getX();    
    mLastTouchDownY = ev.getY();    
    newTouchTarget = addTouchTarget(child, idBitsToAssign);    
    alreadyDispatchedToNewTouchTarget = true;    
    break;
}

最终会调用 addTouchTarget  将当前 View 加入到一个 View 调用链中,并将 mFirstTouchTarget  赋值;

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {    
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);    
    target.next = mFirstTouchTarget;    
    mFirstTouchTarget = target;    
    return target;
}

经过 addTouchTarget 之后,target.next = null;mFirstTouchTarget = target;mTouchTarget = target;

也就是 mFirstTouchTarget == mTouchTarget  !=  null;循环终止后,接着进入下面不拦截的流程,也就是 else 流程

if (mFirstTouchTarget == null) {    
    // No touch targets so treat this as an ordinary view.    
    handled = dispatchTransformedTouchEvent(ev, canceled, null,            
            TouchTarget.ALL_POINTER_IDS);
} else {    
    // Dispatch to touch targets, excluding the new touch target if we already    
    // dispatched to it.  Cancel touch targets if necessary.    
    TouchTarget predecessor = null;    
    TouchTarget target = mFirstTouchTarget;    
    while (target != null) {        
        final TouchTarget next = target.next;        
        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {            
            handled = true;        
        } else {            
            final boolean cancelChild = resetCancelNextUpFlag(target.child)                    
                    || intercepted;            
            if (dispatchTransformedTouchEvent(ev, cancelChild,                    
                    target.child, target.pointerIdBits)) {                
                handled = true;            
            }            
            if (cancelChild) {                
                if (predecessor == null) {                    
                    mFirstTouchTarget = next;                
                } else {                    
                    predecessor.next = next;                
                }                
                target.recycle();                
                target = next;                
                continue;            
            }        
        }        
        predecessor = target;        
        target = next;    
    }
}

target  != null  由 target.next = null 并且 final TouchTarget next = target.next  可以看到,while 循环只会执行一次;

又因为 alreadyDispatchedToNewTouchTarget && target == newTouchTarget 成立,所以 handled = true;

else 的逻辑,是针对的多点触控的逻辑,不在本次分析范围内,感兴趣的可以自己看下;

如果所有的子 View 都不处理,则相当于是自身进行了拦截,就会进入拦截处理的逻辑;

if (mFirstTouchTarget == null) {    
    // No touch targets so treat this as an ordinary view.    
    handled = dispatchTransformedTouchEvent(ev, canceled, null,            
        TouchTarget.ALL_POINTER_IDS);
}

ACTION_MOVE 事件分发流程

Move 事件也分拦截和不拦截的情况;

我们先看不拦截的情况,来看下 MOVE 事件是怎么分发的,同样,我们进入 dispatchTouchEvent 方法看下:
其实可以看到,两个 if 都进行了 DOWN 的判断,所以 MOVE 事件不会进入到这里;

所以,逻辑直接就进入到了 else  的 while 循环里面,最终进入的就是 dispatchTransformedTouchEvent

可以看到,MOVE 事件,不会进行子 View 的问询操作,而是直接分发到目标子 View;

我们来看下拦截的情况,MOVE 事件冲突处理;

事件冲突处理其实可以分为 内部拦截法 和 外部拦截法;

内部拦截法,子 View 处理滑动事件;外部拦截法,父 View 处理滑动事件;

内部拦截法

子 View 处理滑动事件的时候,需要让父 View 不进行事件的拦截,而父 View 在进行拦截之前会有一个判断:

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

也就是说,如果我们让 disallowIntercept = true 的话,那么,父 View 就不会拦截子 View 的事件,而设置这个的值,是通过  requestDisallowInterceptTouchEvent 来实现的;

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {    
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {        
        // We're already in this state, assume our ancestors are too        
        return;    
    }    
    if (disallowIntercept) {        
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;    
    } else {        
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;    
    }    
    // Pass it up to our parent    
    if (mParent != null) {        
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);    
    }
}

也就是说内部拦截法中,子 View 中在 DOWN 事件的时候调用 getParent().requestDisallowInterceptTouchEvent(true),请求父 View 不拦截事件;在 MOVE 事件的时候根据滑动方向,如果需要父 View 处理,则调用 getParent().requestDisallowInterceptTouchEvent(false),请求父 View 拦截事件;

举个例子,我们用 ViewPager 来嵌套一个 RecyclerView 来看下效果,也就是我们最终的代码实现如下:

ViewPager 实现如下:

public class BadViewPager extends ViewPager {

    private int mLastX, mLastY;

    public BadViewPager(@NonNull Context context) {
        super(context);
    }

    public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    // 外部拦截法:父容器处理冲突
    // 我想要把事件分发给谁就分发给谁
    // ViewPager 进行事件的拦截
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
       return true;
    }
}

RecyclerView 实现如下:

public class MyRecyclerView extends RecyclerView {

    public RecyclerView(Context context) {        super(context);
    }

    public RecyclerView(Context context, AttributeSet attrs) {        super(context, attrs);
    }

    // 内部拦截法:子view处理事件冲突
   private int mLastX, mLastY;

   @Override
   public boolean dispatchTouchEvent(MotionEvent event) {
       int x = (int) event.getX();
       int y = (int) event.getY();

       switch (event.getAction()) {
           case MotionEvent.ACTION_DOWN: {
               // 在 DOWN 的时候,要求 ViewPager 不拦截事件
               getParent().requestDisallowInterceptTouchEvent(true);
               break;
           }
           case MotionEvent.ACTION_MOVE: {
               int deltaX = x - mLastX;
               int deltaY = y - mLastY;
               if (Math.abs(deltaX) > Math.abs(deltaY)) {
                   getParent().requestDisallowInterceptTouchEvent(false);
               }
               break;
           }
           case MotionEvent.ACTION_UP: {
               break;
           }
           default:
               break;
       }

       mLastX = x;
       mLastY = y;
       return super.dispatchTouchEvent(event);
   }
}

我们运行看下效果:

可以看到,并没有达到我们期望的效果,RecyclerView 并不能上下滑动,只能 ViewPager 的左右滑动,事件还是冲突了,那么问题出在哪里呢?我们接着分析 dispatchTouchEvent 源码;

我们发现,在源码中,有这么一段逻辑:

// Handle an initial down.i
if (actionMasked == MotionEvent.ACTION_DOWN) {    
    // Throw away all previous state when starting a new touch gesture.    
    // The framework may have dropped the up or cancel event for the previous gesture    
    // due to an app switch, ANR, or some other state change.    
    cancelAndClearTouchTargets(ev);    
    resetTouchState();
}

如果是 DOWN 事件,会把所有的参数状态进行重置,包括了 mGroupFlags 也就是说,我们在 RecyclerView 中设置的 mGroupFlags 并没有生效,在 DWON 的时候被重置了;

也就是说,DOWN 事件中

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

disallowIntercept 一定是 false;

if (!disallowIntercept) {    
    intercepted = onInterceptTouchEvent(ev);    
    ev.setAction(action); // restore action in case it was changed
}

这段逻辑,一定走了进来,也就是 intercepted 是 true,事件进行了拦截,因为我们前面把 ViewPager 的 onInterceptTouchEvent 方法返回了 true;根据前面的分析,ViewPager 把 DOWN 事件进行了拦截,也就不会分发给子 View(RecyclerView)了,所以,RecyclerView 就能竖向滑动了,如果 RecyclerView 想拿到竖向滑动的事件,那么父 View(ViewPager)对 DWON 事件不能进行拦截,修改如下:

public class BadViewPager extends ViewPager {

    private int mLastX, mLastY;

    public BadViewPager(@NonNull Context context) {
        super(context);
    }

    public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    // 外部拦截法:父容器处理冲突
    // 我想要把事件分发给谁就分发给谁
    // ViewPager 进行事件的拦截
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
       // 对 DOWN 事件不进行拦截,交给子 View(RecyclerView) 处理;
       if(event.getAction() == MotionEvent.ACTION_DOWN) {
           super.onInterceptTouchEvent(event);
           return false; 
       }
       return true;
    }
}

ViewPager 不拦截 MOVE 事件,当 RecyclerView 拿到 MOVE 事件的时候,进行左右滑动的时候,要求父 View(ViewPager)拦截事件自己处理,通过  getParent().requestDisallowInterceptTouchEvent(false);

ViewPager 拦截了 MOVE 事件,intercepted = true,我们看下 ViewPager 怎么处理 MOVE 事件:

ViewPager 执行到了 else 的逻辑中,因为 intercepted = true,所以 cancelChild 也等于 true;

diapatchTransformedTouchEvent 中 cancelChild 传入的就是 true,我们进入这个方法看下:

可以看到,子 View (RecyclerView)的 MOVE 事件被替换成取消了,然后调用子 View (RecyclerView)的 dispatchTouchEvent 方法进行事件的处理,RecyclerView 最终调用的是 return super.dispatchTouchEvent(),所以 diapatchTransformedTouchEvent 最终返回的也是 true,所以 handled = true;

因为 cancelChild = true;所以 mFirstTouchTarget 被重置成了 null,前面有分析 next 其实空的原因;

那么当第二个 MOVE 事件进来的时候,父 View(ViewPager)直接进行拦截,intercepted = true;

接着进入了事件分发处理流程 dispatchTransformedTouchEvent 方法

接下来的 MOVE 事件,就被父 View(ViewPager)拦截并处理了;

我们运行看下效果:

我们看到,可以上下滑动了,RecyclerView 拿到了 MOVE 事件;

外部拦截法

外部拦截法就比较简单了,判断父 View(ViewPager)的滑动方向和触摸事件,进行处理即可,子 View(RecyclerView)不用做任何处理;

public class BadViewPager extends ViewPager {    
    private int mLastX, mLastY;    
    public BadViewPager(@NonNull Context context) {        
        super(context);    
    }    
    public BadViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {        
        super(context, attrs);    
    }    
    // 外部拦截法:父容器处理冲突    
    // 我想要把事件分发给谁就分发给谁    
    @Override    
    public boolean onInterceptTouchEvent(MotionEvent event) {                
        int x = (int) event.getX();        
        int y = (int) event.getY();        
        switch (event.getAction()) {            
            case MotionEvent.ACTION_DOWN: {                
                mLastX = (int) event.getX();                
                mLastY = (int) event.getY();                
            break;            
        }            
        case MotionEvent.ACTION_MOVE: {                
            int deltaX = x - mLastX;                
            int deltaY = y - mLastY;
            // 水平滑动的时候,自己处理,垂直滑动的时候,不处理                
            if (Math.abs(deltaX) > Math.abs(deltaY)) {                    
                return true;                
            }                
            break;            
        }            
        case MotionEvent.ACTION_UP: {                
            break;            
        }            
        default:                
            break;        
        }        
        return super.onInterceptTouchEvent(event);    
    }
}

简历润色

简历上可写:深度理解事件分发的原理,可解决复杂滑动冲突;

下一章预告

自定义 ViewPager 炫酷指示器~

欢迎三连

来都来了,点个关注,点个赞吧,你的支持是我最大的动力~~~感谢!

Android进阶宝典—事件冲突的解决方法 所以如果当前View消费了DOWN事件,那么返回值为true,也就是dispatchTransformedTouchEvent返回了true,那么会进入下面代码块中。 阅读详情

相关推荐

Android事件冲突原理及解决方法

Android事件冲突原理和解决方法

是阿超 1372

Android 事件分发:为什么有时候会出现事件冲突?事件的顺序是如何的?出现事件冲突如何解决呢?比如为什么左右可以滑动,而上下却不行?

事件分发的源码第一次看比较复杂,需要反复观看,才能理解,并且结合控件案例去测试,效果会更好。

前期后期 1331

Android 最新各种下拉刷新,同时解决事件冲突问题

非常多的下拉刷新上拉加载更多的效果,支持各种View!包含源码供学习,导入时,注意依赖关系

Android 必懂系列 —— 事件冲突及解决方案(源码分析)

还不知道事件分发的朋友可以先去看看我的另一篇事件分发后再来看时间冲突——事件分发 【1】什么是时间冲突? 简单来说就是,一个动作(down,move等)只能由一个View或者ViewGroup处理,而有时我们希望ViewA处理这个事件,但是却是ViewB处理的这个事件,这时我们就说产生了时间冲突。 【2】解决事件冲突的常用方案 注意:解决事件冲突只能在Move动作的时候 ①外部处理 外部处理就是在希望处理的子View的父容器中处理 ②内部处理 直接在希望处理的容器中处理 【3】先来过一遍Move的

zjcoming 3834

Android滑动事件冲突的解决方法

滑动是Android中不可缺少的一部分,多个滑动必然会产生冲突,比如我们最常见的是ScrollView中嵌套了ListView,一般做法是计算出ListView的总高度,这样就不用去滑动ListView了。又比如一个ViewPager嵌套Fragment,Fragment里面又有ListView,这原本是有滑动冲突的,但是ViewPager内部去帮我们解决了这种冲突。那如果我们要自己解决冲突又该怎么办呢。 下面有两种方式来解决: 外部拦截法 外部拦截法是指在有点击事件时都要经过父容器,那么在父容器时如果需要拦截就拦截自己处理,不需要则传递给下一层进行处理,下面看个例子: 首先定义一个水平滑动

Android滑动事件冲突详解(一)

首先,我们假设这样一个场景:一个ViewPager里面嵌套一个ViewPager,内部滑动方向和外部滑动方向一样时,该怎么解决这一冲突呢? 针对滑动冲突这里给出两种解决方案:外部拦截法,内部拦截法。 外部拦截法 外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此拦截事件,就拦截,不需要就不拦截,这种方法比较符合点击事件的分发机制。这种方法代码如下: @Override public boolean onInterceptTouchEvent(MotionEvent ev) { ... boolean intercepted=false; swit

android事件冲突

事件冲突实例 ViewPager RecyclerView ScrollView

zhangyangxiongxin的专栏 798

虚拟机安装Centos8.5

Centos8.5

weixin_63604047的博客 957

如何应对Android面试官-> NestedScrollView 嵌套滑动机制详解,手写 NestedScrollView 核心实现

NestedScrollView嵌套滑动机制详解,手写NestedScrollView核心实现,大厂面试官助你轻松应对嵌套滑动连环炮

IT_Android的博客 4487

「免费开源」从 OCR 识别到结构化输出:合同文本高效比对的完整解决方案!

本文将系统拆解这一解决方案的技术逻辑、实施步骤与落地价值,为企业合同管理数字化转型提供实操指南。字段提取:自动识别并提取预设的关键字段(如 “甲方”“乙方”“合同金额”“履约期限” 等),若遇到模糊表述(如 “付款时间为合同签订后 30 个工作日内”),系统可自动解析为 “履约期限:合同签订日 + 30 工作日”;预设结构化字段:根据合同类型(如采购合同、服务合同、租赁合同),定义需提取的关键字段(如 “甲方名称”“乙方名称”“合同金额”“有效期起 / 止日期”“履约地点”“违约金比例” 等);

qxsz_yyh的博客 835

Ubuntu下NVIDIA驱动安装全攻略:从报错到完美运行nvidia-smi的完整流程

本文提供了在Ubuntu系统中安装NVIDIA驱动的完整解决方案,重点解决因驱动版本冲突、内核模块未加载或Secure Boot安全启动导致的`nvidia-smi`报错问题。通过彻底清理旧驱动、处理Secure Boot、选择兼容版本并正确安装,最终确保驱动完美运行。

weixin_29196891的博客 364

如何应对Android面试官->嵌套滚动原理大揭秘,实战京东首页二级联动

本章节主要从 嵌套滑动、滑动冲突方案解决来入手,这里不是基于协调者布局;

IT_Android的博客 1520

ComfyUI整合包、秋叶ComfyUI、Florence节点报错下载并加载Florence2模型No module named ‘timm.layers‘、ComfyUI报错解决

步骤操作1打开2地址栏输入cmd并回车3输入命令:4安装完成后重新打开 ComfyUI,执行工作流,即可解决报错。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 1240

大龄程序员的2024年1月总结:转岗、热榜、分享、健康、技术文章

一月总结

IT_Android的博客 1243

用 Joule 点燃 ABAP 开发者的舞台感:SAP BTP 上的 ABAP AI 能力实战解读

摘要:SAP推出的Joule for developers和ABAP AI功能为开发者提供了智能辅助工具,旨在优化ABAP开发流程。主要功能包括上下文感知的代码预测补全、代码解释、单元测试生成和开发问题实时解答。这些能力特别适合应对Cloud环境下ABAP开发的复杂约束,如Clean Core原则和受限语法范围。通过减少重复劳动和提升代码理解效率,该工具帮助开发者将注意力集中在业务逻辑而非技术细节上。随着企业加速向S/4HANA迁移,这些AI功能将在遗留代码改造和云适配中发挥更大作用,建议团队将其纳入工程体

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 296

SAP_PP模块中MRP中信号灯简介

一、信号灯的作用二、常见的信号灯颜色及含义三、信号灯的来源四、常见场景示例示例1:物料满足所有需求示例2:生产订单已延迟示例3:缺料或未产生计划订单五、相关事务码参考六、如何应对红色或黄色信号灯?七、前台操作说明在 SAP PP 模块中,**MRP(物料需求计划)运行结果中的“信号灯”是一种非常直观的可视化提示工具,用于帮助用户快速识别物料计划是否存在异常或需要关注的问题。这些信号灯通常出现在MD04(库存/需求清单)、MD05(单物料MRP清单)、MD07(MRP列表)**等事务代码的界面中。

我小时候很黑的博客 626

生活生下来活下去

最近一直在追的一部小说,确切的说是追的一位作者『黑色枷锁』,我从2015年开始就在看他的小说,第一部小说『我的老千生涯』(已完结),第二部小说『我的老千江湖』(已完结),第三部小说『新民间老八门前传』(已2完结),第四部小说『新民间老八门』(连载中),小说写的很有意思,写江湖,写微表情,写人情世故,作者将自己的一些亲身经历融合到了故事情节中,里面也有很多让我觉得有意思的语录,摘抄一些。后续如果产品上使用了 Compose 的技术,我也会告诉大家来体验一下;希望接下来的日子里,病毒远离我!

452

华为OD机试 双机位C卷:微服务的集成测试 (C/C++/Py/Java/JS/Go)

华为OD机试 双机位C卷 :微服务的集成测试 ,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解。华为OD机试 微服务的集成测试 , 华为OD 微服务的集成测试

qq_45776114的博客 2066

Android的事件分发机制以及滑动冲突的解决

声明: 本文主要涉及VIew的事件分发与滑动冲突的解决,关于View的事件分发流程的部分内容参考自: Android事件分发机制详解:史上最全面、最易懂 个人感觉该文较全面,总结也很好,但略显冗长,且源码基于Android5.0以前的版本。本文中简化了核心原理,再补充了一些必要知识点,并使用最新的源码重新做了分析。 基础知识 事件分发的对象 MontionEvent...

未子涵的博客 3477

YOLOv11改进策略【Conv和Transformer】| IJCAI 2024 利用FreqFormer中的SFA 空间 - 频率注意力和CTA 通道转置注意力 二次改进C3k2

在FreqFormer模型中,SFA(Spatial-Frequency Attention)和CTA(Channel Transposed Attention)模块是关键组成部分,它们针对传统Transformer在图像超分辨率任务中的不足而设计,旨在更好地恢复高频细节,提升模型性能。

Limiiiing的博客 758

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Android滑动冲突解决方案内外部拦截法及原理

本文接上篇Android滑动冲突一内部拦截外部拦截简介 一 ViewPager嵌套ListView的滑动冲突,内部拦截法为何ViewPager的onInterceptTouchEvent要做判断而不是直接返回true? 我们重温下Android事件分发二之ViewGroup如何处理事件中ViewGroup事件分发方法的源码。注意这个ViewGroup对应到我们例子的ViewPager class:ViewGroup: @Override public boolean dispa.

LJ_GOD的博客 5976

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

事件冲突有那几种?有哪些解决方式?

在Android开发中,事件冲突通常发生在多个View之间,特别是当一个ViewGroup包含了多个子View时。这些冲突可能源于不同的触摸或滑动操作,导致系统不确定应该将事件传递给哪个View来处理。事件冲突主要分为以下几种类型:外部滑动方向和内部滑动的方向不一致:外部滑动方向和内部滑动的方向一致:组合冲突:为了解决Android中的事件冲突问题,可以采取以下几种方法:外部拦截法:内部拦截法:使用方法:设置事件优先级:自定义ViewGroup:制定合适的滑动策略:分层处理:综上所述,解决Android事件

qq_43664361的博客 601

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Android中事件分发中的冲突处理

用于事件的分发,所有的触摸事件都要经过这个方法。:用于判断是否拦截某个事件,只有ViewGroup才有这个方法。:用于处理触摸事件。,即事件从Activity开始,经过Window传递到顶级ViewGroup,然后逐层向下传递,直到找到合适的View处理该事件。通过自定义控件来处理特定的冲突场景。mLastY = y;break;// 如果横向移动距离大于纵向移动距离,则不拦截事件break;

分享技术和日常 905

Android开发:解决父子视图点击事件冲突问题

本文针对Android开发中父子视图点击事件冲突的问题,提出了两种解决策略。首先,通过重写`onTouchEvent`方法,实现自定义触摸事件处理逻辑,确保父视图在子视图被点击时也能接收到触摸事件。其次,利用Android的事件分发机制,将子视图的`clickable`属性设为`false`,使点击事件沿事件分发链传递至父视图。这两种方法都能有效解决父子视图点击事件冲突问题,确保应用能根据实际需求正确响应用户的操作。

iwork168的博客 488

Android高级UI篇,事件分发机制及冲突的原因与解决方案

Android事件分发机制、事件冲突解决

qq_27450983的博客 1024

根据热门面试题分析Android事件分发机制(二)---事件冲突分析处理

事件冲突一般发生在父view和子view的组合中,比如说viewpager和listview的组合中。产生事件冲突的原因是一个事件(down,up,move)到来时,有时候我们希望是Aview处理,但是却是Bview处理。这样就导致了我们想处理事件的view收不到事件,不想处理事件的view收到了事件。而解决冲突就是我们通过viewGroup的onInterceptTouchEvent()方法去合理的分发事件,让想处理事件的view收到事件,不想处理的view躺平啥也不做。...

知识应该共享,创造才应收费 620
上一篇: Jetpack Compose -> 声明式UI & Modifier
下一篇: 如何应对Android面试官->文字中心绘制和颜色渐变,实战头条炫酷ViewPager指示器
老A说
博客等级 码龄14年 1181粉丝 85原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值