美文网首页
自定义流式布局

自定义流式布局

作者: zkxok | 来源:发表于2017-07-14 08:52 被阅读0次
                                        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();
    }

相关文章

网友评论

      本文标题:自定义流式布局

      本文链接:https://www.haomeiwen.com/subject/aydkhxtx.html