實現一個可定製化的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 中添加一些數據:

    <com.zhengsr.tablib.FlowLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#15000000"
        >

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="001"/>
		....
    </com.zhengsr.tablib.FlowLayout>

運行看一下,發現子控件按照我們的方式,擺放好了:
在這裏插入圖片描述

二、完善代碼

看上去已經實現了我們的效果,但是給FlowLayout加上padding呢?

    <com.zhengsr.tablib.FlowLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:background="#15000000"
        >
        .... 

在這裏插入圖片描述
咦,有點不對;所以還得修改;
想一下,padding 會影響哪些部分呢?
FlowLayout 的寬度肯定不會受影響,但是高度是會的;然後onLayout 中,子控件的初始位置,應該要加上 padding,所以,修改後的代碼,應該是這樣:

在這裏插入圖片描述
在這裏插入圖片描述
重新運行一下;發現已經ok了:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章