View绘制3-onLayout

来源:互联网 发布:ae软件中文版免费版 编辑:程序博客网 时间:2024/04/29 14:08

作用

在执行完成onMeasure()确定了View的大小后,需要执行onLayout()来确定View的位置

实现

先看View的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);            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);                }            }        }

setFrame方法会先把l,t,r,b设置给mLeft,mTop,mRight,mBottom,一个View他的4个第多年确认了,那么View本身位置也就确认了。
然后比较位置是否和之前的位置发生改变,如果发生改变就调用onLayout()方法

onLayout()方法

查看View的onLayout()方法

    /**     * Called from layout when this view should     * assign a size and position to each of its children.     */    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    }

发现这个方法是个空实现,但是注释说,onLayout()方法被用于指定子View,View的父ViewGroup实现

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

发现ViewGroup中是一个抽象方法
其实很好理解,如果一个在父容器中确定子View的位置必然是一种具体情况,需要具体的布局去实现,

以LinearLayout为例

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

通过布局方向的不同分为layoutVertical(l, t, r, b)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;        int childSpace = width - paddingLeft - mPaddingRight;        final int count = getVirtualChildCount();        // 省略 ...        for (int i = 0; i < count; i++) {            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();                final int layoutDirection = getLayoutDirection();                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,                        layoutDirection);                // 省略 ...                if (hasDividerBeforeChildAt(i)) {                    childTop += mDividerHeight;                }                childTop += lp.topMargin;                setChildFrame(child, childLeft, childTop                        + getLocationOffset(child), childWidth, childHeight);                childTop += childHeight + lp.bottomMargin                        + getNextLocationOffset(child);                i += getChildrenSkipCount(child, i);            }        }    }

a. 获取child可用的空间,child的个数,由上自下逐个遍历
b. 获得左上角顶点后,根据onMeasure中获得的宽高,就可以计算出其他3个点的位置,然后通过setChildFrame()方法执行确定子控件的位
c. 同时继续遍历直到结束
d. setChildFrame方法体

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

其实就是调用的child的layout方法,如果child也是一个容器那么子元素有可以通过子元素的onLayout方法去确定他的孙元素,从而整个执行过程就完成了

注意事项

1.一般情况下getWidth()和 getMeasureWidth( )返回的值是一样的,不过存在特殊情况,复杂布局,例如改写layout方法

    public void layout(int l, int t, int r, int b) {        super.layout(l, t + 100, b, r + 100);    }

这样View的实际getTop和getBottom就比Measure多100.
2.继承ViewGroup来实现onLayout()往往特别复杂,本着不重复发明轮子的原则,使用已有的来实现如LinearLayout RelativeLayout

参考:《Android开发艺术探讨》任玉刚

0 0