• Android View体系(八)从源码解析View的layout和draw流程


    相关文章
    Android View体系(一)视图坐标系
    Android View体系(二)实现View滑动的六种方法
    Android View体系(三)属性动画
    Android View体系(四)从源码解析Scroller
    Android View体系(五)从源码解析View的事件分发机制
    Android View体系(六)从源码解析Activity的构成
    Android View体系(七)从源码解析View的measure流程

    前言

    上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了。

    1.View的layout流程

    先来看看View的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;
        }
    

    传进来里面的四个参数分别是View的四个点的坐标,它的坐标不是相对屏幕的原点,而且相对于它的父布局来说的。 l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离;
    r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。来看看setFrame()方法里写了什么:

        protected boolean setFrame(int left, int top, int right, int bottom) {
            boolean changed = false;
    
            if (DBG) {
                Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                        + right + "," + bottom + ")");
            }
    
            if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
                changed = true;
    
                // Remember our drawn bit
                int drawn = mPrivateFlags & PFLAG_DRAWN;
    
                int oldWidth = mRight - mLeft;
                int oldHeight = mBottom - mTop;
                int newWidth = right - left;
                int newHeight = bottom - top;
                boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
    
                // Invalidate our old position
                invalidate(sizeChanged);
    
                mLeft = left;
                mTop = top;
                mRight = right;
                mBottom = bottom;
                mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    
            ...省略  
            }
            return changed;
        }

    在setFrame()方法里主要是用来设置View的四个顶点的值,也就是mLeft 、mTop、mRight和 mBottom的值。在调用setFrame()方法后,调用onLayout()方法:

      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        }
    

    onLayout()方法没有去做什么,这个和onMeasure()方法类似,确定位置时根据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()方法。既然这样,我们就来看看LinearLayout的onLayout()方法:

      @Override
        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);
            }
        }

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

    这个方法会遍历子元素并调用setChildFrame()方法:

      private void setChildFrame(View child, int left, int top, int width, int height) {        
            child.layout(left, top, left + width, top + height);
        }

    在setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值是逐渐增大的,这是为了在垂直方向,子元素是一个接一个排列的而不是重叠的。

    2.View的draw流程

    View的draw流程很简单,先来看看View的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;
    
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    ...省略
       // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    ...省略
      // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    ...省略
       // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    ...省略
      // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);
    
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
       }

    从源码的注释我们看到draw流程有六个步骤,其中第2步和第5步可以跳过:

    1. 如果有设置背景,则绘制背景
    2. 保存canvas层
    3. 绘制自身内容
    4. 如果有子元素则绘制子元素
    5. 绘制效果
    6. 绘制装饰品(scrollbars)

    好了,关于View的工作流程就讲到这里了,接下来会讲到自定义View。

  • 相关阅读:
    ie用document.getElementsByName获取不到
    js_设置光标到文本的最后位置
    js-转大小写
    mysql查询数据表的路径
    myeclipse导出javadoc时特殊字符 尖括号
    keyCode码集合
    mysql查询数据库约束
    oracle查询每个表的占用空间
    MYSQL复制表
    MacOs上的Intellij idea高频快捷键总结(2018.1版本)
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/6769642.html
Copyright © 2020-2023  润新知