View体系的一些小问题|青训营笔记

135 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的的第5天

问题

设置点击事件逻辑的时候,最基础的办法就是先用 findViewById() 来绑定实例,其次就是设置一个匿名内部类来监听点击,继而处理事件。那么我们可以提出以下的问题。

  1. findViewById() 如何找到并绑定对应的 View

    首先,findViewById() 会做一个层层代理,执行到 DecorView 这一层的 findViewById() 中。

    然后,DecorView 本质上是 ViewGroup,那么就变成了在 ViewGoup 上寻找对应的 View

    由于 ViewGroup 是继承自 View,我们先来查看 View 的代码

    //View
    public <T extends View> T findViewById(@IdRes int id) {
        if(id == NO_ID){
            return Null;
        }
        return findViewTraversal(id);
    }
    
    public <T extends View> T findViewTraversal(@IdRes int id) {
        if(id == mID){
            return (T)this;
        }
        return null;
    }
    

    从上述的代码可以看出,View 中如果 id 不存在就会返回null;如果存在且等于自己,就会返回自己。

    下面我们来看 ViewGroup 中的代码

    //ViewGroup
    /**
         * {@hide}
         */
        @Override
        protected <T extends View> T findViewTraversal(@IdRes int id) {
            if (id == mID) {
                return (T) this;
            }
    
            final View[] where = mChildren;
            final int len = mChildrenCount;
    
            for (int i = 0; i < len; i++) {
                View v = where[i];
    
                if ((v.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {
                    v = v.findViewById(id);
    
                    if (v != null) {
                        return (T) v;
                    }
                }
            }
    
            return null;
        }
    
    

    ViewGroup 是继承自 View 的,直接使用了 View 的 findViewById() 方法,所以只复写了 ViewGroupfindViewTraversal()。其处理逻辑是,迭代查看它的哪个子View符合,然后返回;否则返回 null。

  2. 我们常重写的 onClick() 是和 onLongClick() 他们同时设置的话会同时执行吗?执行逻辑有何不同?

    这两个方法如何执行的,我们需要查看它的源码

    //View 
    public boolean onTouchEvent(MotionEvent event) {
            ...
    
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        ...
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                            ...
                                if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // This is a tap, so remove the longpress check
                                removeLongPressCallback();//标注2
    
                                // Only perform take click actions if we were in the pressed state
                                if (!focusTaken) {
                                    // Use a Runnable and post this rather than calling
                                    // performClick directly. This lets other visual state
                                    // of the view update before click actions start.
                                    if (mPerformClick == null) {
                                        mPerformClick = new PerformClick();//标注1
                                    }
                                    if (!post(mPerformClick)) {
                                        performClickInternal();
                                    }
                                }
                            ...
                        mIgnoreNextUpEvent = false;
                        break;
    
                    case MotionEvent.ACTION_DOWN:
                        ...
    
                        if (!clickable) {
                            checkForLongClick(//标注3
                                    ViewConfiguration.getLongPressTimeout(),
                                    x,
                                    y,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
                            break;
                        }
    
                        ...
                        break;
    
                    ...
                }
    
                return true;
            }
    
            return false;
        }
    

    在上文我们知道 onClick() 方法是通过上述代码标注1处来设置的,可知手指离开屏幕的时候,此处执行的是普通的点击事件。

    下面我们说下普通点击和长按点击分别如何执行。现在我们再看回标注3,此处执行的是 checkForLongClick() ,即一点击屏幕就会执行该方法,该方法会开启一个线程来计时和处理 onLongClick()。然后就看到条件为手指离开屏幕处的标注2,此处是判断此时是否已经执行了 onLongClick(),若是未执行,就说明是未能到达触发条件,此时移除不执行长按事件。移除后,就会去到标注3处,执行 onClick() 普通的点击事件。

  3. 常用的交互事件和触摸事件有哪些

    交互事件

    上文方法中的 onTouch()onClick() 之类的方法的关系是前者包含后者,包括 onLongClick() 都是基于此封装的。onTouch() 是他们的入口。

    触摸事件

    参考

    View.java - Android Code Search

    ViewGroup.java - Android Code Search

    第四届字节青训营