博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android View绘制流程(源码 API27)
阅读量:6415 次
发布时间:2019-06-23

本文共 21082 字,大约阅读时间需要 70 分钟。

ViewGroup继承自View,ViewGroup是一个包含View的容器。

接口ViewManager里有addView、updateViewLayout、removeView方法,添加、更新、移除方法。

同时ViewGroup是个抽象类,不能直接使用,常用的子类有LinearLayout、relativeLayout、constrainstLayout、frameLayout、CoordinatorLayout等。

public abstract class ViewGroup extends View implements ViewParent, ViewManager 复制代码

我们自定义View时,往往需要重写onMeasure()、onDraw()

自定义ViewGroup时,往往需要重写onMeasure()、onLayout()、onDraw()

这里我们来分析下View的绘制流程。

ViewRootImpl#performTraversals()

  • //执行测量过程,performMeasure() -> view.measure()
  • //执行布局过程,performLayout() -> view.layout()
  • //执行绘制过程,performDraw() -> view.draw()
private void performTraversals() {                    WindowManager.LayoutParams lp = mWindowAttributes;                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);                    //执行测量过程                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                    //执行布局过程                     performLayout(lp, mWidth, mHeight);                    //执行绘制过程                     performDraw();}复制代码

ViewRootImpl#getRootMeasureSpec() 根据

private static int getRootMeasureSpec(int windowSize, int rootDimension) {        int measureSpec;        switch (rootDimension) {        case ViewGroup.LayoutParams.MATCH_PARENT:            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);            break;        case ViewGroup.LayoutParams.WRAP_CONTENT:            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);            break;        default:            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);            break;        }        return measureSpec;    }复制代码

ViewRootImpl#performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }复制代码

ViewRootImpl#performLayout()

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,            int desiredWindowHeight) {        mInLayout = true;        final View host = mView;            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());        mInLayout = false;    }复制代码

ViewRootImpl#performDraw()

private void performDraw() {         final boolean fullRedrawNeeded = mFullRedrawNeeded;        mFullRedrawNeeded = false;        try {            draw(fullRedrawNeeded);        } finally {            mIsDrawing = false;            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }复制代码

ViewRootImpl#draw()

private void draw(boolean fullRedrawNeeded) {        Surface surface = mSurface;                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {                    return;                }        }    }复制代码

ViewRootImpl#drawSoftware()

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,            boolean scalingRequired, Rect dirty) {        final Canvas canvas;            canvas = mSurface.lockCanvas(dirty);            canvas.translate(-xoff, -yoff);            mView.draw(canvas);        return true;    }复制代码

MeasureSpec

MeasureSpec是View中的一个子类,代表的是测量规格:32位的int类型,高两位是mode,低30位是size

Mode有三种状态:

  • UNSPECIFIED :父控件没有给子view任何限制,子View可以设置为任意大小。linearLayout/scrollView (随心所欲)
  • EXACTLY:父View有确切的大小,子View (match_parent、dp/px)必须是这个范围内 (固定大小)
  • AT_MOST:父View为子view指定了一个范围,在这个范围内,子View可以尽可能的大,wrap_content(受限制的)
public static class MeasureSpec {        private static final int MODE_SHIFT = 30;        private static final int MODE_MASK  = 0x3 << MODE_SHIFT; // 11 0000000000000(后接30个0)        public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 000000000000000(后接30个0)        public static final int EXACTLY     = 1 << MODE_SHIFT; // 01 000000000000000(后接30个0)        public static final int AT_MOST     = 2 << MODE_SHIFT; // 10 000000000000000(后接30个0)        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);            }        }        //得到测量模式        @MeasureSpecMode        public static int getMode(int measureSpec) {            //noinspection ResourceType            return (measureSpec & MODE_MASK);        }        //得到测量尺寸        public static int getSize(int measureSpec) {            return (measureSpec & ~MODE_MASK);        }    }复制代码

View#measure()

**View#measure()**是final类型,子类不能重写,自定义View/ViewGroup往往重写的是onMeasure()

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        //如果和父类的optical bounds不一样,则需要更新下widthMeasureSpec和heightMeasureSpec                onMeasure(widthMeasureSpec, heightMeasureSpec);    }复制代码

View#onMeasure()

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

View#setMeasuredDimension() final类型,子类不能重写

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {        //还是与那个optical bounds有关,如果自己和父类不一致,可能会更新measuredWidth,measuredHeight        setMeasuredDimensionRaw(measuredWidth, measuredHeight);    }复制代码

View#setMeasuredDimensionRaw() 给mMeasuredWidth、mMeasuredHeight赋值

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {        mMeasuredWidth = measuredWidth;        mMeasuredHeight = measuredHeight;    }复制代码

View#getDefaultSize()

measureSpec 是由父类传递给子类的测量规格,int类型,32个bit,高两位是模式,低30是尺寸。

  • 如果父类是specMode是unspecified(随你所想):则自己取size
  • 如果父类的specMode是at_most(受限)或者exactly(固定尺寸),则自己取specSize
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#getSuggestedMinimumWidth()

mBackground.getMinumWidth()返回背景图Drawable的原始宽度,若无原始宽度,则为0;

  • 注:BitmapDrawable有原始宽度,而ShapeDrawable没有
protected int getSuggestedMinimumWidth() {        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());    }          protected int getSuggestedMinimumHeight() {        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());    }复制代码

View的测量过程总结一下:

根据widthMeasureSpec和heightMeasureSpec

LinearLayout#onMeasure()

前面我们说了View#measure()是final类型,不能被重写,需要通过重写onMeasure()来实现自己的尺寸测量

但是ViewGroup中既没有measure()方法,也没有onMeasure()方法)

因为不同的ViewGroup(LinearLayout、relativeLayout、constrainstLayout、frameLayout)有不同的布局特性,,测量自己大小的方式也不一样,比如说LinearLayout会根据orientation,在垂直方向或者水平方向进行叠加;FrameLayout会取子View里最大的一个作为自己的大小;所以,ViewGroup的子类需要各自实现onMeasure()方法。

这里我们以LinearLayout为例: 先看个图,省略部分细节

LinearLayout#onMeasure()

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

LinearLayout#measureVertical()

    1. 遍历所有子View & 测量:measureChildren()
    1. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
    1. 存储测量后View宽/高的值:调用setMeasuredDimension()
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {        final int count = getVirtualChildCount(); // childCount        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            final LayoutParams lp = (LayoutParams) child.getLayoutParams();            totalWeight += lp.weight; //如果有weight属性,需要进行二次测量            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;                //测量子View并给子View的widthMeasureSpec/heightMeasureSpec赋值                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,                        heightMeasureSpec, usedHeight);                 final int childHeight = child.getMeasuredHeight();                final int totalLength = mTotalLength; //h用于存储LinearLayout在竖直方向的高度                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                       lp.bottomMargin + getNextLocationOffset(child)); //每次测量完child会进行叠加        }        setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),                resolveSizeAndState(maxHeight, heightMeasureSpec,                        (childState<

LinearLayout#measureChildBeforeLayout()

void measureChildBeforeLayout(View child, int childIndex,            int widthMeasureSpec, int totalWidth, int heightMeasureSpec,            int totalHeight) {        measureChildWithMargins(child, widthMeasureSpec, totalWidth,                heightMeasureSpec, totalHeight);    }复制代码

ViewGroup#measureChildWithMargins()

  • 根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec,但是这不是子View最终的measureSpec(前面我们分析的时候还有getDefaultSize())

  • child.measure(childWidthMeasureSpec, childHeightMeasureSpec)

    View#measure(widthMeasureSpec,  heightMeasureSpec)   View.onMeasure(widthMeasureSpec,  heightMeasureSpec)  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),                          getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec))复制代码
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);    }复制代码

ViewGroup#getChildMeasureSpec()

根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec

  • 如果子view的layout_width是固定的dp/px,则子View的MeasureSpec = exactly + childSize

  • 如果子view的layout_width是match_parent

    如果父View是exactly,则子View与父View等大,子View的MeasureSpec = exactly + parentSize  如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize  如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 复制代码
  • 如果子View的layout_width是wrap_content

    如果父View是exactly,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize  如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize  如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 复制代码

针对MeasuerSpec = unspecified + 0的状况,会通过child.measure -> getDefaultSize() -> getSuggestedMinumSize()来设定。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {        int specMode = MeasureSpec.getMode(spec);        int specSize = MeasureSpec.getSize(spec);//parentSize是 Math.max(0, specSize - padding) ,padding是所有已用空间+padding+margin        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 not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent has imposed a maximum size on us        case MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size, but our size is not fixed.                // Constrain child to not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size. It can not be bigger than us.                resultSize = size;                resultMode = MeasureSpec.AT_MOST;            }            break;        // Parent asked to see how big we want to be        case MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have it                resultSize = childDimension;                resultMode = MeasureSpec.EXACTLY;            } else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            } else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should be                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;                resultMode = MeasureSpec.UNSPECIFIED;            }            break;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }复制代码

view#layout()

  • View的最终的布局位置和大小完全由mLeft、mTop、mRight、mBottom这4个参数决定,确定了左上角的位置和宽高也就知道四个点的位置。

    (mLeft,mTop, mRight, mBottom)  (mLeft,mTop, mLeft + getMeasuredWidth(), mTop + getMeasuredHeight())复制代码
  • 也就是说在layout过程中,通过getMeasuredWidth()、getMeasuredHeight得到view的宽度、高度

    getMeasuredWidth() = mMeasuredWidth & MEASURED_SIZE_MASK // mMeasuredWidth的低30位  getMeasuredHeight() = mMeasuredHeight & MEASURED_SIZE_MASK // mMeasuredHeight的低30位复制代码
  • layout之后可以通过getWidth()、getHeight()得到view宽度、高度

    getWidth() = mRight - mLeft  getHeight() = mBottom - mTop复制代码
  • 一般来说getMeasuredWidth() == getWidth(),也可以不一样。

public void layout(int l, int t, int r, int b) {    //存储旧值,用于onLayoutChangeListener        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);                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);                     }    }复制代码

View#setFrame()

protected boolean setFrame(int left, int top, int right, int bottom) {        boolean changed = false;        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {            changed = true;            //用于onSizeChanged的监听            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; //设置一批新的left、top、right、bottom            mTop = top;            mRight = right;            mBottom = bottom;            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);            if (sizeChanged) {                sizeChange(newWidth, newHeight, oldWidth, oldHeight);            }        }        return changed;    }复制代码

View#onLayout() View没有子View,所以onLayout是个空的实现

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }复制代码

ViewGroup#layout()

final方法,子类不能重写,会调用super.super.layout(l, t, r, b)

@Override    public final void layout(int l, int t, int r, int b) {            super.layout(l, t, r, b);    }复制代码

ViewGroup#onLayout()

因为不同的ViewGroup具有不同的布局特性,所以定义为抽象方法,所有继承自ViewGroup的都需要重写onLayout()

@Override    protected abstract void onLayout(boolean changed,            int l, int t, int r, int b);复制代码

LinearLayout#onLayout()

这里仍然以LinearLayout的orientation为vertical的为例

@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);        }    }复制代码

LinearLayout#layoutVertical()

  • 遍历子View,对子View进行摆放。

    * 根据android:gravity属性对childTop进行调整  * 根据android:gravity属性对childLeft进行调整  * setChildFrame(child, childLeft, childTop, childWidth, childHeight)复制代码
void layoutVertical(int left, int top, int right, int bottom) {        int childTop;        int childLeft;        final int count = getVirtualChildCount();//根据android:gravity属性对childTop进行调整        for (int i = 0; i < count; i++) {            final View child = getVirtualChildAt(i);                         final int childWidth = child.getMeasuredWidth(); //measureSize,所以要先measure再layout                final int childHeight = child.getMeasuredHeight();                final LinearLayout.LayoutParams lp =                        (LinearLayout.LayoutParams) child.getLayoutParams();//根据android:gravity属性对childLeft进行调整                childTop += lp.topMargin;                setChildFrame(child, childLeft, childTop + getLocationOffset(child),                        childWidth, childHeight);                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);            }        }    }复制代码

View#getMeasureWidth()

public final int getMeasuredWidth() {        return mMeasuredWidth & MEASURED_SIZE_MASK;    }复制代码

LinearLayout#setChildFrame()

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

View#draw()

  • 绘制背景
  • 绘制view内容
  • 绘制children
  • 绘制装饰
public void draw(Canvas canvas) {        if (!dirtyOpaque) {            drawBackground(canvas);        }        if (!verticalEdges && !horizontalEdges) {            if (!dirtyOpaque) onDraw(canvas);            dispatchDraw(canvas);            onDrawForeground(canvas);            drawDefaultFocusHighlight(canvas);    }复制代码

View#onDraw() 自定义View都要重写onDraw,比如说textView/button/imageView啦

protected void onDraw(Canvas canvas) {    }复制代码

ViewGroup#dispatchDraw()

protected void dispatchDraw(Canvas canvas) {        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);        final int childrenCount = mChildrenCount;        final View[] children = mChildren;            for (int i = 0; i < childrenCount; i++) {                    more |= drawChild(canvas, transientChild, drawingTime);            }      }复制代码

ViewGroup#drawChild()

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {        return child.draw(canvas, this, drawingTime);    }复制代码

LayoutParams

ViewGroup.LayoutParams

public static class LayoutParams {        public static final int FILL_PARENT = -1;        public static final int MATCH_PARENT = -1;        public static final int WRAP_CONTENT = -2;        public int width;        public int height;        public LayoutParams(int width, int height) {            this.width = width;            this.height = height;        }    }复制代码

ViewGroup.MarginLayoutParams

public static class MarginLayoutParams extends ViewGroup.LayoutParams {          public int leftMargin;        public int topMargin;        public int rightMargin;        public int bottomMargin;        private int startMargin = DEFAULT_MARGIN_RELATIVE;        private int endMargin = DEFAULT_MARGIN_RELATIVE;    }复制代码

LinearLayout.LayoutParams

ViewGroup的继承类都有其各自不同的特性,LayoutParams就是它们的属性特征。

public static class LayoutParams extends ViewGroup.MarginLayoutParams {        public float weight;        public int gravity = -1;        public LayoutParams(Context c, AttributeSet attrs) {            super(c, attrs);            TypedArray a =                    c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);            a.recycle();        }    }复制代码

参考:

转载于:https://juejin.im/post/5b6bd16f5188251ac5552396

你可能感兴趣的文章
马哥linux作业--第八周
查看>>
dubbo01
查看>>
python 写json格式字符串到文件
查看>>
分布式文件系统MogileFS
查看>>
电力线通信载波模块
查看>>
linux vim详解
查看>>
Java23种设计模式案例:策略模式(strategy)
查看>>
XML解析之DOM4J
查看>>
图解微服务架构演进
查看>>
SQL PATINDEX 详解
查看>>
一些常用的网络命令
查看>>
CSP -- 运营商内容劫持(广告)的终结者
查看>>
DIV+CSS命名规范有助于SEO
查看>>
js生成二维码
查看>>
C指针练习
查看>>
web项目buildPath与lib的区别
查看>>
php对redis的set(集合)操作
查看>>
我的友情链接
查看>>
ifconfig:command not found的解决方法
查看>>
js使用正则表达式判断手机和固话格式
查看>>