自定义ViewGroup之LinearLayout

自定义ViewGroup,通过重写OnMeasure和OnLayout方法,实现纵向LinearLayout功能

OnMeasure 测量方法

先说思路,纵向的LinearLayout测量,宽度和高度分别有match_parent,wrap_content两种情况。
1· 宽度、高度match_parent,对应精准模式 MeasureSpec.EXACTLY,直接设置宽高值
2. 宽度warp_content,需要测量所有子View的宽度,找出所有子View中,宽度最大的值,为LinearLayout的宽度。
3. 高度warp_content,需要测量所有子View的高度,所有子view的高度和为LinearLayout的高度。
4. 注意计算LinearLayout的padding和子View的margin

贴代码:

 @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // super.onMeasure(widthMeasureSpec, heightMeasureSpec);

         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
         int widthMode = MeasureSpec.getMode(widthMeasureSpec);
         switch (widthMode) {
             case MeasureSpec.AT_MOST:
                 // 一般当childView设置其宽、高为wrap_content时
                 break;
             case MeasureSpec.EXACTLY:
                 // 精准值模式,一般当childView设置其宽、高为精确值、match_parent时
                 mWidth = widthMeasureSpec;
                 break;
             case MeasureSpec.UNSPECIFIED:
                 //表示子布局想要多大就多大,一般出现在AadapterView的item
                 break;
         }

         switch (heightMode) {
             case MeasureSpec.AT_MOST:
                 // 一般当childView设置其宽、高为wrap_content时
                 break;
             case MeasureSpec.EXACTLY:
                 // 精准值模式,一般当childView设置其宽、高为精确值、match_parent时
                 heightMode = heightMeasureSpec;
                 break;
             case MeasureSpec.UNSPECIFIED:
                 //表示子布局想要多大就多大,一般出现在AadapterView的item
                 break;
         }
         int count = getChildCount();
         // 如果没有子View,不进行测量
         if (count <= 0) {
             setMeasuredDimension(mWidth, mHeight);
             return;
         }

         int childWidth = 0, childHeight = 0;
         mHeight = 0;
         for (int position = 0; position < count; position++) {
             View childView = getChildAt(position);
             // 测量子view
             measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
             LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
             if (widthMode != MeasureSpec.EXACTLY) {
                 // 开启自动测量宽度
                 int childMarginLeft = layoutParams.leftMargin;
                 int childMarginRight = layoutParams.rightMargin;
                 childWidth = childView.getMeasuredWidth() + childMarginLeft + childMarginRight + getPaddingLeft() + getPaddingRight();
                 // 取子View中,宽度最大的作为View的宽度
                 if (childWidth > mWidth) {
                     if (childWidth > screenWidth) {
                         mWidth = screenWidth;
                     } else {
                         mWidth = childWidth;
                     }
                 }
             }

             if (heightMode != MeasureSpec.EXACTLY) {
                 // 开启自动测量高度
                 childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
                 mHeight += childHeight;
             }
         }

         setMeasuredDimension(mWidth, mHeight + getPaddingTop() + getPaddingBottom());

     }

测量的三种模式

               int widthMode = MeasureSpec.getMode(widthMeasureSpec);
               switch (widthMode) {
                   case MeasureSpec.AT_MOST:
                       // 一般当childView设置其宽、高为wrap_content时
                       break;
                   case MeasureSpec.EXACTLY:
                       // 精准值模式,一般当childView设置其宽、高为精确值、match_parent时
                       mWidth = widthMeasureSpec;
                       break;
                   case MeasureSpec.UNSPECIFIED:
                       //表示子布局想要多大就多大,一般出现在AadapterView的item
                       break;
               }

注释很清楚,不解释了

测量子View

// 测量子View,测量的宽和高,抛去了margin
measureChildWithMargins(childView, widthMeasureSpec, 0, heightMeasureSpec, 0);
// 测量子View,测量的宽和高包含margin值
measureChild(childView,widthMeasureSpec,heightMeasureSpec);

这里为了方便使用padding和margin,使用 measureChildWithMargins 来测量,获取到view的净宽高
测量完后,计算View的宽度和高度时,还需要手动加上margin
childWidth = childView.getMeasuredWidth() + childMarginLeft + childMarginRight + getPaddingLeft() + getPaddingRight();
childHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;


OnLayout 布局方法

OnLayout 方法,就是通过childView.layout(l,t,r,b),对子view进行布局
思路也比较简单,计算左上角和右下角坐标点,抛去padding和margin的话,第一次左上角为 (0,0)点,右下角为(childWidth,childHeight)
第二次 左上角坐标为(0,oldHeight),右下角为(childWidht,oudHeight+childHeight)
以此类推
...
实际上,还要加上padding和margin的计算
代码比较简单,直接贴了

   @Override
      protected void onLayout(boolean changed, int l, int t, int r, int b) {
          int count = getChildCount();
          if (count <= 0) {
              return;
          }
          int left = 0, top = getPaddingTop(), right, bottom;
          View childView;
          for (int position = 0; position < count; position++) {
              childView = getChildAt(position);
              LayoutParams layoutParams = (LayoutParams) childView.getLayoutParams();
              left = layoutParams.leftMargin + getPaddingLeft();
              top += layoutParams.topMargin;
              right = childView.getMeasuredWidth() + getPaddingRight() + layoutParams.leftMargin;
              bottom = childView.getMeasuredHeight() + top;
              childView.layout(left, top, right, bottom);
              top += childView.getMeasuredHeight() + layoutParams.bottomMargin;
          }
      }

LayoutParams 的使用

上面代码中,需要获取子view的margin值,所以要给我们LinearLayout配置LayoutParams才能获取到margin
配置方法也很简单,自定义一个LayoutParams继承ViewGroup.MarginLayoutParams
重写ViewGroup 的generateLayoutParams方法,返回我们自定义的LayoutParams即可

自定义LayoutParams 代码:

    public static class LayoutParams extends ViewGroup.MarginLayoutParams {


            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
            }

            public LayoutParams(int width, int height) {
                super(width, height);
            }

            public LayoutParams(MarginLayoutParams source) {
                super(source);
            }

            public LayoutParams(ViewGroup.LayoutParams source) {
                super(source);
            }
        }

绑定自定义的LayoutParams:

    @Override
       public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
           return new LayoutParams(getContext(), attrs);
       }

源码地址:
SimpleVLinearLayout

你可能感兴趣的:(自定义ViewGroup之LinearLayout)