专栏首页码上积木拇指记者打探事件分发机制背后的秘密(下)

拇指记者打探事件分发机制背后的秘密(下)

前言

上一期跟随拇指记者,发现了Android公司在指派具体的人之前的种种机制,今天就继续探索,看看任务具体的处理消费逻辑。

交给做任务具体的人(ViewGroup)

开始分派任务,也就是ViewGroup的事件分发时间,这部分内容是老生常谈了,最重要的就是这个dispatchTouchEvent方法。

假设我们没有看过源码,那么 事件来了,会产生多种传递拦截的可能,我画了个脑图:

其中产生的疑问就包括:

  • ViewGroup是否拦截事件,拦截后怎么处理?
  • 不拦截后交给子View或者子ViewGroup怎么处理?
  • 子View怎么决定是否拦截?
  • 子View拦截后怎么处理事件?
  • 子View不拦截事件后父元素ViewGroup怎么处理事件?
  • ViewGroup不拦截,子View也不拦截,最终事件怎么处理?

接下来就具体分析分析。

ViewGroup是否拦截事件,拦截后怎么处理?

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        //1
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
            } 
        } 

        //2    
        if (!canceled && !intercepted) {
            //事件传递给子view
        }

        //3
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        }
    }

    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else { 
            handled = child.dispatchTouchEvent(event);
        }
    }

上述代码分成了三部分,分为ViewGroup是否拦截、拦截后则不再传递下去,ViewGroup拦截后的处理。

1、ViewGroup是否拦截

可以看到,初始化了一个变量intercepted,代表viewGroup是否拦截。

如果满足两个条件任意一个,才去讨论ViewGroup是否拦截:

  • 事件为ACTION_DOWN,也就是按下事件。
  • mFirstTouchTarget不为null

其中mFirstTouchTarget是个链表结构,代表某个子元素成功消费了该事件,所以mFirstTouchTarget不为null就代表有子view消费事件,这个待会再细谈。当第一次进入这个方法,事件肯定就是ACTION_DOWN,所以就进入了if语句,这时候获取了一个叫做disallowIntercept(不允许拦截)的变量,暂且按下不表,接着看。然后给这个intercepted赋值为onInterceptTouchEvent方法的结果,我们可以理解为 viewGroup是否拦截取决于onInterceptTouchEvent方法。

2、拦截后则不再传递

如果viewGroup拦截了,也就是intercepted为true,自然也就不需要再往子view或者子ViewGroup进行传递了。

3、ViewGroup拦截后的处理

如果mFirstTouchTarget为null,则表示没有子View进行拦截,然后就转向执行dispatchTransformedTouchEvent方法,代表ViewGroup要自己再进行一次分发处理。

这里有个问题就是为什么不直接判断intercepted呢?非要去判断这个mFirstTouchTarget

  • 因为mFirstTouchTarget==null不仅代表ViewGroup要自己消费事件,也代表了ViewGroup没消费并且子View也没有去消费事件,两种情况都会执行到这里。

也就是ViewGroup拦截或子View没有拦截,都会调用到dispatchTransformedTouchEvent方法,在该方法中,最后会调用super.dispatchTouchEvent

super代表ViewGroup的父类View,也就是ViewGroup会作为一个普通View执行View.dispatchTouchEvent方法,至于这个方法具体做了什么,待会和View的事件处理再一起看。

通过上面的分析,我们可以得出ViewGroup拦截的伪代码:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        if (onInterceptTouchEvent(event)) {
            isConsume = super.dispatchTouchEvent(event);
        } 
    } 
    return isConsume;
}

如果是ViewGroup,会先执行到onInterceptTouchEvent方法判断是否拦截,如果拦截,则执行父类View的dispatchTouchEvent方法。

ViewGroup不拦截后交给子View或者子ViewGroup处理?

接着说ViewGroup不拦截的情况,也就会传到子View的情况:

    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int childrenCount = mChildrenCount;

            //1
            if (newTouchTarget == null && childrenCount != 0) {
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = getAndVerifyPreorderedIndex(
                            childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(
                            preorderedList, children, childIndex);

                    //2
                    if (!child.canReceivePointerEvents()
                            || !isTransformedTouchPointInView(x, y, child, null)) {
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }

                    //3
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }
        }
    }

ViewGroup不拦截,则intercepted为false,那么就会进入上述的if语句中。

同样分为三部分来说,分别是遍历子View,判断事件坐标,传递事件

1、遍历子View

第一部分就是遍历当前ViewGroup所有的子View。

2、判断事件坐标

然后会判断这个事件是否在当前子View的坐标内,如果用户触摸的地方都不是当前的View自然不需要对这个view在进行分发处理,还有个条件就是当前View没有在动画状态。

3、传递事件

如果事件坐标在这个View内,就开始传递事件,调用dispatchTransformedTouchEvent方法,如果为true,就调用addTouchTarget方法记录事件消费链。

dispatchTransformedTouchEvent方法是不是有点熟悉?没错,刚才也出现过,再看一遍:

    private boolean dispatchTransformedTouchEvent(View child) {
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else { 
            handled = child.dispatchTouchEvent(event);
        }
    }

这里对传进来的 child进行了判断,这个child就是子View,如果子View不为null,就调用这个子View的dispatchTouchEvent方法,继续分发事件。如果为null,就是刚才的情况,调用父类的dispatchTouchEvent方法,默认为自己来消费事件。

当然,这个child有可能为viewGroup有可能为View,总之就是继续分发调用子View或者子ViewGroup的方法。

到此,一个关于dispatchTouchEvent的递归就显现出来了:如果某个ViewGroup无法消费事件,那么就会传递给子view/子ViewGroup的dispatchTouchEvent方法,如果是ViewGroup,那么又会重复这个操作,直到某个View/ViewGroup消费事件。

最后,如果dispatchTransformedTouchEvent方法返回true,就代表有子view消费了事件,然后会调用到addTouchTarget方法:

在该方法中,会对mFirstTouchTarget这个单链表进行了赋值,记录消费链(但是在单点触控的情况下,其单链表的结构并没有用上,只是作为一个普通的TouchTarget对象,待会会说到),然后就break退出了循环。

接下来就看看关于View内部具体处理事件的逻辑。

子View怎么处理事件,是否拦截?

public boolean dispatchTouchEvent(MotionEvent event) {
        
        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;
            }
        }
        return result;
    }

其实就是两个逻辑:

  • 1、如果View设置了setOnTouchListener并且onTouch方法返回true,那么onTouchEvent就不会被执行。
  • 2、否则,执行onTouchEvent方法。

所以默认情况下是直接会执行onTouchEvent方法。

关于View的事件分发我们也可以写一段伪代码,并且增加了setOnClickListener方法的调用:

public void consumeEvent(MotionEvent event) {
    if (!setOnTouchListener || !onTouch) {
        onTouchEvent(event);
    } 

    if (setOnClickListener) {
        onClick();
    }
}

子View拦截后怎么处理事件?

子View拦截后,就会给单链表mFirstTouchTarget赋值。

这个刚才已经说过了。逻辑就在addTouchTarget方法中,我们来具体看看:

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

    public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
        final TouchTarget target;
        target.child = child;
        return target;
    }

这个单链表到底怎么连的呢?之前我们说过dispatchTouchEvent是一个递归的过程,当某个子View消费了事件,那么通过addTouchTarget方法,就会让mFirstTouchTarget的child值指向那个子View,依此向上,最后就会拼接成一个类似单链表结构,尾节点就是消费的那个View。

为什么说类似呢?因为mFirstTouchTarget并没有真正连起来,而是通过每个ViewGroup的mFirstTouchTarget间接连起来。

打个比方,我们假设一个View树关系:

    A
   / \
  B   C
    /  \
   D    E

A、B、C为ViewGroup,D、E为View。

当我们触摸的点在ViewD中,事件分发的顺序就是A-C—D

在C遍历D的时候,ViewD消费了事件,所以走到了addTouchTarget方法中,包装了一个包含ViewD的TouchTarget,我们叫它TargetD。

然后设置C的mFirstTouchTarget为TargetD,也就是其child值为ViewD。

再返回上一层,也就是A层,因为D消费了事件,所以C的dispatchTouchEvent方法也返回了true,同样调用了addTouchTarget方法,包装了一个TargetC。

然后会设置A的mFirstTouchTarget为TargetC,也就是其child值为ViewC。

最终的分发结构就是:

A.mFirstTouchTarget.child -> C

C.mFirstTouchTarget.child -> D

所以说mFirstTouchTarget通过child找到了消费链的下一层View,然后下一层又继续通过child找到下下层View,依次往下,就记录了消费的完整路径。

mFirstTouchTarget的链表结构用到哪了呢?多点触控。

对于多点触控且点击目标不同的情况,mFirstTouchTarget才会作为链表结构存在,next指向上一个手指按下时创建的TouchTarget对象。

而在单点触控情况下,mFirstTouchTarget链表会蜕变成单个TouchTarget对象:

  • mFirstTouchTarget.next 始终为null。
  • mFirstTouchTarget.child 赋值为这条消费链的下一层View,一层层递归调用每一层的mFirstTouchTarget.child,直到消费的那个view。

最后再补充一点,每次ACTION_DOWN事件来到的时候,mFirstTouchTarget就会被重置,迎接新的一轮事件序列。

子View不拦截事件后ViewGroup怎么处理事件?

子View不拦截事件,那么mFirstTouchTarget就为null,退出循环后,调用了dispatchTransformedTouchEvent方法。

        //3
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        }

最终调用了super.dispatchTouchEvent,也就是View.dispatchTouchEvent方法。

可以看到子View不拦截事件和ViewGroup拦截事件的处理是一样的都会走到这个方法中。

那么这个方法到底干了什么呢?上面说到View的处理方法dispatchTouchEvent已经说过了,还是那段伪代码,只不过在这里View是作为ViewGroup的父类。

所以,小结一下,如果所有子View都不处理事件,那么:

  • 默认执行ViewGrouponTouchEvent方法。
  • 如果设置ViewGroupsetOnTouchListener,就会执行onTouch方法。

ViewGroup不拦截,子View也不拦截,最终事件怎么处理?

最后一点,如果ViewGroup不拦截,子View也不拦截,这个意思就是mFirstTouchTarget == null 的同时,dispatchTransformedTouchEvent方法也返回false。

总之,就是所有ViewGroup的dispatchTouchEvent方法都返回false,这时候该怎么处理呢?返回到一开始大佬会谈的时候:

//Activity.java
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

没错,如果superDispatchTouchEvent方法返回false,那么就会执行Activity的onTouchEvent方法。

小结

小结一下:

  • 事件分发的本质就是一个递归方法,通过往下传递,调用dispatchTouchEvent方法,找到事件的处理者,这也就是项目中常见的责任链模式
  • 在消费过程中,ViewGroup的处理方法就是onInterceptTouchEvent
  • 在消费过程中,View的处理方法就是onTouchEvent方法。
  • 如果底层View不消费,则一步步往上执行父元素的onTouchEvent方法。
  • 如果所有View的onTouchEvent方法都返回false,则最后会执行到Activity的onTouchEvent方法,事件分发也就结束了。

完整事件消费伪代码:

public boolean dispatchTouchEvent(MotionEvent event) {
    boolean isConsume = false;
    if (isViewGroup) {
        //ViewGroup
        if (onInterceptTouchEvent(event)) {
            isConsume = consumeEvent(event);
        } else {
            isConsume = child.dispatchTouchEvent(event);
        }
    } else {
        //View
        isConsume = consumeEvent(event);
    }

    if (!isConsume) {
        //如果自己没拦截,子View没有消费,自己也要调用消费方法
        isConsume = consumeEvent(event);
    }
    return isConsume;
}


public void consumeEvent(MotionEvent event) {
    //自己消费事件的逻辑,默认会调用到onTouchEvent
    if (!setOnTouchListener || !onTouch) {
        onTouchEvent(event);
    } 
}

dispatchTouchEvent() + onInterceptTouchEvent() + onTouchEvent(),大家也可以把这三个方法作为理解记忆事件分发的重点。

后续任务处理(事件序列)

终于,任务找到了它的主人,看似流程也结束了,但是还存在一个问题就是,这个任务之后的后续任务该怎么处理呢?比如要增加某某模块功能。

不可能再走一遍公司流程吧?如果按照正常逻辑,是应该找到当初负责我们任务的那个人来继续处理,看看Android公司是不是这么做的。

一个MotionEvent事件序列一般包括:

ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL

刚才我们都说的是ACTION_DOWN,也就是手机按下的事件处理,那么后续的移动手机,离开屏幕事件该怎么处理呢?

假设之前已经有一个ACTION_DOWN并且被某个子View消费了,所以mFirstTouchTarget会有一条完整的指向,这时候来了第二个事件——ACTION_MOVE

    if (!canceled && !intercepted) {
       if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {          
    }

然后就会发现,ACTION_MOVE事件根本进不去对子View的循环方法,而是直接到了最后面的逻辑:

    if (mFirstTouchTarget == null) {
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                handled = true;
            } else {
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                    target.child, target.pointerIdBits)) {
                       handled = true;
                }
            }
            predecessor = target;
            target = next;
        }
    }

如果mFirstTouchTarget为null,就是之前说过的转到ViewGroup自身的onTouchEvent方法。

这里很明显不为null,所以走到else中,又开始遍历mFirstTouchTarget,之前说过单点触控的时候,target.next为null,target.child为消费链的下一层View,所以其实就是将事件交给了下一层View。

这里有个点很多朋友可能之前没注意到,就是当ACTION_DOWN的时候,走到这里,会通过mFirstTouchTarget找到那个消费的View执行dispatchTransformedTouchEvent。但是这之前,遍历View的时候已经执行了一次dispatchTransformedTouchEvent方法,难道这里还要执行一次dispatchTransformedTouchEvent方法吗?这不就重复了?

  • 这就涉及到另一个变量alreadyDispatchedToNewTouchTarget。这个变量代表之前是否已经执行过一次View消费事件,当事件为ACTION_DOWN,就会遍历View,如果view消费了事件,那么alreadyDispatchedToNewTouchTarget就被赋值为true,所以到这里也就不会再次执行了,直接handled = true

所以后续任务的处理逻辑也基本明白了:

只要某个View开始处理拦截事件,那么这一整个事件序列都只能交给它来处理。

优化任务派发流程(解决滑动冲突)

到此,任务终于是分发完成了,任务完成后,小组开了一个总结会议

其实任务分发过程还是有可以优化的过程,比如有些任务是不一定就只交给一个人做,比如交给两个人做,把A擅长的任务给A做,B擅长的任务给B做,最大化利用好每个人。

但是我们之前的逻辑默认是按下任务交给了A,后续都会交给A。所以这时候就需要设计一种机制对某些任务进行拦截。

其实这就涉及到滑动冲突的问题了,举例一个场景:

外面的ViewGroup是横向移动,而内部的ViewGroup是需要纵向移动的,所以需要在ACTION_MOVE的时候对事件进行判断和拦截。(类似ViewGroup+Fragment+Recyclerview)

直接说Android公司的解决方案,两种方案:

  • 外部拦截法。
  • 内部拦截法。

外部拦截法

外部拦截法比较简单,因为不管子View是否拦截,每次都会执行onInterceptTouchEvnet方法,所以我们就可以在这个方法中,根据自己的业务条件选择是否拦截事件。

    //外部拦截法:父view.java      
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercepted = false;
        //父view拦截条件
        boolean parentCanIntercept;

        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
        }
        return intercepted;

    }

逻辑很简单,就是根据业务条件,在onInterceptTouchEvent中决定是否拦截,因为这种方法是在父View中控制是否拦截,所以这种方法叫做外部拦截法。

但是这和我们之前的认知又冲突了,如果ACTION_DOWN交给了子View处理,那么后续事件应该会直接被分发给这个view呀,为什么还能被父View拦截的?

我们再来看看dispatchTouchEvent方法:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            intercepted = onInterceptTouchEvent(ev);
        } 

        // Dispatch to touch targets.
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        } else {
            while (target != null) {
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    handled = true;
                } else {
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                            || intercepted;
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                        handled = true;
                    }
                }
            }
        }
    }

当事件为ACTION_MOVE的时候,并且在onInterceptTouchEvent方法返回了true,所以这里的intercepted=true,再到下面的逻辑,cancelChild的值也为true,然后被传到了dispatchTransformedTouchEvent方法,没错,又是这个方法,不同的是cancelChild子段为true。

看这个字段的名字肯定是和取消子view事件有关的,继续看看:

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        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;
        }
    }

看出来了么,当第二个字段cancel为true的时候,事件会被修改成ACTION_CANCEL!!,然后才会被继续传下去。

所以就算某个View消费了ACTION_DOWN,但是当后续事件来的同时,在父元素的onInterceptTouchEvent()中返回true,那么这个事件就会被修改为ACTION_CACLE事件再传给子View。

所以子View再次交出了对该事件序列的控制权,这也就是外部拦截法能实现的原因。

内部拦截法

继续看看内部拦截法:

    //父view.java            
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

    //子view.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        //父view拦截条件
        boolean parentCanIntercept;

        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                if (parentCanIntercept) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.dispatchTouchEvent(event);
    }

内部拦截法是将主动权交给子View,如果子View需要事件就直接消耗,否则交给父容器处理。我们列举下DOWN和MOVE两种情况:

  • ACTION_DOWN的时候,子View必须能消费,所以父View的onInterceptTouchEvent要返回false,否则就被父View拦截了,而且后续事件也不会传到子View这里了。
  • ACTION_MOVE的时候,父View的onInterceptTouchEvent方法要返回true,表示当子View不想消费的时候,父View能及时消费,那么子View怎么控制呢?可以看到代码设置了一个requestDisallowInterceptTouchEvent方法,这个是干嘛呢?
    protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (disallowIntercept) {
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
    }

这种通过|=&= ~运算符修改参数是源码中常见的设置标识的方法:

  • |= 将标志位设置为1
  • &= ~将标识位设置为0

所以在需要父元素拦截的时候就设置了requestDisallowInterceptTouchEvent(false)方法,让标志位设置为0,这样父元素就能执行到onInterceptTouchEvent方法。

具体生效代码就在dispatchTouchEvent方法中:

    if (actionMasked == MotionEvent.ACTION_DOWN) {
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }

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

可以看到,如果disallowIntercept为false,就代表父View要拦截,然后就会执行到onInterceptTouchEvent方法,在onInterceptTouchEvent方法中返回ture,父View成功拦截。

总结

经过拇指记者的探访,终于把Android公司对于事件任务处理摸清楚了,希望对于屏幕前的你能有些帮助,下期再见啦。

参考

《Android开发艺术探索》 https://wanandroid.com/wenda/show/12119 http://gityuan.com/2016/12/31/input-ipc/ https://juejin.cn/post/6844903926446161927 https://www.jianshu.com/p/b7f33f46d33c https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1868

感谢大家的阅读,有一起学习的小伙伴可以关注下公众号—码上积木❤️ 每日一个知识点,建立完整体系架构。

本文分享自微信公众号 - 码上积木(Lzjimu),作者:积木zz

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-04-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 拇指记者打探事件分发机制背后的秘密(上)

    聊到事件分发,很多朋友就会想到view的dispatchTouchEvent,其实在此之前,Android还做了很多工作。

    码上积木
  • QQ的成功,远没有你想象的那么顺利和轻松

    如果QQ是一个人,看似风光,其实从出生到成长,过程饱经错荡,堪算坎坷。它的人生历程确实也够励志的了。

    JackJiang
  • QQ的成功,远没有你想象的那么顺利和轻松

    如果QQ是一个人,看似风光,其实从出生到成长,过程饱经错荡,堪算坎坷。它的人生历程确实也够励志的了。

    JackJiang
  • 大屏时代的生态变迁,看平板手机的拇指热键与界面布局

    iPhone出现之后的几年,手机屏幕的尺寸基本都保持在4英寸以下(以对角线计算),非常便于单手操作。然而,随着大屏手机不断涌入市场,到2014年年中,已经有将近...

    博文视点Broadview
  • 脑机头条 第38期| 能玩"剪刀石头布"的脑机!密歇根大学开发由大脑意识精密控制的假肢

    几日前,美国密歇根大学的研究人员在《科学》子刊《科学转化医学》发表了脑控假肢领域一篇重磅文章。文章介绍了研究人员通过一种新的神经接口技术,开发出一款由意识精密控...

    脑机接口社区
  • 想搭上“人工智能”顺风车,李彦宏的未来医疗还需完善这几个方面

    镁客网
  • 这些网站,99%人用过都说是神器,还不收藏!

    —— 由谷歌开发的一个基于AI分析并猜出你要画什么的平台,是原先“你画我猜”的升级版,让你从现有图库里找出最符合脑中形象的图案。

    黑泽君
  • “携程泄密”原因技术拆解

    叶亚明万万没有想到,他在携程网大干快上的技术改造升级给其OpenStack团队造成巨大压力。这位携程网新任技术副总裁自上任始,便对整个技术构架进行大刀阔斧的...

    静一
  • 密码发展史之古典密码

    密码(Cryptology)是一种用来混淆的技术,它希望将正常的、可识别的信息转变为无法识别的信息。密码学是一个即古老又新兴的学科,密码学一词源自希腊文“kry...

    安智客
  • 能玩“剪刀石头布”!密歇根大学开发科幻假肢,可由大脑意识精密控制

    3 月 5 日,《科学》子刊《科学转化医学》发表了脑控假肢领域一篇重磅文章,来自美国密歇根大学的研究人员通过一种新的神经接口技术,开发出一款由意识精密控制的假肢...

    大数据文摘
  • 每天220亿人使用的一个小功能,Facebook点赞按钮的设计门道

    一年前,Facebook点赞按钮发布更新。一年后的今天,Facebook小小的点赞按钮因为Ted刚发布的一段演讲掀起波澜。设计一个像FB点赞按钮那么小的东西很难...

    BestSDK
  • 秘密共享—隐私计算和区块链共识中的榫卯

    2018年5月正式生效的GDPR是欧盟“史上最严”条例,是数十年来数据安全和数据隐私法规方面最重要的一次变化。随后,2019年生效的加州CCPA,对个人数据及信...

    绿盟科技研究通讯
  • Unity3D游戏开发初探—2.初步了解3D模型基础

      简而言之,3D模型就是三维的、立体的模型,D是英文Dimensions的缩写。

    Edison Zhou
  • 4G催生下的移动视频行业新“热点”

    每一波技术浪潮到来,洗牌、颠覆、破局都会上演。4G就是这样的技术浪潮。大流量应用将爆发,视频App首当其冲。对视频App来说,4G是机遇,也是挑战。搜狐...

    罗超频道
  • 慢工出细活,Facebook点赞按钮设计中的门道

    一年前,Facebook点赞按钮发布更新。一年后的今天,Facebook小小的点赞按钮因为Ted刚发布的一段演讲掀起波澜。设计一个像FB点赞按钮那么小的东西很...

    奔跑的小鹿
  • EKT多链技术谈 | 数学:区块链里的精密元件

    前言:数学在人类文明的发展中起着非常重要的作用。牛顿当年通过数学计算预见了发射人造天体的可能性;爱因斯坦相对论的质能公式从数学论证的角度预示了原子能时代的来临;...

    风中凌乱的靓仔
  • 维基解密再爆猛料:CIA利用漏洞入侵全球数十亿个人电子设备

    大数据文摘
  • 机器人如何才能更像人?关键在于它

    据Futurism报道,对于人类来说,特别是从事制造业工作的人,打个结、剥离电缆线外皮、将销子插入孔中或使用钻头工具等行为都很常见。它们看起来似乎都很简单,但同...

    机器人网
  • 下暴雨出不了门?这有27部优秀的黑客纪录片

    大数据文摘

扫码关注云+社区

领取腾讯云代金券