实现一个可定制化的TabFlowLayout(一) -- 测量与布局

今天要实现的效果如下,支持数据直接写入或者在 XML 中布局

在这里插入图片描述

FlowHelper工程源码

一、自定义 ViewGroup

从上面的效果看,自定义有挺多种选择,比如继承 LinearLayout 或者 HorizontalScrollView … ,但其实直接继承ViewGroup去动态测量更香;
首先,步骤也很简单:

  1. 继承 ViewGroup
  2. 重写 onMeasure,计算子控件的大小从而确定父控件的大小
  3. 重写 onLayout ,确定子控件的布局

直接看第二部,由于是横向,那么如果控件是wrap_content ,则需要拿到子控件的大小,然后给父控件,如下:

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int childCount = getChildCount();
        int width = 0;
        int height = 0;
        /**
         * 计算宽高,由于是横向 width 应该是所有子控件的累加,不用管模式了
         */
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == View.GONE){
                continue;
            }
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
            //拿到 子控件宽度
            int cw = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
            int ch = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;

            width += cw;
            //拿到 子控件高度,拿到最大的那个高度
            height = Math.max(height, ch);

        }
        if (MeasureSpec.EXACTLY == heightMode) {
            height = heightSize;
        }
        setMeasuredDimension(width, height);
    }

看到上面有同学会问了,你怎么可以把控件的 LayoutParams 转成 MarginLayoutParams 呢?不怕报错吗?
是的,会报错,但是为什么可以强转呢?其实跟下面4个方法有关(可以参考LinearLayout 的源码):

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new MarginLayoutParams(p);
    }

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

    @Override
    protected boolean checkLayoutParams(LayoutParams p) {
        return p instanceof MarginLayoutParams;
    }
}

接着在 onLayout 中,摆放子控件的位置:

  @Override
   protected void onLayout(boolean changed, int l, int t, int r, int b) {
       int count = getChildCount();
       int left = 0;
       int top = 0;
       for (int i = 0; i < count; i++) {
           View child = getChildAt(i);
           MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();
           int cl = left + params.leftMargin;
           int ct = top + params.topMargin;
           int cr = cl + child.getMeasuredWidth() ;
           int cb = ct + child.getMeasuredHeight();
           //下个控件的起始位置
           left += child.getMeasuredWidth() + params.leftMargin + params.rightMargin;
           child.layout(cl, ct, cr, cb);
       }
   }

这样,我们的测量和摆放就已经弄好了,接着就是我们在 xml 中添加一些数据:

    

        

运行看一下,发现子控件按照我们的方式,摆放好了:
在这里插入图片描述

二、完善代码

看上去已经实现了我们的效果,但是给FlowLayout加上padding呢?

    
        .... 

在这里插入图片描述
咦,有点不对;所以还得修改;
想一下,padding 会影响哪些部分呢?
FlowLayout 的宽度肯定不会受影响,但是高度是会的;然后onLayout 中,子控件的初始位置,应该要加上 padding,所以,修改后的代码,应该是这样:

实现一个可定制化的TabFlowLayout(一) -- 测量与布局_第1张图片
实现一个可定制化的TabFlowLayout(一) -- 测量与布局_第2张图片
重新运行一下;发现已经ok了:
在这里插入图片描述

你可能感兴趣的:(Android-实例)