FlowLayout 自定義流式布局

上效果圖

image.png
package com.zt.flowlayout.weget;

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

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

/**
 * 自定義 流式布局
 */
public class FlowLayout extends ViewGroup {
    //橫向分割 寬度
    private int mHorizontalSpacing = dp2px(16);
    //縱向分割 寬度
    private int mVerticalSpacing = dp2px(8);

    private List<List<View>> allLineViews = new ArrayList<>();
    private List<Integer> lineHeights = new ArrayList<>();


    /**
     * 通過new 創(chuàng)建對象
     *
     * @param context
     */
    public FlowLayout(Context context) {
        super(context);
    }

    /**
     * 通過反射
     *
     * @param context
     * @param attrs
     */
    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

    /**
     * 防止內(nèi)存抖動 , 每次清空而不是 重新創(chuàng)建
     */
    private void clearMeasureParams() {
        allLineViews.clear();
        lineHeights.clear();
    }

    /**
     * 框架的寬高
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        clearMeasureParams();

        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //獲取父控件的寬度高度
        int selfWidth = MeasureSpec.getSize(widthMeasureSpec);  //ViewGroup解析的父親給我的寬度
        int selfHeight = MeasureSpec.getSize(heightMeasureSpec); // ViewGroup解析的父親給我的高度

        //保存當前行所有view
        List<View> lineViews = new ArrayList<>();
        //當前行高度
        int lineHeight = 0;
        //當前行寬度
        int lineWidthUsed = 0;


        int parentNeededWidth = 0;  // measure過程中猎物,子View要求的父ViewGroup的寬
        int parentNeededHeight = 0; // measure過程中何恶,子View要求的父ViewGroup的高


        //獲取所有孩子計算 遞歸計算孩子所需寬高
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            LayoutParams lp = childView.getLayoutParams();
            int childMeasureSpecWidth = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
            int childMeasureSpecHeight = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
            childView.measure(childMeasureSpecWidth, childMeasureSpecHeight);

            //當前view 的具體寬 高
            int childMeasuredWidth = childView.getMeasuredWidth();
            int childMeasuredHeight = childView.getMeasuredHeight();


            //判斷是否需要換行
            if (lineWidthUsed + childMeasuredWidth + mHorizontalSpacing > selfWidth) {
                //保存當前行 所有控件
                allLineViews.add(lineViews);
                //保存當前行高
                lineHeights.add(lineHeight);
                //記錄 當前父控件所需寬高
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
                parentNeededHeight += lineHeight + mVerticalSpacing;


                //當前行 相關(guān)保存數(shù)據(jù) 重置
                lineViews = new ArrayList<>();
                lineHeight = 0;
                lineWidthUsed = 0;
            }

            //保存當前行所有控件
            lineViews.add(childView);
            //計算出最大高度//如每個控件高度不一樣
            lineHeight = Math.max(lineHeight, childMeasuredHeight);
            //當前行寬度
            lineWidthUsed = lineWidthUsed + childMeasuredWidth + mHorizontalSpacing;

            if (i == childCount - 1) {//防止最后一個控件 是需要換行
                //保存當前行 所有控件
                allLineViews.add(lineViews);
                //保存當前行高
                lineHeights.add(lineHeight);
                //記錄 當前父控件所需寬高
                parentNeededWidth = Math.max(parentNeededWidth, lineWidthUsed);
                parentNeededHeight += lineHeight;

            }
        }
        //再度量自己,保存
        //根據(jù)子View的度量結(jié)果,來重新度量自己ViewGroup
        // 作為一個ViewGroup票髓,它自己也是一個View,它的大小也需要根據(jù)它的父親給它提供的寬高來度量
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int realWidth = (widthMode == MeasureSpec.EXACTLY) ? selfWidth : parentNeededWidth;
        int realHeight = (heightMode == MeasureSpec.EXACTLY) ? selfHeight : parentNeededHeight;
        setMeasuredDimension(realWidth, realHeight);
    }

    /**
     * 確定 每個子view 的位置
     *
     * @param changed
     * @param l
     * @param t
     * @param r
     * @param b
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //確定第一個控件的xy位置 就是 paddingTop 和 paddingLeft w
        int curL = getPaddingLeft();
        int curT = getPaddingTop();

        int lineCount = allLineViews.size();
        for (int i = 0; i < lineCount; i++) {
            //獲取所有行數(shù) 的控件
            List<View> views = allLineViews.get(i);
            //獲取當前行高
            int height = lineHeights.get(i);
            //當前行數(shù)有幾個控件
            int nowLienView = views.size();
            for (int j = 0; j < nowLienView; j++) {
                //確定第一個控件位置
                View view = views.get(j);
                //調(diào)用過 onMeasure 這個就有值,
                //右邊位置 需要當前寬度 加上左邊位置
                int curR = view.getMeasuredWidth() + curL;
                //下邊位置 需要當前高度 加上上邊位置
                int curB = view.getMeasuredHeight() + curT;
                view.layout(curL, curT, curR, curB);
                //第二個或者之后的 x 軸需要改變也就是 curL 需要加上當前控件的寬 和 橫向分割寬度
                curL = curR + mHorizontalSpacing;
            }
            //第二行 left 需要重置,top 需要加上 上一行的高度 和 縱向分割寬度
            curL = getPaddingLeft();
            curT = curT + height + mVerticalSpacing;
        }

    }


    public int dp2px(float dpValue) {
        float scale = getContext().getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闯狱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抛计,更是在濱河造成了極大的恐慌哄孤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吹截,死亡現(xiàn)場離奇詭異瘦陈,居然都是意外死亡,警方通過查閱死者的電腦和手機波俄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門晨逝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人懦铺,你說我怎么就攤上這事捉貌。” “怎么了冬念?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵趁窃,是天一觀的道長。 經(jīng)常有香客問我急前,道長醒陆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任裆针,我火速辦了婚禮刨摩,結(jié)果婚禮上寺晌,老公的妹妹穿的比我還像新娘。我一直安慰自己澡刹,他們只是感情好折剃,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著像屋,像睡著了一般怕犁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上己莺,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天奏甫,我揣著相機與錄音,去河邊找鬼凌受。 笑死阵子,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胜蛉。 我是一名探鬼主播挠进,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼誊册!你這毒婦竟也來了领突?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤案怯,失蹤者是張志新(化名)和其女友劉穎君旦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嘲碱,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡金砍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了麦锯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恕稠。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扶欣,靈堂內(nèi)的尸體忽然破棺而出鹅巍,到底是詐尸還是另有隱情,我是刑警寧澤宵蛀,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布昆著,位于F島的核電站,受9級特大地震影響术陶,放射性物質(zhì)發(fā)生泄漏凑懂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一梧宫、第九天 我趴在偏房一處隱蔽的房頂上張望接谨。 院中可真熱鬧摆碉,春花似錦、人聲如沸脓豪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扫夜。三九已至楞泼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笤闯,已是汗流浹背堕阔。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留颗味,地道東北人超陆。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像浦马,于是被迫代替她去往敵國和親时呀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容