View工作原理

View工作原理

1. MeasureSpec

MeasureSpec意为测量规格,是一个32位int值,高2位代表测量模式SpecMode,低30位代表该测量模式下的规格大小SpecSize。通过MeasureSpec.makeMeasureSpec()可以得到一个measureSpec;代码如下:

 public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,@MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
1.1 SpecMode

SpecMode有三类,分别是

  • UNSPECIFIED : 父容器不对view有任何限制,想要多大就给多大,一般只有系统内部使用
  • EXACTLY : 父容器已经检测出view的大小,即SpecSize的值,一般对应于LayoutParams中的match_parent和具体数值 如 100dp
  • AT_MOST :父容器给定一个具体的值SpecSize,view的具体大小由自身实现,但是不能超过SpecSize,对应于;LayoutParams中的wrap_content

2. LayoutParams

LayoutParams主要保存了一个View的布局参数,包括width,height,margin等

3. MeasureSpec和LayoutParams关系

顶级DecorView:由于没有父容器,因此MeasureSpec由屏幕尺寸和自身LayoutParams决定

普通view:其MeasureSpec由父容器的MeasureSpec和自身LayoutParams决定

3.1 DecorView:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

其中desiredWindowWidth、desiredWindowHeight为屏幕尺寸,我们来看一下getRootMeasureSpec()

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            //MATCH_PARENT,精确模式时,大小为窗口大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            //WRAP_CONTENT,最大模式时,大小不定,但是不能超过最大值,也就是窗口的大小
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            //固定大小 如100dp,为LayoutParams指定的大小
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
3.2 普通view

普通view的MeasureSpec由父容器根据自身MeasureSpec和子view的LayoutParams计算而来,measureChildWithMargin()

 protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

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

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

首先会获取child的LayoutParams然后调用getChildMeasureSpec()方法分别计算出child的宽高规格,计算childWidthMeasureSpec时传入的参数为parentWidthMeasureSpec,左右padding和margin,以及child自身的width。

我们来看看getChildMeasureSpec做了什么

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

先取出父容器的测量规格和减去间距后剩余大小

switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

接着,根据父容器的SpecMode和自身的宽高值开始计算测量规格,

  1. 当父容器为EXACTLY时:
    • childDimension > 0,即确定值如100dp,那么子view的测量模式为EXACTLY,大小就是childDimension给定的大小。
    • childDimension == MATCH_PARENT,那么子View测量模式为EXACTLY,大小为父容器剩余大小specSize - padding。
    • childDimension == WRAP_CONTENT,那么子View测量模式为AT_MOST,大小不超过父容器剩余空间
  2. 当父容器为AT_MOST时:
    • childDimension > 0,即确定值如100dp,那么子view的测量模式为EXACTLY,大小就是childDimension给定的大小。
    • childDimension == MATCH_PARENT,那么子View测量模式为AT_MOST,大小为父容器剩余大小specSize - padding。
    • childDimension == WRAP_CONTENT,那么子View测量模式为AT_MOST,大小不超过父容器剩余空间

如图:

在这里插入图片描述

4.Measure过程

单一view只需完成自身测量,而ViewGroup除了完成自身测量,还要去遍历调用子元素的measure

4.1 view的measure过程

测量流程由measure方法开始,然后在其中调用onMeasure()

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

我们先看看setMeasuredDimension,只是用于设置view的宽高测量值

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

接着看getDefaultSize()

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

当view测量模式为EXACTLY或者AT_MOST时,返回的就是measureSpec中的大小。上面代码中的size由getSuggestedMinimumWidth()计算而来

protected int getSuggestedMinimumWidth() {
   return (mBackground == null) ? mMinWidth:max(mMinWidth,mBackground.getMinimumWidth());
}

当view设置了背景时,返回值为mMinWidth与mBackground大小两者的最大值,mMinWidth通过android:minWidth设置,默认为0。

4.2 viewGroup的measure过程

viewGroup除了完成自身测量过程,还会遍历调用子view的measure。ViewGroup中并没有重写onMeasure,而是提供了一个measureChildren方法

    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,计算出子view的measureSpec,然后调用其measure方法,我们看下measureChild方法

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

就是根据父容器的measureSpec和子View的layoutParams计算出子View的measureSpec。getChildMeasureSpec方法我们上面已经分析过了。

值得注意的是,ViewGroup并没有对测量过程进行统一实现,因为不同的布局各有特点,所以onMeasure由ViewGroup子类根据自身特点去实现,比如LinearLayout,RelativeLayout

5.Layout过程

Layout是用来确定元素位置的,layout方法确定View本身的位置,然后在其中调用onLayout方法确定子元素位置。

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

在layout方法中首先会调用setFrame初始View四个顶点的位置,这样自身的位置也就确定了,接着调用onLayout确定子元素位置。onLayout在View中是空实现,交由具体的布局去实现

5.2 ViewGroup的Layout过程

以LinearLayout为例

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

根据orientation不同调用layoutVertical或者layoutHorizontal方法,我们只看layoutVertical()

void layoutVertical(int left, int top, int right, int bottom) {
    ...
	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 += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

遍历子view,获取累计每个子view的高度和间距,然后调用setChildFrame,这个方法会调用子View的layout方法,从而确定子View的位置,因为没确定一个子View,childTop会逐渐增大,也就使得子View按照从上到下的方式线性排列。

6.Draw过程

步骤

  1. 绘制背景 drawBackground(canvas)
  2. 绘制自身内容 onDraw()
  3. 绘制子View dispatchDraw()
  4. 绘制装饰 onDrawScrollBars()
public void draw(Canvas canvas) {
       ...

        
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);// Step 1, draw the background, if needed
        }

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

            drawAutofilledHighlight(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);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            return;
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值