美文网首页
View的工作原理

View的工作原理

作者: Johnson_Coding | 来源:发表于2019-07-15 17:02 被阅读0次

    viewRoot对应viewRootImpl类,它是连接WindowManager和decorview的纽带 ,view的三大流程都是通过viewroot的performTaversals方法通过measure,layout,draw三个过程绘制完成。
    measure:测量view的宽高,measure完成后可通过getMeasureWidth和getMeasureHeight获取宽高。
    layout:确定view在父容器的位置,可以获取四个坐标和view宽高。通过getTop,getLeft,getRight,getBotton获取。
    draw:负责view绘制在屏幕上,只有此方法完成后view才会显示在屏幕上.

    measure过程

    一般view的measure是由自身view和子view一起决定的,例如linearLayout。

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (mOrientation == VERTICAL) {
                measureVertical(widthMeasureSpec, heightMeasureSpec);
            } else {
                measureHorizontal(widthMeasureSpec, heightMeasureSpec);
            }
        }
    

    源码会先判断水平还是竖直,然后遍历子元素,调用measureChildBeforeLayout(),这个方法内部会调用
    measureChindWithMargin()获取子元素的layoutparames 然后调用measure(childWidthMeasureSpec, childHeightMeasureSpec),其中会调用方法getDefaultSize(), 这个会确定measureSpecMode()和measureSpecSize(),计算子view的大小,最后在加上margin得出最终大小。
    MeasureSpec:MeasureSpec代表一个32为int值,其中高两位代表SpecMode,低30位代表SpecSize。
    AT_MOST:父容器指定一个可用大小,view的大小小于或等于这个值,它对应layoutParams的wrap_content
    EXACTLY:父容器指定一个精确大小,view的大小就是specSize指定的值,对应LayoutParams的match_parent。
    unspecified:不限制大小,一般用于系统内部,表示测量状态,一般实际开发用不到。


    QQ图片20190715162643.png
    public abstract class ViewGroup ...{
    ...
    //遍历所有的子元素
    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);
                }
            }
        }
    //调用子元素的 measure 方法
    protected void measureChildWithMargins(View child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
              //获取子元素的 layoutParams
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            //getChildMeasureSpec 此处获取 getChildMeasureSpec
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                    mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                            + heightUsed, lp.height);
            //调用子元素的 measure 方法(子元素宽MeasureSpec,子元素高MeasureSpec)
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    ...
    }
    

    一般一个view可能会被调用多次或者view的measure过程和activity生命周期不同步导致获取的是0,可以通过avtivity/view的onWindowFocusChanged()或者view.post()或viewTree监听获取。

    layout过程

    首先通过setFrame()方法设置四个坐标然后调用onLayout

    public class LinearLayout extends ViewGroup {
    ...
     @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);
            }
        }
    ...
    void layoutVertical(int left, int top, int right, int bottom) {
           ...//获取竖向子元素的数量
            final int count = getVirtualChildCount();
            ...
            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();
    
                    if (hasDividerBeforeChildAt(i)) {
                        childTop += mDividerHeight;
                    }
                    //childTop 变大,往下排序
                    childTop += lp.topMargin;
                    setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);
                    childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                    i += getChildrenSkipCount(child, i);
                }
            }
        }
    ...//调用子元素的layout方法,它会遍历所有子元素,确定子元素位置,进行从上到下排序。
    private void setChildFrame(View child, int left, int top, int width, int height) {        
            child.layout(left, top, left + width, top + height);
        }
    

    draw过程

    将view绘制到屏幕上
    (1)绘制背景 background(canvas)
    (2)绘制自己(onDraw)
    (3)绘制children(dispatchDraw)
    (4)绘制装饰(onDrawScrollBars)

    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;
    
            /*
             * 绘制遍历执行几个绘图步骤,必须按照适当的顺序执行:(渣百度翻译。。。)
             *
             *      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 绘制过程的传递是通过 dispatchDraw 来实现的,dispatchDraw 会遍历所有的子元素的 draw 方法,View 没有具体的实现 dispatchDraw ,子类需要重写 dispatchDraw 。

    自定义view分类

    1.继承 View 重写 onDraw 方法

    这种方法主要用于实现一些不规则的效果,既要重写 onDraw 方法,这种方式需要自己支持 wrap_content,并且 padding 也需要自己处理。

    2.继承 ViewGroup 派生特殊的 Layout

    这种方法主要用于实现自定义布局,这种方式稍微复杂一些,需要合适的处理 ViewGroup 的测量、布局这两个过程,并同事处理子元素的测量和布局过程。

    3.继承特定的 View(比如 TextView)

    这种方法比较常见,一般用于扩展某种已有的 View 的功能,这种方法不需要自己支持 wrap_content 和 padding 等。

    4.继承特定的 ViewGroup(比如 LiearLayout)

    这种方法也比较常见,这种方法不需要自己处理 ViewGroup 的测量和布局。

    相关文章

      网友评论

          本文标题:View的工作原理

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