Android view绘制流程详解(二)View的绘制

Android view绘制流程详解分为两部分:

ViewTree的生成

View的绘制

View的绘制流程

一、ActivityThread的RESUME_ACTIVITY消息

当Activity的H接收到RESUME_ACTIVITY消息的时候,调用了handleResumeActivity方法。

case RESUME_ACTIVITY:
    SomeArgs args = (SomeArgs) msg.obj;
    handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
            args.argi3, "RESUME_ACTIVITY");
    break;

在handleResumeActivity中:
1、获取Activity
2、通过activity获取phoneWindow
3、通过phoneWindow获取DecorView
4、通过WindowManagerImpl.addView(),将DevorView传入

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    r = performResumeActivity(token, clearHide, reason);
        final Activity a = r.activity;
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();   getWindow获取PhoneWindow对象
            View decor = r.window.getDecorView();通过PhoneWindow获取DecorView对象。
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();获取windowManager实现类WindowManagerImpl
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
TYPE_BASE_APPLICATION这个类型表示这个window是所有程序的基础window,所有其他程序都显示在其上面
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
        } else if (!willBeVisible) {
            r.hideForNow = true;
        }
。。。。。。。。
}
WindowManagerImpl.java中调用了WindowManagerGlobal类中的addView方法。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal.java中首先创建一个RootViewImpl对象,然后调用它的setView()方法。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow{
    ViewRootImpl root;
    synchronized (mLock) {
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
    root.setView(view, wparams, panelParentView);
}

perforTraversals()绘制入口

view的绘制流程就是perforTraversals()方法开始的,绘制流程主要分为三个步骤:

  1. 测量:performMeasure()
  2. 布局:performLayout()
  3. 绘制:performDraw()

步骤一、测量:

  1. MeasureSpec类:它是View的一个静态内部类,MeasureSpec用来封装从父容器传递过来的对子容器的布局要求,它是(子view自身的LayoutParams参数,就是我们在xml布局文件中写的layout_width、layout_height的值转化过来的)和(父容器对子view的约束要求的measureSpec)这两者经过计算得出来的对子view的约束measureSpec。通过一个32位的值来表示测量结果,高2位是mode,低30位是大小。mode分为3种:UNSPECIFIED(0)、EXACTLY(1):具体大小、AT_MOST(2):最大值如wrap_content。
  2. Measure关键方法一、measureChildWithMargin(),其中有个getChildMeasureSpec()方法用来测量子view的尺寸,共9中情况需要知道。
  3. Measure关键方法二、setMeasuredDimension()方法,每个view重写onMeasure方法后都需要通过这个方法来保存测量尺寸,保存完了之后,getMeasureWidth()才有效。
  4. 当子view遍历测量结束之后,将每个view的宽高分别进行累加
private void performTraversals() {
这个mView在setView中传入,其实就是DecorView
    final View host = mView;
    mIsInTraversal = true;//是否正在遍历
    mWillDrawSoon = true;//是否马上需要绘制View
    boolean windowSizeMayChange = false;
    boolean newSurface = false;
    boolean surfaceChanged = false;
这个mWindowAttributes在serView种赋值,其实就是phoneWindow的属性
    WindowManager.LayoutParams lp = mWindowAttributes;
顶层视图DecorView所需要的窗口的宽度和高度
    int desiredWindowWidth;
    int desiredWindowHeight;
 .................
    Rect frame = mWinFrame;
    if (mFirst) { //在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
        mFullRedrawNeeded = true;
        mLayoutRequested = true;
        //这个判断决定了DecorView的高度是否包括状态栏
        if (shouldUseDisplaySize(lp)) {
            Point size = new Point();
            mDisplay.getRealSize(size);
            desiredWindowWidth = size.x;
            desiredWindowHeight = size.y;
        } else {
            Configuration config = mContext.getResources().getConfiguration();
            desiredWindowWidth = dipToPx(config.screenWidthDp);
            desiredWindowHeight = dipToPx(config.screenHeightDp);
        }
        mAttachInfo.mUse32BitDrawingCache = true;
        mAttachInfo.mHasWindowFocus = false;
        mAttachInfo.mWindowVisibility = viewVisibility;
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mLastConfiguration.setTo(host.getResources().getConfiguration());
        mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
        if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
            host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
        }
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
        dispatchApplyInsets(host);
    } else {
        desiredWindowWidth = frame.width();
        desiredWindowHeight = frame.height();
        if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            windowSizeMayChange = true;
        }
    }
    ..............
    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        mForceNextWindowRelayout = false;
        mAttachInfo.mWindowLeft = frame.left;
        mAttachInfo.mWindowTop = frame.top;
        if (mWidth != frame.width() || mHeight != frame.height()) {
            mWidth = frame.width();
            mHeight = frame.height();
        }
        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
主要就是以下三句话:
1、基于PhoneWindow的lp属性,在PhoneWindow中为rootView找到MeasureSpec。mWidth和mHeight表示PhoneWindow可用的宽高,lp.widthhe和lp.height表示PhoneWindow的宽高的lp值。最终返回值是rootView的MeasureSpec值。rootView也就是DecorView。
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
2、核心语句在这里:执行测量的操作
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;
                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                //如果当设置了权重不为0的时候,需要在执行一次测量,所以网上很多说relativeLayout测量两次,LinearLayout测量一次,其实是有条件的
                if (measureAgain) {
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
                layoutRequested = true;
            }
        }
    } else {
        maybeHandleWindowMove(frame);
    }
执行测量操作,mView是DecorView extends FramLayout extends ViewGroup extends Viewprivate void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
     mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

View.java 这里measure是用final修饰的,因此,不能被重写,所以view.measure()或者child.measure()方法进入都从这里开始分发。传入的参数用来限制宽高的大小,真正测量在onMeasure中实现。所以这里直接跳到自定义的View中看onMeasure即可,这里的第一个实现view就是DevorView。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
.........
     // measure ourselves, this should set the measured dimension flag back
     onMeasure(widthMeasureSpec, heightMeasureSpec);
.........
}
当子view在重写onMeasure的时候,必须重写serMeasureDimension()方法,这个方法不然会报IllegalStateException异常。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
可以看到这个setMeasureDimension方法就是用来保存mMeasureWidth和mMeasureHeight的值的。比如Linear Layout、relativeLayout等都在onMeasure的最后重写了setMeasureDimension方法,只有重写这个方法以后,getMeasureWidth获取值才有效。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
...............
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

DevorView.java中的onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
............这里一堆先计算出自身的MeasureSpec对象,super指的是FramLayout,在super中遍历计算出每个子view的空间大小..........
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
子view遍及计算结束后,回到这里继续执行代码
............
}
FramLayout.java中的中的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
这是measure的关键方法。measureChildWithMargins方法用来测量子view所需要的MeasureSpec对象,需要在ViewGroup中统一实现,因为每个容器都会用到。
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
..............
遍历子view测量结束后,又回到这里,通过serMeasureDimension方法设置。
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
}
ViewGroup中的measureChildWithMargins实现方法,通过getChildMeasureSpec方法计算出子view的约束性空间MeasureSpec,然后继续通过child.measure()方法传递给子view。
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);
}

这个方法是传递measureSpec的关键,分9中情况(wrap_content的计算方法):
如果父view是EXCACTLY:

  1. 子view值确定,size = childDimension ;mode = EXCACTLY;
  2. 子view == MATCH_PARENT,size = size ;mode = EXACTLY;
  3. 子view == WRAP_CONTENT,size = size ;mode = AT_MOST;

如果父view是AT_MOST:

  1. 子view值确定,size = childDimension ;mode = EXCACTLY;
  2. 子view == MATCH_PARENT,size = size ;mode = AT_MOST;举例,linearLayout中上面固定高度,下面match_parent的场景,只能说最大时父布局大小。
  3. 子view == WRAP_CONTENT,size = size ;mode = AT_MOST;

如果父view是UNSPECIFIED:

  1. 子view值确定,size = childDimension ;mode = UNSPECIFIED;
  2. 子view == MATCH_PARENT,api23之前时0,之后是size = size ;mode = UNSPECIFIED;
  3. 子view == WRAP_CONTENT,api23之前时0,之后是size = size ;mode = UNSPECIFIED;
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) {
    case MeasureSpec.EXACTLY:    
        if (childDimension >= 0) {    
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

Measure大致流程总结:
测量的关键方法是measureChildWithMargin(),里面通过调用getChildMeasureSpec方法计算出子view所需要的空间大小。

步骤二、布局

  1. layout和measure的区别在与layout的入口是在ViewGroup中,因为只有容器才需要将自己的child执行layout。
  2. 关键方法一、setFrame(),这个方法中设置了当前View的左上右下的值,并且返回是否改变位置。
  3. 关键方法二、layoutChildren(),跟measure一样,遍历子view放在FramLayout中实现,通过layoutChildren()方法根据本view的左上右下的值,以及子view的layoutParams属性(gravity和margin的值)计算出子view的左上右下的值。

注意: getWidth()和getHeight()是在setFram()方法之后调用才有效。所以在onCreate中直接获取view的宽高是获取不到的,需要用到ViewTreeObserver方法。

当performMeasure方法执行顺利结束后,会将layoutRequested 赋值为ture结束后,然后继续执行布局。Layout是根据performMeasure方法测量的值和view的属性来确定view摆放的位置。

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
这里很干脆利索,条件成立进来第一件事就是performLayout;
        performLayout(lp, mWidth, mHeight);
        .............
    }
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;
    final View host = mView;
这里跟measure一样,直接调用了DecorvVew的layout,layout跟measure的区别是只有容器才需要layout,因此这里入口layout也就是final修饰的layout在viewGroup中,因此调用的是ViewGroup.layout();
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
	............
}
ViewGroup.java中的layout方法,这里跟measure一样,也是个final修饰,继承者不能重写。因此只要调用view.layout(),或者child.layout()都从这里进入。
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
这里直接调用了super的layout方法;
        super.layout(l, t, r, b);
    } else {
        mLayoutCalledWhileSuppressed = true;
    }
}
View.java中的layout方法,这个是layout的关键部分,layout布局就是通过这里的setFrame方法设置左上右下四个点的。
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;
这里通过setFrame方法给当前View的左上右下四个值赋值,并判断View是否发生变化,这个方法放在第一步,基本上通过这个方法就能确定子view摆放的大概位置了。
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
这里跟measure一样,如果当前View发生变化了或者调用了requestLayout,那么就调用onLayout()方法;
        onLayout(changed, l, t, r, b);
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        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中的onLayout方法是一个空方法,onLayout是父容器布局子容器用的,所以在view中实现onLayout没有意义,只有ViewGroup中才有意义
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
DecorView.java,onLayout的第一个实现类当然是在DecorView中了,这里进来就调用了super方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    getOutsets(mOutsets);
    if (mOutsets.left > 0) {
        offsetLeftAndRight(-mOutsets.left);
    }
    if (mOutsets.top > 0) {
        offsetTopAndBottom(-mOutsets.top);
    }
    if (mApplyFloatingVerticalInsets) {
        offsetTopAndBottom(mFloatingInsets.top);
    }
    if (mApplyFloatingHorizontalInsets) {
        offsetLeftAndRight(mFloatingInsets.left);
    }
    updateElevation();
    mAllowUpdateElevation = true;
    if (changed && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER) {
        getViewRootImpl().requestInvalidateRootRenderNode();
    }
}
FramLayout.java,这里跟measure一样,也是在FramLayout中遍历子view来确定各个子view的位置。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
这里根据父view的坐标位置,以及子view的属性值,如measureWidth、measureHeight、marging、gravity等计算出子view的位置。然后通过child.layout方法层层传递。
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();
    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
            int childLeft;
            int childTop;
            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }
            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
            //根据位置属性计算出view应该摆放的位置坐标。
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
            //计算完当前的位置坐标后,然后继续递归遍历子view
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

Layout大致流程:首先到达ViewGroup中的final layout方法,这个方法是final,然后再super到view中的layout方法,这个中通过setFrame方法计算出左上右下的值,然后开始调用onLayout方法,这在DecorView中也就是自定义布局的view中通过重写onLayout方法重新给子view进行布局摆放

步骤三、绘制

performDraw——draw(得到一个surface)判断是否开启硬件加速,drawSoftware(通过surface.lockcanvas()获取canvas)——canvas.setDensity(mDensity)设置画布的尺寸——view.draw()判断是否透明、绘制滚动条等一些操作,等然后通过onDraw和dispatchDraw去分发消息。注意:过度绘制现象如何避免,1、打开设置的“GPU呈现模式分析”:渲染界面所用的耗时;“调试GPU过度绘制”:分析界面层级:蓝绿粉红;2、通过clipRect方法去进行裁剪。
1、了解surface、window、view之间的关系:
简单来讲,Widown是用来展示surface的,对于非view来说,比如surfaceView和textureView都可以直接获取surface然后通过window加载,但是view需要通过viewRootImpl转为surface。

boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    if (!cancelDraw && !newSurface) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }
        这里进行绘制操作
        performDraw();
    } else {
        if (isViewVisible) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    mIsInTraversal = false;
}

问题:wrap_content是如何计算。就是那9种情况

引申:动态换肤
动态换肤主要分为两个步骤:
步骤一:重写LayoutInflater的Factory2(为了兼容AppcompotActivity用LayoutInflaterFactory)来重写onCreateView()方法,这个内部类是API11才添加进来的,我们可以在这个方法中重新解析view以及对应的属性,并将他们添加到内存集合中去备用。
步骤二:加载并解析apk资源包,然后获取其中的资源文件,然后通过缓存的view和属性直接进行设置。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值