自定义 view - 布局 onLayout

自定义 view 的3个核心方法

  • onMeasure
    根据 view 的测量模式计算确定 view 的宽高
  • onLayout
    ViewGroup 中对所有的子 view 排版,决定子 view 的位置
  • onDraw
    具体绘制 view

本节我们来说说 onLayout,view 如何决定显示位置的


我们已经知道 view 有2种:view ,ViewGroup 。那么我们弄明白 view 和 ViewGroup 如何布局就行了,大家来看图:


自定义 view - 布局 onLayout_第1张图片
944365-6e978f448667eb52.png

view 如何布局

view 是不可再分的,view 里面不能再放 view 进去了,那么 view 的布局就是决定自己的布局,换句话说就是决定自己在父控件中的位置,但其实我们在继承 view 的自定义 view 中很少重写 view 的 onLayout 方法,因为 view 的布局方法不是给 view 自己用的,是给父控件用的,view 只需要计算出自己的宽高大小,然后回传数据给父控件,父控件根据其布局特性,调用子 view 的布局方法

view 对外提供的布局方法是 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) {
        ...
    // 通过以下赋值语句记录下了视图的位置信息,即确定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 底部位置

}

view 的 layout 方法确定了 view 的4个点的位置,注意这4个参数是对应 view 坐标系的 left ,top ,right ,bottom 4个值,在我们自己需要使用这个方法时,应该知道4个坐标值表示什么,怎么算

最后我们要知道父控件会计算出子 view 的位置坐标,然后统一遍历所有的子 view ,执行他们的 layout 方法已决定他们的位置。


ViewGroup 如何布局

上面说了具体的 view 布局其实需要他所属的父控件,也就是 ViewGroup 来决定。

ViewGroup 会自上而下、一层层地遍历所有的子 view,直到完成整个View树的layout()过程


自定义 view - 布局 onLayout_第2张图片
自上而下遍历

自定义 view - 布局 onLayout_第3张图片
遍历流程

ViewGroup 是一个容器,是用来存放具体 view 的,不同的 ViewGroup 游不同的对 view 的排列特性,这些特性决定了 期中 view 的位置,这些位置就是 ViewGroup 在 onLayout 方法中计算出来的, 那么 ViewGroup 作为 view 容器的核心就是其 onLayout 布局方法了。原理很简单,明白了 ViewGroup 布局干了什么就行了

/**
  * 分析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

这里 ViewGroup 的 onLayout 方法写的很简单,无关的都删了。一般我们不会自己去直接继承 ViewGroup 自己去写 view 容器,但是某些时候我们还是会干一些相同的事的,比如 behavior 嵌套滚动中,recycleview 的 layoutManage 布局管理中我们都会涉及到这部分知识,其实原理没多难,计算出子 view 的位置,然后调用子 view 的 layout 方法,难的是如何计算位置,这个就不是这里讨论的了


最后

我对自定义 view 的 onLayout 没什么经验,平时也没用到过,这里简单说说 onLayout 的部分,就是为了给对这里糊涂的同学捋顺思路。想要来哦解更多请再去找资料吧


你可能感兴趣的:(自定义 view - 布局 onLayout)