27
2017
09

Android View 绘制之Layout源码探索

Layout 就是确定子元素的位置,当父布局的位置被确定后,就会循环遍历调用onLayout方法

先回到performTraversals方法

 private void performTraversals() {
      ......
      //lp.width和lp.height在创建ViewGroup实例时值为MATCH_PARENT
      int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
      int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
      ......
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      ......
      performLayout(lp, desiredWindowWidth, desiredWindowHeight);
      ......
      performDraw();
      ......
  }

performLayout

  private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {

        final View host = mView;
        ...

            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...

    }

可以看到主要还是调用了View.layout,传入已经测量好的宽和高

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确定具体的位置,当然他在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);
        }
    }

可以看到先判断了Orientation,是平行还是垂直布局,我们就调一个继续看

layoutVertical

void layoutVertical(int left, int top, int right, int bottom) {


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



                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

                i += getChildrenSkipCount(child, i);
            }
        }
    }

这里遍历所有子孩子,然后执行setChildFrame方法,看着方法也就是和setFrame差不多,确定子孩子的位置,而后面childtop会慢慢变大,意味着子孩子会越来越往下摆放。当然这也是线性布局的属性.

setChildFrame

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

同时setChildFrame 传入也是自己的宽高

 final int childWidth = child.getMeasuredWidth();
 final int childHeight = child.getMeasuredHeight();

这样子孩子又遍历子孩子调用onLayout,最后所有View的位置全部确定

总结: performLayout()会调用最外层的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()设置本View的四个顶点位置。在onLayout(抽象方法)中确定子View的位置,如LinearLayout会遍历子View,循环调用setChildFrame()–>子View.layout()

performLayout()->layout()->setFrame()->onLayout()->setChildFrame()

上一篇:控制Segue跳转的时机 下一篇:view的生命周期