美文网首页Android自定义View
View的工作流程——layout&draw

View的工作流程——layout&draw

作者: xiasuhuei321 | 来源:发表于2016-10-29 19:18 被阅读0次

    ViewGroup的measure流程

    上一篇View的工作流程——measure流程中了解到了View的measure与ViewGroup的measure流程有密不可分的联系,这次就把View的笔记做完。

    ViewGroup作为容器除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类没有重写onMeasure()方法,但是它提供了一个measureChildren方法。这个方法会遍历View去测量他们自身。

        protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec){
            final int size = mChildrenCount;
            final View[] children = mChildren;
            //对子元素遍历
            for (int i = 0; i < size; ++i) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);
                }
            }
        }
    

    从上面的代码来看会对满足条件view调用measureChild方法,跟进去看看这个方法:

        protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
            //拿到子元素的LayoutParams
            final LayoutParams lp = child.getLayoutParams();
            //通过getChildMeasureSpec()创建MeasureSpec
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom, lp.height);
            //将创建的MeasureSpec传递给子元素
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    

    从上面的代码可以看到首先会拿到子元素的LayoutParams,然后通过getChildMeasureSpec方法来创建子元素的MeasureSpec,这个方法在前一篇中有过介绍,这里就不多赘述了。最后会调用子元素的measure方法并将创建的MeasureSpec传递给子元素。在这里就不具体结合实现onMeasure()的ViewGroup的子类来分析了,留待以后对ViewGroup进行更详细的学习的时候再说。

    layout过程

    layout过程主要涉及了两个方法,layout()和onLayout()。layout方法为view和它所有的子元素分配尺寸和位置。layout是Android布局机制(layout mechanism)的第二阶段。在这个阶段,每个parent对他的所有子元素都要调用layout方法去设置他们的位置。子类(派生类)不应该复写这个方法,有子元素的子类应该复写onLayout()方法。在onLayout方法里他们应该调用他们子元素的layout方法。

    以上是layout方法的注释,在很多时候源码的注释=api文档,所以推荐各位经常阅读api文档。毕竟别人解析的再好,也是别人的,不如自己去阅读一手的资料,看看写源码的人给我们的一些建议。

    稍微歪了一下题,接下来看看layout方法的源码:

        public void layout(int l, int t, int r, int b) {
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
    
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
    
            boolean changed = isLayoutModeOptical(mParent) ?
                    setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b);
                mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                    int numListeners = listenersCopy.size();
                    for (int i = 0; i < numListeners; ++i) {
                        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                    }
                }
            }
    
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
        }
    

    以上还是有很多代码理解不能,不过还好跟着书走一遍好了,代码中会通过判断layoutMode(大部分情况下都是返回false),最后通过setFrame方法来设置View四个顶点的位置。四个顶点一旦确定,那么View在父容器中的位置也就确定了。接着会调用onLayout方法,在ViewGroup里onLayout就是个抽象方法,找个实现的子类来看看,以下是LinearLayout的onLayout:

        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            if (mOrientation == VERTICAL) {
                layoutVertical(l, t, r, b);
            } else {
                layoutHorizontal(l, t, r, b);
            }
        }
    

    从字面上来看就是根据LinearLayout的orientation来执行相应的layout,看一下layoutVertical方法的代码:

        void layoutVertical(int left, int top, int right, int bottom) {
            final int paddingLeft = mPaddingLeft;
    
            int childTop;
            int childLeft;
            
            // Where right end of child should go
            final int width = right - left;
            int childRight = width - mPaddingRight;
            
            // Space available for child
            int childSpace = width - paddingLeft - mPaddingRight;
            
            final int count = getVirtualChildCount();
    
            final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
            final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    
            switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already
                   childTop = mPaddingTop + bottom - top - mTotalLength;
                   break;
    
                   // mTotalLength contains the padding already
               case Gravity.CENTER_VERTICAL:
                   childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
                   break;
    
               case Gravity.TOP:
               default:
                   childTop = mPaddingTop;
                   break;
            }
    
            for (int i = 0; i < count; i++) {
                final View child = getVirtualChildAt(i);
                if (child == null) {
                    childTop += measureNullChild(i);
                } else if (child.getVisibility() != GONE) {
                    final int childWidth = child.getMeasuredWidth();
                    final int childHeight = child.getMeasuredHeight();
                    
                    final LinearLayout.LayoutParams lp =
                            (LinearLayout.LayoutParams) child.getLayoutParams();
                    
                    int gravity = lp.gravity;
                    if (gravity < 0) {
                        gravity = minorGravity;
                    }
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                                    + lp.leftMargin - lp.rightMargin;
                            break;
    
                        case Gravity.RIGHT:
                            childLeft = childRight - childWidth - lp.rightMargin;
                            break;
    
                        case Gravity.LEFT:
                        default:
                            childLeft = paddingLeft + lp.leftMargin;
                            break;
                    }
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
    
                    childTop += lp.topMargin;
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                            childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    

    在遍历子元素之前会先初始化childTop,接着遍历子元素,在处理之后会调用setChildFrame方法来为子元素指定对应的位置,而clipTop的值会不断的增大,这意味着之后的元素会被放到靠下的位置,这和vetical的LinearLayout符合。setChildFrame()中会调用子元素的layout,之前分析过layout方法会为自身和自身的子元素确定位置和尺寸信息。如果这个child也有子元素,那么就会递归调用onLayout而子元素的子元素又会调用layout方法……子子孙孙无穷尽了。玩笑,递归肯定有出口的,那就是最里层的元素。如此处理之后,整个View树就完成了layout过程。

    draw

    简单的来说draw就是将view绘制到屏幕上。在调用这个方法之前必须完成onlayout的过程。在自定义view的时候实现onDraw而不是重写draw。

        public void draw(Canvas canvas) {
            final int privateFlags = mPrivateFlags;
            final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                    (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
            mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
            /*
             * Draw traversal performs several drawing steps which must be executed
             * in the appropriate order:
             *
             *      1. Draw the background
             *      2. If necessary, save the canvas' layers to prepare for fading
             *      3. Draw view's content
             *      4. Draw children
             *      5. If necessary, draw the fading edges and restore layers
             *      6. Draw decorations (scrollbars for instance)
             */
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
            // skip step 2 & 5 if possible (common case)
            final int viewFlags = mViewFlags;
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
            if (!verticalEdges && !horizontalEdges) {
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                // Step 4, draw the children
                dispatchDraw(canvas);
    
                // Overlay is part of the content and draws beneath Foreground
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().dispatchDraw(canvas);
                }
    
                // Step 6, draw decorations (foreground, scrollbars)
                onDrawForeground(canvas);
    
                // we're done...
                return;
            }
    

    这个源码的注释还真是详细……主要的流程都已经在源码中标注了出来这里就不做过多的解释了。

    • 画背景
    • 有必要的话保存画布层级
    • 画view的内容
    • 画子元素
    • 有必要的话画边缘恢复层级
    • 画装饰

    到这View的工作流程暂时就过了一遍了,终于为自定义View扫清了一个障碍,不过自定义View还需要更多的练习,光知道原理是没什么用的,还要回用合适的工具、方法来构建出自己想要的东西。

    相关文章

      网友评论

        本文标题:View的工作流程——layout&draw

        本文链接:https://www.haomeiwen.com/subject/nrfyuttx.html