layout流程

2020-09-01  本文已影响0人  鹏鹏灬

一、layout流程

/**
  * 源码分析:layout()
  * 作用:确定View本身的位置,即设置View本身的四个顶点位置
  */ 
  public void layout(int l, int t, int r, int b) {  

    // 当前视图的四个顶点
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  
      
    // 1. 确定View的位置:setFrame() / setOpticalFrame()
    // 即初始化四个顶点的值、判断当前View大小和位置是否发生了变化 & 返回 
    // ->>分析1、分析2
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    // 2. 若视图的大小 & 位置发生变化
    // 会重新确定该View所有的子View在父容器的位置:onLayout()
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  

        onLayout(changed, l, t, r, b);  
        // 对于单一View的laytou过程:由于单一View是没有子View的,故onLayout()是一个空实现->>分析3
        // 对于ViewGroup的laytou过程:由于确定位置与具体布局有关,所以onLayout()在ViewGroup为1个抽象方法,需重写实现(后面会详细说)
  ...

}  

/**
  * 分析1:setFrame()
  * 作用:根据传入的4个位置值,设置View本身的四个顶点位置
  * 即:最终确定View本身的位置
  */ 
  protected boolean setFrame(int left, int top, int right, int bottom) {
        ...
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;

        // 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
        // 从而确定了视图的位置
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
    }

}

/**
  * 分析2:setOpticalFrame()
  * 作用:根据传入的4个位置值,设置View本身的四个顶点位置
  * 即:最终确定View本身的位置
  */ 
  private boolean setOpticalFrame(int left, int top, int right, int bottom) {

        Insets parentInsets = mParent instanceof View ?
                ((View) mParent).getOpticalInsets() : Insets.NONE;

        Insets childInsets = getOpticalInsets();

        // 内部实际上是调用setFrame()
        return setFrame(
                left   + parentInsets.left - childInsets.left,
                top    + parentInsets.top  - childInsets.top,
                right  + parentInsets.left + childInsets.right,
                bottom + parentInsets.top  + childInsets.bottom);
    }
    // 回到调用原处

/**
  * 分析3:onLayout()
  * 注:对于单一View的laytou过程
  *    a. 由于单一View是没有子View的,故onLayout()是一个空实现
  *    b. 由于在layout()中已经对自身View进行了位置计算,所以单一View的layout过程在layout()后就已完成了
  */ 
 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

   // 参数说明
   // changed 当前View的大小和位置改变了 
   // left 左部位置
   // top 顶部位置
   // right 右部位置
   // bottom 底部位置
}  

二、ViewGroup的measure流程

 @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);
        }
        //调用view中的layout方法
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}


/**
  * 分析3:onLayout()
  * 作用:计算该ViewGroup包含所有的子View在父容器的位置()
  * 注: 
  *      a. 定义为抽象方法,需重写,因:子View的确定位置与具体布局有关,所以onLayout()在ViewGroup没有实现
  *      b. 在自定义ViewGroup时必须复写onLayout()!!!!!
  *      c. 复写原理:遍历子View 、计算当前子View的四个位置值 & 确定自身子View的位置(调用子View layout())
  */ 
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

     // 参数说明
     // changed 当前View的大小和位置改变了 
     // left 左部位置
     // top 顶部位置
     // right 右部位置
     // bottom 底部位置

     // 1. 遍历子View:循环所有子View
      for (int i=0; i<getChildCount(); i++) {
          View child = getChildAt(i);   

          // 2. 计算当前子View的四个位置值
            // 2.1 位置的计算逻辑
            ...// 需自己实现,也是自定义View的关键

            // 2.2 对计算后的位置值进行赋值
            int mLeft  = Left
            int mTop  = Top
            int mRight = Right
            int mBottom = Bottom

          // 3. 根据上述4个位置的计算值,设置子View的4个顶点:调用子view的layout() & 传递计算过的参数
          // 即确定了子View在父容器的位置
          child.layout(mLeft, mTop, mRight, mBottom);
          // 该过程类似于单一View的layout过程中的layout()和onLayout(),此处不作过多描述
      }
  
}

在viewGroup中,基本和view的layout流程一致,先完成父View的位置,然后在onLayout中根据逻辑重写子View的摆放逻辑。

三、LinearLayout的onlayout源码分析


/**
  * 分析1:layoutVertical(l, t, r, b)
  */
void layoutVertical(int left, int top, int right, int bottom) {
   
    // 子View的数量
    final int count = getVirtualChildCount();

    // 1. 遍历子View
    for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {

            // 2. 计算子View的测量宽 / 高值
            final int childWidth = child.getMeasuredWidth();
            final int childHeight = child.getMeasuredHeight();

            // 3. 确定自身子View的位置
            // 即:递归调用子View的setChildFrame(),实际上是调用了子View的layout() ->>分析2
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);

            // childTop逐渐增大,即后面的子元素会被放置在靠下的位置
            // 这符合垂直方向的LinearLayout的特性
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}
/**
  * 分析2:setChildFrame()
  */
private void setChildFrame( View child, int left, int top, int width, int height){
    
    // setChildFrame()仅仅只是调用了子View的layout()而已
    child.layout(left, top, left ++ width, top + height);

 }
// 在子View的layout()又通过调用setFrame()确定View的四个顶点
// 即确定了子View的位置
// 如此不断循环确定所有子View的位置,最终确定ViewGroup的位置

linearlayout的layout方法,竖直布局,循环确定子view的位置,通过setChildFrame(即调用子view的layout),来确定子view的位置,然后逐渐正大top值,即后面的会一直向下摆放。

细节问题:getWidth() ( getHeight())与 getMeasuredWidth() (getMeasuredHeight())获取的宽 (高)有什么区别?

getWidth() / getHeight():获得View最终的宽 / 高

getMeasuredWidth() / getMeasuredHeight():获得 View测量的宽 / 高

上一篇下一篇

猜你喜欢

热点阅读