android 實現FlowLayout 流線佈局(自定義ViewGroup)

許佳佳233發表於2016-10-19

專案目的

1、深化自定義View的概念
2、將MeasureSpec、View的繪製流程、Layoutparams等分散的知識點整合成一個demo。

專案靈感

筆者經驗也是有限,此文章主要借鑑張鴻洋前輩的部落格。
原文地址:http://blog.csdn.net/lmj623565791/article/details/38352503

專案預覽(原始碼附文章結尾)

這裡寫圖片描述

主要涉及知識點以及相關連結

1、MeasureSpec

快速理解android View的測量onMeasure()與MeasureSpec

2、Layoutparams以及MarginLayoutParams

Android開發:LayoutParams的用法
MarginLayoutParams–一個可以在程式碼中直接設定margin的方法

3、View的繪製流程

Android檢視繪製流程完全解析,帶你一步步深入瞭解View(二)

4、筆者自定View相關文章

給自定義View新增xml屬性
android 自定義控制元件(底部icon點選效果)

實現要點

1、重寫onMeasure()方法,讓FlowLayout能夠根據子部局的大小來確定自己的大小。
2、重寫onLayout()方法,讓FlowLayout中的子部局能夠即正確的排版,即對其間的子View進行佈局。
3、使用MarginLayoutParams,不僅能夠獲取到子部局的寬高,還能獲取到子佈局的margin引數。

主要程式碼

public class FlowLayout extends ViewGroup {

    private static final String TAG = "FlowLayout";

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

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

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

    // 負責設定子控制元件的測量模式和大小 根據所有子控制元件設定自己的寬和高
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 獲得它的父容器為它設定的測量模式和大小
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
        int modeHeight = MeasureSpec.getMode(heightMeasureSpec);

        Log.e(TAG, sizeWidth + "," + sizeHeight);

        // 如果是warp_content情況下,記錄寬和高
        int width = 0;
        int height = 0;

        int lineWidth = 0;//每行的寬度
        int lineHeight = 0;//每行的高度

        int cCount = getChildCount();
        // 遍歷每個子元素
        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            // 測量每一個child的寬和高
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            // 得到child的lp
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();
            int childWidth = child.getMeasuredWidth() + lp.leftMargin
                    + lp.rightMargin;// 當前子空間實際佔據的寬度
            int childHeight = child.getMeasuredHeight() + lp.topMargin
                    + lp.bottomMargin;// 當前子空間實際佔據的高度

            //如果加入當前child,超出最大寬度,則增加高度
            if (lineWidth + childWidth > sizeWidth) {
                width = Math.max(lineWidth, childWidth);// 取最大的
                lineWidth = childWidth; // 重新開啟新行,開始記錄
                height += lineHeight;// 疊加當前高度,
                lineHeight = childHeight;// 開啟記錄下一行的高度
            } else
            // 否則累加值lineWidth,lineHeight取最大高度
            {
                lineWidth += childWidth;
                lineHeight = Math.max(lineHeight, childHeight);
            }
            // 如果是最後一個,則將當前記錄的最大寬度和當前lineWidth做比較
            if (i == cCount - 1) {
                width = Math.max(width, lineWidth);
                height += lineHeight;
            }

        }
        //設定佈局寬高有三種情況
        //如果是wrap_content,則根據所有子部局的大小來顯示
        //如果是確切值,就根據確切值的大小顯示
        //如果是match_parent,則顯示父佈局能顯示的最大值
        setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth
                : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight
                : height);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int width = getWidth();

        int lineWidth = 0;
        int lineHeight = 0;
        // 獲取孩子的個數
        int cCount = getChildCount();

        int left = 0;
        int top = 0;
        // 遍歷所有的孩子,
        for (int i = 0; i < cCount; i++) {
            View child = getChildAt(i);
            MarginLayoutParams lp = (MarginLayoutParams) child
                    .getLayoutParams();

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            // 如果已經需要換行
            if (childWidth + lp.leftMargin + lp.rightMargin + lineWidth > width) {
                lineWidth = 0;// 重置行寬
                left = 0;
                top += lineHeight;
            }

            //計算childView的left,top,right,bottom,並且設定每個View的位置
            int lc = left + lp.leftMargin;
            int tc = top + lp.topMargin;
            int rc = lc + child.getMeasuredWidth();
            int bc = tc + child.getMeasuredHeight();
            child.layout(lc, tc, rc, bc);

            //最後不要忘記累加
            left += child.getMeasuredWidth() + lp.rightMargin
                    + lp.leftMargin;
            lineWidth += childWidth + lp.leftMargin + lp.rightMargin;
            lineHeight = Math.max(lineHeight, childHeight + lp.topMargin
                    + lp.bottomMargin);
        }
    }
}

原始碼下載:http://download.csdn.net/detail/double2hao/9657250

相關文章