layout优化之view绘制流程

layout优化之view绘制流程


在加载了layoutxml文件创建view后,本文主要讲解view的显示过程,因为这个一般是不做修改的,我们这里只是大致的描述一下,不做详细的分析。

view从加载到绘制一般要经过三个阶段:measurelayoutdraw。我们在重写view的时候一般情况下重写的是draw方法,这个负责具体的显示界面,前两个流程主要是测量定位view的位置,以及相关的属性。



首先我们看下measure方法,这个是在整个过程中是相对比较难理解的,其中又根据父控件的属性不同划分为三种模式,一种是upspecifiedexactly,at-most,其中upspecified是无限制的,想多大就多大,一般来说针对的属性是fill_parent,match_patent等父控件没有规定大小的时候,子空间想多大就多大。

exactly模式应该说是我们写作的时候都固定了大小直接把viewheightwidth的大小固定,如:android:layout_height=”100dp”android:layout_width=”200dp”

at-most模式应该是规定父控件的大小,view只能在一定的范围内进行变化,但是并不能超过parent的大小。

Viewmeasure主要是根据parentview的参数和自身的参数进行测量,在onmeasure方法中进行具体的操作。V

ViewGroup类没有具体的是现实onmeasure方法,其实我们知道具体的测量都是在onMeasure方法中进行实现的,我们去查找子类发现framelayoutlinearlayout等有具体实现onMeasure方法,现在我们看下framelayoutonmeasure方法的实现。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            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);
                }
            }
        }
    }

    // Account for padding too
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));

    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(0, getMeasuredHeight()
                        - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                        - lp.topMargin - lp.bottomMargin);
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        height, MeasureSpec.EXACTLY);
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                        getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                        lp.topMargin + lp.bottomMargin,
                        lp.height);
            }

            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

源码好长,从源码中我们能看到framelayout的大小主要是根据subview的大小和其他的一些因素共同决定的。从代码来看所有的孩子测量之后,经过一系类的计算之后得到寬高的最大值,这个值就是Framelayout的寬高,然后在通过setMeasuredDimension设置自己的宽高,FrameLayout可能用最大的子View的大小。对于LinearLayout,可能是高度的累加,也有可能是宽度的叠加。总的来说,父View是等所有的子View测量结束之后,再来测量自己,根据子view值进行一定的计算,然后得到自己的寬高。

measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程

layout的过程

在完成了measure后就会开始layout过程,整个layout过程比较容易理解,layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measureView所得到的布局大小和布局参数,将子View放在合适的位置上。整个layout的核心主要是一下几点

1,View.layout方法可被重载,ViewGroup.layoutfinal的不可重载,ViewGroup.onLayoutabstract的,子类必须重载实现自己的位置逻辑

2,measure操作完成后得到的是对每个View经测量过的measuredWidthmeasuredHeightlayout操作完成之后得到的是对每个View进行位置分配后的mLeftmTopmRightmBottom,这些值都是相对于父View来说的

3,ViewgetWidth()getHeight()方法来获取View测量的宽高,必须在onlayout结束后才有实际的值,意思就是在我们加载xml前取view的高和寬都是错误的。

4,layout必须是有parent的,没有parentlayout是没有意义的

Draw的过程

Draw发生在measurelayout之后进行,此时view的大小和位置都已经固定,显示的位置也一定固定,只是还么有显示出来,draw就是把具体的需要展现给用户的效果给显示出来,其实整个draw的过程就是在一个Canvas上做画的过程。具体是在PhoneWindow.DecorViewViewRootImpl中的代码会创建一个Canvas对象,然后调用Viewdraw()方法来执行具体的绘制工,若是viewgroup的话会执行递归subviewdraw方法进行subview的绘制。

现在我们查看一下viewdraw方法中具体的实现,这个是view展现给我们的效果,所以我们需要详细的分析下

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;

    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      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);

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

        // we're done...
        return;
    }

第一步:背景绘制,这个基本上每个view都会绘制

从代码来看在画背景的时候,当背景为null的时候是不进行绘制的,直接返回。这个主要看注释就可以了,不做过多解释。

第二步:view内容的绘制

onDraw(canvas)方法是view用来draw自己的,在这里是一个null的方法,因为具体如何绘制,颜色线条什么样式就需要子View自己去实现,ViewGroup也没有实现。因为每个View的内容是各不相同的,所以需要由子类去实现具体逻辑。不同的view画出的效果不同。

第三步:对子view的绘制

dispatchDraw(canvas)方法是用来绘制子View的,但是在view方法中没有实现这个方法,只有在viewGroup中才会实现此问题。

剩下的几步主要也是对各种条件的绘制,焦点高亮滑动条等,基本上和绘制自己一致,这里不再做详细的分析

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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值