王學崗高級UI2——自定義流式佈局

思路:得到每一個控件的寬,判斷剩餘的控件是否能滿足控件,不能的話就換一行。
換一行就要對佈局的擺放進行改變。

package com.dn_alan.myapplication;



import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;


import java.util.ArrayList;
import java.util.List;

/**
 *      思路,我們知道繪製流程最終會調用到我門的OnMesure  和   onLayout,
 *     而不同的佈局,他們自己的實現不一樣,所以纔有了我們使用的這些基本佈局組件
 *     那麼我們現在自己來開發一個瀑布式的流式佈局
 */
public class WaterfallFlowLayout extends ViewGroup {
    private static final String TAG = "WaterfallFlowLayout";
    /**
     * 用來保存行高的列表
     */
    private List<Integer> lstLineHegiht = new ArrayList<>();
    /**
     * 用來保存每行views的列表
     */
    private List<List<View>> lstLineView = new ArrayList<>();

    private boolean isFlag;

    public WaterfallFlowLayout(Context context) {
        super(context);
    }

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

    public WaterfallFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    /**
     * @param widthMeasureSpec
     * @param heightMeasureSpec MeasureSpec是父控件提供給子View的一個參數,作爲設定自身大小參考,只是個參考,要多大,還是View自己說了算。
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "onMeasure");
        //測量的方法會走兩次,如果不添加這個flag,佈局會向下移動
        if(isFlag){
            return;
        }
        isFlag = true;

        //1.先完成自己的寬高測量
        //需要得到mode進行判斷我的顯示模式是怎樣的
        //獲取父容器建議的寬和高
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //獲取父容器模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        //當前控件寬高(自己)
        int measureWidth = 0;
        int measureHeight = 0;

        //當前行寬,行高,因爲存在多行,下一行數據要放到下方,行高需要保存
        int iCurLineW = 0;
        int iCurLineH = 0;


        //1.確認自己當前空間的寬高,這裏因爲會有兩次OnMeasure,進行二級測量優化,所以採用IF_ELSE結構
        //二級優化原理在源碼具體Draw時,第一次不會直接進行performDraw的調用反而是在下面重新進行了一次scheduleTraversals
        //在ViewRootImpl源碼2349-2372之中我門會看到  scheduleTraversals在我們的2363
        //對View遍歷之前我們要判斷不同的模式,判斷父容器的模式
        //MeasureSpec.EXACTLY是matchParent或者具體的固定值
        if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY){
            measureWidth = widthSize;
            measureHeight = heightSize;
        }else{
            //當前VIEW寬高
            int iChildWidth = 0;
            int iChildHeight = 0;
            //獲取子VIEW數量用於迭代
            int childCount = getChildCount();


            //單行信息容器
            //當前行存放多少個View
            List<View> viewList = new ArrayList<>();
            for (int i = 0;i < childCount;i++){
                View childAt = getChildAt(i) ;
                //1.測量自己
                //測量子View的寬和高,測量完之後會進行保存
                measureChild(childAt,widthMeasureSpec,heightMeasureSpec);
                //2.獲取getLayoutParams 即XML資源中定義的參數
                //獲取偏移量,即佈局中margin屬性的值
                MarginLayoutParams layoutParams = (MarginLayoutParams) childAt.getLayoutParams();


                //3.獲得實際寬度和高度(MARGIN+WIDTH)
                iChildWidth = childAt.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                iChildHeight = childAt.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

                //4.是否需要換行
                //判斷當前剩餘的寬度是否需要下一個控件的繪製。判斷是否需要換行
                if(iCurLineW + iChildWidth > widthSize){
                    //4.1.紀錄當前行信息
                    //4.1.1.紀錄當前行最大寬度,高度累加
                    measureWidth = Math.max(measureWidth,iCurLineW);
                    measureHeight += iCurLineH;
                    //4.1.2.保存這一行數據,及行高
                    lstLineHegiht.add(iCurLineH);
                    lstLineView.add(viewList);

                    //4.2.紀錄新的行信息
                    //4.2.1.賦予新行新的寬高
                    iCurLineW = iChildWidth;
                    iCurLineH = iChildHeight;

                    //4.2.2添加新行紀錄
                    viewList = new ArrayList<View>();
                    viewList.add(childAt);

                }else{
                    //5.1.不換行情況
                    //5.1.1.記錄某行內的消息行內寬度的疊加、高度比較
                    iCurLineW += iChildWidth;
                    iCurLineH = Math.max(iCurLineH, iChildHeight);

                    //5.1.2.添加至當前行的viewList中
                    viewList.add(childAt);
                }

                //6.如果正好是最後一行需要換行
                if(i == childCount - 1){
                    //6.1.記錄當前行的最大寬度,高度累加
                    measureWidth = Math.max(measureWidth,iCurLineW);
                    measureHeight += iCurLineH;


                    //6.2.將當前行的viewList添加至總的mViewsList,將行高添加至總的行高List
                    lstLineView.add(viewList);
                    lstLineHegiht.add(iCurLineH);

                }


            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //開始佈局
        //1.取得所有視圖信息
        //與之當前組件上下左右四個邊距
        int left,top,right,bottom;
        //當前頂部高度和左部高度
        int curTop = 0;
        int curLeft = 0;
        //開始迭代
        int lineCount = lstLineView.size();
        for(int i = 0 ; i < lineCount ; i++) {
            List<View> viewList = lstLineView.get(i);
            int lineViewSize = viewList.size();
            for(int j = 0; j < lineViewSize; j++){
                View childView = viewList.get(j);
                MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();


                left = curLeft + layoutParams.leftMargin;
                top = curTop + layoutParams.topMargin;
                right = left + childView.getMeasuredWidth();
                bottom = top + childView.getMeasuredHeight();
                //同理,通過調用自身的layout進行佈局
                childView.layout(left,top,right,bottom);
                //左邊部分累加
                curLeft += childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
            }
            //進入下一行
            curLeft = 0;
            curTop += lstLineHegiht.get(i);
        }
        lstLineView.clear();
        lstLineHegiht.clear();
    }
}

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.dn_alan.myapplication.WaterfallFlowLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            style="@style/text_flag_01"
            android:text="hello" />

        <TextView
            style="@style/text_flag_01"
            android:text="hello,hi" />

        <TextView
            style="@style/text_flag_01"
            android:text="你是我的"
            android:textSize="18sp" />

        <TextView
            style="@style/text_flag_01"
            android:text="hello,man" />

        <TextView
            style="@style/text_flag_01"
            android:text="helloview" />

        <TextView
            style="@style/text_flag_01"
            android:text="view" />

        <TextView
            style="@style/text_flag_01"
            android:text="我是你的"
            android:textSize="20sp" />

        <TextView
            style="@style/text_flag_01"
            android:text="he" />

        <TextView
            style="@style/text_flag_01"
            android:text="hello" />

        <TextView
            style="@style/text_flag_01"
            android:text="textview" />

        <TextView
            style="@style/text_flag_01"
            android:text="view" />

        <TextView
            style="@style/text_flag_01"
            android:text="hello,view" />

        <TextView
            style="@style/text_flag_01"
            android:text="hello,mt" />

        <TextView
            style="@style/text_flag_01"
            android:text="hel" />

        <TextView
            style="@style/text_flag_01"
            android:text="hel" />

        <TextView
            style="@style/text_flag_01"
            android:text="hel2" />

    </com.dn_alan.myapplication.WaterfallFlowLayout>



</LinearLayout>

 <style name="text_flag_01">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">4dp</item>
        <item name="android:background">@drawable/flag_01</item>
        <item name="android:textColor">#ffffff</item>
    </style>

看下效果
在這裏插入圖片描述

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