自定义流式布局

                                        5v >流式布局是啥呢?相信大家各种app里面都见过这样的布局,下面2张图分别是网易云音乐的热门搜索栏,和淘宝的历史搜索栏。用的就是流式布局,可见流式布局应用还是挺广泛的
网易云音乐热门搜索.png

淘宝历史搜索.png

在自定义流式布局之前,我们先说下它的特点以及应用场景。
流式布局的特点:简单的来说就是,一行放不下,就放到下一行。
应用场景:各大APP的热门搜索栏,历史搜索栏等等。

1、分析

1、首先应该继承ViewGroup
2、对于FlowLayout,需要指定的LayoutParams,我们目前只需要能够识别margin即可,即使用MarginLayoutParams,需要重写generateLayoutParams方法.
3、重写onMeasure方法,测量子view的宽高,设置自己的宽和高;在onMeasure中根据 child views 计算出FlowLayout高度;如果不是wrap_content(可能是确切的值或者match_parent),此时的测量模式都是EXACTTLY,接使用父ViewGroup传入的计算值即可。如果是wrap_content,onMeasure中计算所有childView的宽和高,然后根据childView的宽和高,计算自己的宽和高。
4、重写onLayout方法,在onLayout中根据子View的宽高对所有的childView进行布局。

2、重写generateLayoutParams方法

ViewGroup LayoutParams :每个 ViewGroup 对应一个 LayoutParams; 即 ViewGroup -> LayoutParams
generateLayoutParams不知道转为哪个对应的LayoutParams ,其实很简单,就是如下:
子View.getLayoutParams 得到的LayoutParams对应的就是 子View所在的父控件的LayoutParams;
例如,LinearLayout 里面的子view.getLayoutParams ->LinearLayout.LayoutParams
所以 咱们的FlowLayout 也需要一个LayoutParams,由于上面的效果图是子View的 margin,
所以应该使用MarginLayoutParams。即FlowLayout->MarginLayoutParams

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

3、重写onMeasure

 /*** 负责测量子控件的宽高,根据子控件的宽高,来设置自己的宽高
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取父容器给子View(FlowLayout)设置的测量模式和大小

        int iWidthMode = MeasureSpec.getMode(widthMeasureSpec);//宽的测量模式
        int iWidthSize = MeasureSpec.getSize(widthMeasureSpec);//宽的测量大小

        int iHeightMode = MeasureSpec.getMode(heightMeasureSpec);
        int iHeightSize = MeasureSpec.getSize(heightMeasureSpec);


        int iMeasureW = 0;//最终测量出来的自身(FlowLayout)的宽
        int iMeasureH = 0;//最终测量出来的自身(FlowLayout)的高
        if (iWidthMode == MeasureSpec.EXACTLY && iHeightMode == MeasureSpec.EXACTLY) {
            iMeasureW = iWidthSize;
            iMeasureH = iHeightSize;
        } else {
            int iChildCount = getChildCount();
            int iChildWidth = 0;//测量出来的子View的宽
            int iChildHeight = 0;//测量出来的子View的高
            int iCurLineW = 0;//记录当前行的宽度(是通过累加这一行所有View的宽度得来的)
            int iCurLineH = 0;//记录当前行的高度
            List<View> viewList = new ArrayList<>();
            for (int i = 0; i < iChildCount; i++) {
                View childView = getChildAt(i);
                measureChild(childView, widthMeasureSpec, heightMeasureSpec);
                MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
                iChildWidth = childView.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                iChildHeight = childView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;

                //当(不断累加的行的宽度+正在算的子View的宽度)>父容器的宽度时,就要换行
                if (iCurLineW + iChildWidth > iWidthSize) {
                    /**************************记录当前行的信息*********************/
                    //iMeasureW(历史最大宽度,也是最终的FlowLayout的宽度)
                    //记录当前行的信息(取历史测量出的宽度和当前这行的宽度中的最大值)
                    iMeasureW = Math.max(iMeasureW, iCurLineW);
                    iMeasureH += iCurLineH;//行高累加

                    //将当前行的viewList添加至总的mViewList
                    mViewLinesList.add(viewList);
                    //当前行的行高添加到总的行高里
                    mLineHeights.add(iCurLineH);

                    /**************************记录新建行的信息*********************/
                    //1、新建一行,重新赋值新建的那一行的宽高
                    iCurLineW = iChildWidth;//新建行的宽=当前子View的宽
                    iCurLineH = iChildHeight;

                    //2、新建一行的viewList初始化,添加第一个childView
                    viewList = new ArrayList<>();
                    viewList.add(childView);

//                    /**************************换行时,如果正好是最后一个需要换行*********************/
//                    //需要特别注意,特别容易出错,负责会少一行
//                    if(i==iChildCount-1){
//                        iMeasureW = Math.max(iMeasureW,iCurLineW);
//                        iMeasureH +=iCurLineH;//行高累加
//
//                        //将当前行的viewList添加至总的mViewList
//                        mViewLinesList.add(viewList);
//                        //当前行的行高添加到总的行高里
//                        mLineHeights.add(iCurLineH);
//                    }
                } else {//没有超过,就行内宽高累加
                    /**************************1记录行内的信息*********************/
                    iCurLineW += iChildWidth;
                    //取当前行高与当前View的行高中的大值
                    iCurLineH = Math.max(iCurLineH, iChildHeight);

                    /**************************2添加至当前行的viewList里*********************/
                    viewList.add(childView);
                }

                //注意别加错地方
                /**************************换行时,如果正好是最后一个需要换行*********************/
                //需要特别注意,特别容易出错,负责会少一行
                if (i == iChildCount - 1) {
                    //为啥比较历史最大宽和当前行宽的大小(因为如果只有一个view,那么历史最大宽首先为0
                    // ,还有下一行宽可能大于或者小于之前的历史最大宽度)
                    iMeasureW = Math.max(iMeasureW, iCurLineW);
                    iMeasureH += iCurLineH;//行高累加

                    //将当前行的viewList添加至总的mViewList
                    mViewLinesList.add(viewList);
                    //当前行的行高添加到总的行高里
                    mLineHeights.add(iCurLineH);
                }
            }
        }

        setMeasuredDimension(iMeasureW, iMeasureH);
    }

4、重写onLayout

代码与onMeasure非常类似,只需要根据child view的宽度和高度放到指定位置即可。

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int iLineSize=mViewLinesList.size();
        int iChildLeft;
        int iChildTop;
        int iChildRight;
        int iChildBottom;
        int iCurLeft = 0;
        int iCurTop = 0;

        for (int i=0;i<iLineSize;i++){
            List<View> viewList = mViewLinesList.get(i);
            for (int j=0;j<viewList.size();j++){
                View childView = viewList.get(j);
                MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
                iChildLeft = iCurLeft+layoutParams.leftMargin;
                iChildTop = iCurTop+layoutParams.topMargin;
                //下面少写了childView.getMeasuredWidth()的child导致背景连成一片
                iChildRight = iChildLeft+childView.getMeasuredWidth();
                iChildBottom = iChildTop+childView.getMeasuredHeight();
                childView.layout(iChildLeft,iChildTop,iChildRight,iChildBottom);

                //摆完一个View后,起始点要跟着挪
                iCurLeft += childView.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin;
            }
            //画完一行后iCurTop也要累加
            iCurTop += mLineHeights.get(i);
            iCurLeft = 0;//因为换行了,所以iCurLeft归0
        }
        //特别注意:一定得清空,否则没有效果
        mViewLinesList.clear();
        mLineHeights.clear();
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容