View绘制之layout过程

来源:互联网 发布:魔法王座坐骑数据 编辑:程序博客网 时间:2024/06/06 04:21

这篇博客主要是接着上一篇为大家讲述View绘制的第二步layout(布局)全过程:

首先要为大家普及一些相关知识:

/**     * Left position of this view relative to its parent.     * //当前View的左边位置相对与父元素     * @return The left edge of this view, in pixels.     */    @ViewDebug.CapturedViewProperty    public final int getLeft() {        return mLeft;    }
/**     * The distance in pixels from the left edge of this view's parent     * to the left edge of this view.     * //从父View的左边到子View左边的距离,单位是pixels     * {@hide}     */    @ViewDebug.ExportedProperty(category = "layout")    protected int mLeft;
/**     * Right position of this view relative to its parent.     * //当前View的右边位置相对于其父元素     * @return The right edge of this view, in pixels.     */    @ViewDebug.CapturedViewProperty    public final int getRight() {        return mRight;    }
/**     * The distance in pixels from the left edge of this view's parent     * to the right edge of this view.     * //从父View的左边到子View右边的距离,单位是pixels     * {@hide}     */    @ViewDebug.ExportedProperty(category = "layout")    protected int mRight;
 /**     * Bottom position of this view relative to its parent.     * //当前View的底边位置相对于其父元素     * @return The bottom of this view, in pixels.     */    @ViewDebug.CapturedViewProperty    public final int getBottom() {        return mBottom;    }
/**     * The distance in pixels from the top edge of this view's parent     * to the bottom edge of this view.     * //从父View的顶边到子View底边的距离,单位是pixels     * {@hide}     */    @ViewDebug.ExportedProperty(category = "layout")    protected int mBottom;
/**     * Top position of this view relative to its parent.     * //当前View的顶边位置相对于其父元素     * @return The top of this view, in pixels.     */    @ViewDebug.CapturedViewProperty    public final int getTop() {        return mTop;    }
/**     * The distance in pixels from the top edge of this view's parent     * to the top edge of this view.     * //从父View的顶边到子View顶边的距离,单位是pixels     * {@hide}     */    @ViewDebug.ExportedProperty(category = "layout")    protected int mTop;

如图所示:

这里写图片描述

介绍完了这个基础知识,首先为大家呈现一张关于布局的大体流程图(根据LinerLayout)
这里写图片描述

布局开始的第一步是调用layout方法,源代码如下:

View:

//View的layout()方法public void layout(int l, int t, int r, int b) {    //将View上一次的Left、Top、Bottom、Right的参数保存        int oldL = mLeft;        int oldT = mTop;        int oldB = mBottom;        int oldR = mRight;        //设置View的Left、Top、Bottom、Right的参数,确定View相对于父View的位置        boolean changed = setFrame(l, t, r, b);        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {        //如果View的Left、Top、Bottom、Right的参数有改变,调用onLayout()        //方法重新确定该View下子View的位置            onLayout(changed, l, t, r, b);            mPrivateFlags &= ~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 &= ~FORCE_LAYOUT;    }

ViewGroup:

//ViewGroup的layout()方法,基本与View的layout()方法一致public final void layout(int l, int t, int r, int b) {        if (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            mLayoutSuppressed = true;        }

在View的layout()方法中
1、第三处首先将该View的Left、Top、Right、Bottom参数上一次的值保存
2、然后调用setFrame(l, t, r, b),我们来看看这个方法的源代码:

    protected boolean setFrame(int left, int top, int right, int bottom) {        boolean changed = false;        if (DBG) {            Log.d("View", this + " View.setFrame(" + left + "," + top + ","                    + right + "," + bottom + ")");        }        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {            //如果Left、Right、Top、Bottom的参数有改变,changed的值即为true,即如果View在父View的位置改变,那么就要调用Layout方法重新确定该View下子View的位置            changed = true;            // Remember our drawn bit            int drawn = mPrivateFlags & DRAWN;            int oldWidth = mRight - mLeft;            int oldHeight = mBottom - mTop;            int newWidth = right - left;            int newHeight = bottom - top;            //该View宽高与原来相比是否有所改变            boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);            // Invalidate our old position            invalidate(sizeChanged);        //重新设置该View的Left、Top、Right、Bottom的参数            mLeft = left;            mTop = top;            mRight = right;            mBottom = bottom;            if (mDisplayList != null) {                mDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);            }            mPrivateFlags |= HAS_BOUNDS;            if (sizeChanged) {                if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {                    // A change in dimension means an auto-centered pivot point changes, too                    if (mTransformationInfo != null) {                        mTransformationInfo.mMatrixDirty = true;                    }                }                //如果View的宽高相当于原来有改变就会回调onSizeChanged()方法                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);            }            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {                // If we are visible, force the DRAWN bit to on so that                // this invalidate will go through (at least to our parent).                // This is because someone may have invalidated this view                // before this call to setFrame came in, thereby clearing                // the DRAWN bit.                mPrivateFlags |= DRAWN;                invalidate(sizeChanged);                // parent display list may need to be recreated based on a change in the bounds                // of any child                invalidateParentCaches();            }            // Reset drawn bit to original value (invalidate turns it off)            mPrivateFlags |= drawn;            mBackgroundSizeChanged = true;        }        return changed;    }

3、如果该View的Left、Top、Right、Bottom的参数改变了,将调用onLayout()方法确定其子View的位置,
源代码如下:

   /**     * //当这个View需要重新给它子View设置尺寸和位置的时候会被回调     * Called from layout when this view should     * assign a size and position to each of its children.     *      * //View的派生类应该重写这个方法,给每个子View布局的时候会被回调     * Derived classes with children should override     * this method and call layout on each of     * their children.     * @param changed This is a new size or position for this view     * @param left Left position, relative to parent     * @param top Top position, relative to parent     * @param right Right position, relative to parent     * @param bottom Bottom position, relative to parent     */    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }

这个方法并没有被具体内容,是用来确定该View下面子View的位置,我们选择一下LinerLayout的onLayout()方法看一下源代码:

@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);        }    }
 void layoutVertical(int left, int top, int right, int bottom) {        final int paddingLeft = mPaddingLeft;        int childTop;        int childLeft;        // Where right end of child should go        final int width = right - left;        int childRight = width - mPaddingRight;        // Space available for child        int childSpace = width - paddingLeft - mPaddingRight;        final int count = getVirtualChildCount();        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;        final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;        switch (majorGravity) {           case Gravity.BOTTOM:               // mTotalLength contains the padding already               childTop = mPaddingTop + bottom - top - mTotalLength;               break;               // mTotalLength contains the padding already           case Gravity.CENTER_VERTICAL:               childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;               break;           case Gravity.TOP:           default:               childTop = mPaddingTop;               break;        }        for (int i = 0; i < count; i++) {        //确定每一个子View的Left、Top、Right、Bottom的参数            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();                int gravity = lp.gravity;                if (gravity < 0) {                    gravity = minorGravity;                }                final int layoutDirection = getLayoutDirection();                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {                //不同布局属性会改变childLeft                    case Gravity.CENTER_HORIZONTAL:                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)                                + lp.leftMargin - lp.rightMargin;                        break;                    case Gravity.RIGHT:                        childLeft = childRight - childWidth - lp.rightMargin;                        break;                    case Gravity.LEFT:                    default:                        childLeft = paddingLeft + lp.leftMargin;                        break;                }                if (hasDividerBeforeChildAt(i)) {                //如果有分届线,会增加childTop                    childTop += mDividerHeight;                }        //该布局的外边距                childTop += lp.topMargin;        //根据这个布局得到Left、Top设置子View的Left、Top、Right、Top参数                setChildFrame(child, childLeft, childTop + getLocationOffset(child),                        childWidth, childHeight);                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);                i += getChildrenSkipCount(child, i);            }        }    }

在LinerLayout的Vertical布局中,childTop在不停叠加,这也是符合逻辑的,因为越是在LinerLayout布局中越往下的View,Top的值应该越大的。最后调用setChildFrame(child, childLeft, childTop + getLocationOffset(child)的方法,去设置这个参数源码如下:

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

最后又调用View的layout()方法去设置该子View相当于父View的Left、Top、Right、Bottom,从而确定该View的宽高位置,一般来说View的长宽在measure()时就确定了,但是如果在layout()时强行改变也是可以的,调用layout()方法直接设置。在这里解释了为什么说一个View的宽高位置是在layout()时确定的,View的layout()过程在此结束。

0 0
原创粉丝点击