美文网首页
layout流程

layout流程

作者: 鹏鹏灬 | 来源:发表于2020-09-01 16:37 被阅读0次

    一、layout流程

    • 流程layout->onLayout
    /**
      * 源码分析: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测量的宽 / 高

    相关文章

      网友评论

          本文标题:layout流程

          本文链接:https://www.haomeiwen.com/subject/zkbvsktx.html