Explicación detallada del código fuente del proceso del mecanismo de distribución de eventos de Android

La Vista en Android es una estructura de árbol y las Vistas pueden superponerse. Cuando hay varias Vistas que pueden responder al lugar en el que se hizo clic, ¿quién debería ser este evento de clic? Para resolver este problema, hay un mecanismo de distribución de eventos.

1. Concepto

1. ¿Qué es un evento?

Cada interacción del usuario con la interfaz en Android, haga clic, mantenga presionada, mueva, levante, etc. es un evento.
Al mismo tiempo, cada vez que toca la pantalla para salir de la pantalla se denomina secuencia de eventos . El evento anterior se llama el predecesor del evento siguiente.

MotionEvent:
en Android, cada evento se encapsula como un objeto MotionEvent, que tiene diferentes tipos:
se introducen cuatro tipos comúnmente utilizados en el desarrollo.

ACTION_DOWN: indica que el dedo toca la pantalla, si y solo si el dedo toca la pantalla (presionado, no movido, no levantado)
ACTION_MOVE: el dedo se mueve en la pantalla
ACTION_UP: el dedo sale de la pantalla

ACTION_CANCEL:
este evento es especial. La condición de activación es cuando una vista recibe un evento predecesor, pero el control principal intercepta el siguiente evento. En este momento, la vista recibirá un evento ACTION_CANCEL.
Por ejemplo: coloque un botón en la vista de desplazamiento, cuando se presiona, ya que se puede hacer clic en el botón de forma predeterminada, el botón consumirá el evento ACTION_DOWN. Al arrastrarlo, el evento ACTION_MOVE será interceptado por su control principal. En este momento, Button recibirá un evento ACTION_CANCEL

2. ¿Qué es la distribución de eventos?

Cada evento se transfiere desde la pantalla a cada vista, y una vista determinada controla todo el proceso de manejo de eventos (eventos de consumo) o ignorar eventos (eventos que no consumen). Llamado el mecanismo de distribución de eventos.

Pase la relación jerárquica:
Actividad-> Ventana-> DecorView-> ViewGroup-> View

La actividad contiene un objeto PhoneWindow, PhoneWindow es la única subclase de Window, cada objeto PhoneWindow contiene un DecorView, DecorView hereda de ViewGroup.

Segundo, el código fuente detallado relacionado con el proceso.

1 、 Actividad

Diagrama de flujo:

Proceso de distribución de eventos en actividad

Código fuente de la parte del proceso principal de la actividad:

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//该方法是一个空的方法,在每一个事件序列一开始会进行调用
        }
        if (getWindow().superDispatchTouchEvent(ev)) {//从这里正式进入事件分发流程
            return true;//表示事件被消费:结束
        }
        return onTouchEvent(ev);//调用Activity的 onTouchEvent 方法并返回结果:结束
    }
    .....
    
     public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {//允许点击空白部分消失,且点击了window之外
            finish();
            return true;
        }

        return false;
    }

Parte del código fuente del proceso principal de Window:

//该方法在Window类里面,在Activity  onTouchEvent()方法里被调用
    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
        表示:支持点击空白部分消失、持有DecorView对象、点击的是当前window持有DecorView之外
            return true;
        }
        return false;
    }

getWindow (). superDispatchTouchEvent (ev):
call-> superDispatchTouchEvent (ev) en PhoneWindow, PhoneWindow call-> superDispatchTouchEvent (ev) en DecorView, porque DecorView hereda de ViewGroup Finalmente, se llama al método dispatchTouchEvent (ev) en ViewGroup.

2 、 ViewGroup :

Diagrama de flujo:

Proceso de distribución de eventos de ViewGroup

Parte del código fuente del proceso principal:

ViewGroup: dispatchTouchEvent () hace principalmente tres cosas
1. Para juzgar si el evento necesita ser interceptado
2. Encuentre la vista en la que el usuario hizo clic en el viewGroup actual
3. Distribuya el evento a la vista

    public boolean dispatchTouchEvent(MotionEvent ev) {
                   ...
                   
        //从这里开始
        boolean handled = false;//定义为整个方法返回值
        if (onFilterTouchEventForSecurity(ev)) {是否符合安全策略,该方法在view中
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            
            if (actionMasked == MotionEvent.ACTION_DOWN) {//down事件,全部事件的开始
            
            //当开始一个新的触摸手势时,扔掉所有以前的状态
                cancelAndClearTouchTargets(ev);//清除所有触摸事件
                resetTouchState();//重置状态
            }

            final boolean intercepted;//是否拦截的判断
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {//该值当事件被子view消费后被赋值
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);//一般情况下直接返回false,源码在下面
                    ev.setAction(action); // 恢复操作
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
                      
                      ...
                      
            // 检查是否是取消事件
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
                    
                     ...
                     
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {//不是取消事件且不拦截
            //是按下事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                        
                    final int actionIndex = ev.getActionIndex(); // 事件索引
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    //清除早期触摸事件
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;//子view数量
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // 获取所有能接收事件的子view.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        //是否自定义view绘制顺序
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //从前到后遍历子view
                        for (int i = childrenCount - 1; i >= 0; i--) {
                        //获取view的真实索引,解释在下方
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                                    //根据索引获取目标view
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                                         ...
                                         
                            //当前view是否能接受事件,当前view是否在该事件点击范围之内
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // 表示当前子view获取到了触摸事件
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            
                            //在dispatchTransformedTouchEvent方法中进行下层view的分发
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                   
                                   .....
                                   
                                //一旦返回为true,则代表事件被子view消费
                                //addTouchTarget 在方法中为mFirstTouchTarget赋值
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        //循环结束
                        if (preorderedList != null) preorderedList.clear();
                    }

                    .....
                }
            }
            
            

            if (mFirstTouchTarget == null) {
                // 依然没有子view去处理事件
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                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;
                }
            } 
            
            .......
            
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;//最后返回
    }
    
    
    
    
    
    //是否拦截事件
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    //是鼠标事件且点击了左键,正常手机操作不会出现这样情况,故返回false
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }
    
    
    
    
    
    //获取子view的真实索引
    private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
        final int childIndex;
        if (customOrder) {//是否自定义
        //该方法需要在自定义绘制顺序时去实现,否则默认返回i
            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;
    }
    
    
    
    
    
    //根据索引获取目标view
    private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
            int childIndex) {
        final View 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;
    }
    
    
    
    
    //进行下层view的分发,主流程相关部分代码
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        //是否是取消事件
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
            //调用的是View的dispatchTouchEvent
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        .........
        
        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);
        }

        // 重置
        transformedEvent.recycle();
        return handled;
    }

Métodos relacionados en Vista:

//是否符合安全策略
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //表示接收此事件部分的窗口被完全遮挡,不处于顶部
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            return false;
        }
        return true;
    }

3 、 Ver

Diagrama de flujo:

Ver proceso de distribución de eventos

Análisis del código fuente del proceso principal:

public boolean dispatchTouchEvent(MotionEvent event) {
        if (event.isTargetAccessibilityFocus()) {
            // 没有焦点,不能获取到事件
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // 我们有焦点,得到事件,然后使用正常的事件调度。
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        ...

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 停止滚动
            stopNestedScroll();
        }

        //以下是在view中事件分发的核心逻辑
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                //鼠标拖动
                result = true;
            }
            //
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                //注册过OnTouchListener监听,且调用的onTouch返回true
                //注:OnTouchListener监听在此时被调用
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                //没有注册过OnTouchListener监听或者onTouch返回false、调用onTouchEvent返回true
                //注:onTouchEvent在此时被调用
                result = true;
            }
        }

        ...

        return result;
    }
    
    


    //该方法中调用mOnClickListener
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

//是否可点击
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            return clickable;//如果设置过禁止点击,直接返回可点击状态
        }

        //是否有点击事件代理
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {//内部为默认处理逻辑
                case MotionEvent.ACTION_UP:

        ...

                    if (!focusTaken) {

                        if (!post(mPerformClick)) {
                            performClickInternal();//在此方法中调用mOnClickListener监听
                        }
                    }


        ...
                    break;

        ...

            }

            return true;
        }

        return false;
    }

Cosas que hacer en la vista:
1. Determine si hay foco
2. Determine si es un evento del mouse
3. Determine si hay monitoreo OnTouchListener y llame al método onTouch de la interfaz de monitoreo, llame al método onTouchEvent, determine si hay monitoreo OnClickListener y llame a onClick.

Nota: El evento onInterceptTouchEvent solo está disponible en ViewGroup. La Actividad es el evento que comienza a distribuirse sin intercepción, y la vista es la última en recibir el evento y no necesita ser interceptada.

Nota:

getParent().requestDisallowInterceptTouchEvent(true)
//该方法可以控制父控件是否进行拦截,true为不拦截,false为拦截。其原理是控制是否调用onInterceptTouchEvent方法
Publicado 60 artículos originales · 25 alabanzas · Más de 10,000 visitas

Supongo que te gusta

Origin blog.csdn.net/qq_41466437/article/details/104778018
Recomendado
Clasificación