安卓复习之旅—View的工作流程2

上一偏文章主要了解了一下ViewRoot和DecorView 以及MeasureSpec ,这篇文章主要讲解View的三大流程;

measure过程
measure过程要分两种情况,一种是普通view,一种是viewgroup:

  • 1.view的measure
    view的measure方法是一个final类型的方法,意味着不能重写这个方法,在这个方法中调用了onMeasure方法,我们可以重写onMeasure方法,
 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

          ...
    }

看看onMeasure方法的实现:

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

调用setMeasuredDimension方法设置view的测量值,其中getSuggestedMinimumWidth()是系统默认提供的值,这里就不深究了,下面看看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;
    }

在UNSPECIFIED模式下,返回系统默认值,EXACTLY和AT_MOST模式下,返回measureSpec中的值;这里也可以得到一个结论:当我们直接继承一个view的自定义控件需要重写onMeasure方法去设置wrap_content时的自身大小,否则在布局中使用wrap_content的效果与match_parent一样,原因是当view设置为wrap_content时,模式是AT_MOST,通过前一篇文章中的getChildMeasureSpec方法可以知道,view的specSize就是parentSize;重写onMeasure的代码思路如下:

 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);


        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(viewWidth, viewHeight);
        }
        else if(widthMode == MeasureSpec.AT_MOST ){
            setMeasuredDimension(viewWidth, heightSize);

        }
        else if(heightMode == MeasureSpec.AT_MOST ){
            setMeasuredDimension(widthSize, viewHeight);

        }
  • 2.viewgroup的measure

对于viewgroup来说,出了完成自己的measure过程,还要遍历子view的measure方法,各个子view完成对自身的measure过程,与view不同的是,viewgroup是一个抽象类,它是通过measureChildren()方法开始measure,先看看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);
            }
        }
    }

从以上代码可以看出,确实是会通过measureChild方法对每一个子view进行measure,

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

measureChild方法先得到子view的MeasureSpec,然后再调用子view的measure方法;

view的measure过程是三大流程中最复杂的一个,measure完成以后,通过getMeasuredHeight()和getMeasuredWeight()可以得到view的测量高宽,但是在某些极端情况下,系统可能会多次measure才能得到最终的值,在这种情况下,在onMeasure中可能得到的值不是准确的。我们最好在onLayout中获取;还有一个需要注意的是,我们在onCreate、onStart和onResume中均得不到view的宽高,因为measure过程与activity的生命周期不是同步的;

layout过程

layout过程的作用是viewgroup来确定子元素的位置,当viewgroup的位置确定后,会遍历所有子元素并调用其layout方法,在layout方法中又会调用onLayout方法。
先看viewgroup的layout方法,

@Override
    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(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

可以看出是调用父类的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);

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

首先通过setFrame确定view的四个顶点位置,此时父容器的位置也就确定了,接下来就会调用onLayout方法确定子元素的位置;

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

onLayout方法并没有给出具体的实现,当继承特定的组件时,不需要我们重写onLayout方法,继承viewgroup时则需要重写onLayout方法;

draw过程

draw的作用是将view绘制到屏幕上,view的绘制流程遵循以下几步:

  1. 绘制背景drawBackground(canvas);
  2. 绘制自己onDraw(canvas);
  3. 绘制子元素dispatchDraw(canvas);
  4. 绘制装饰onDrawForeground(canvas);

看看draw源码:

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

            // 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(canvas)来实现的, dispatchDraw(canvas);会遍历所有的子元素的draw方法,view有一个特殊的方法:setWillNotDraw,先看一下源码;

/**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

意思是如果一个view不需要绘制任何内容,那么设置这个标记位为TRUE后,系统会进行优化,默认情况下标记位为FALSE。

以上就是view的三大流程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值