自定义ViewGroup系列---流式布局FlowViewGroup

本篇继续ViewGroup系列来讲述自定义ViewGroup的流程。本篇要讲述的是流式布局。
在Java Swing中,有一种布局,叫流式布局,这种布局的特点是子组件按照从左到右,从上到下的顺序依次排序,如果一行放不下了,自动显示到下一行。Android中并没有提供这样的布局,本节,我们将一起来实现这种流式布局FlowViewGroup。

对于FlowViewGroup来说,有两个难点:

1. 计算子组件的排布后的宽度和高度

FlowViewGroup的宽度wrap_content时计算逻辑如下 measureWidth:

  • 当子view的宽度和 > FlowViewGroup的宽度时 —> FlowViewGroup的宽度不变。
  • 当子view的宽度和 < FlowViewGroup的宽度时 —> FlowViewGroup的宽度为子组件宽度之和。

FlowViewGroup的高度wrap_content时计算逻辑如下 measureHeight:

  • 遍历子组件,当前子view与本行已经排布的子view之和 是否超过FlowViewGroup的宽度,未超过,不换行,将此子view放到本行;否则就需要换行了,更新FlowViewGroup的高度
2. 对子组件进行定位,要根据当前行的宽度、已占据的宽度来计算当前子组件是否需要换行显示 onLayout。

是否换行就会影响子组件的left和top值。此逻辑与计算FlowViewGroup的宽高思路有相近之处。

测量FlowViewGroup时,不允许子组件的宽度比容器还大。

下面贴出FlowViewGroup的这个代码,请读者关注measureWidth、measureHeight和onLayout方法,关键代码都添加了注释。

/**
 * Created by cyy on 2020/7/30.
 * 流式布局
 */

public class FlowViewGroup extends ViewGroup {
    public FlowViewGroup(Context context) {
        super(context);
    }

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

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

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        getContext();
        int n = getChildCount();
        int maxViewHeight = 0;//当前行子组件的最大高度
        int maxLineWidth = 0;//当前行子组件的总宽度
        int totalHeight = 0;//累加总高度
        int width = getMeasuredWidth();//容器宽度

        for(int i = 0 ; i < n ; i++){//循环遍历子view
            View child = getChildAt(i);

            if(maxLineWidth + getChildAt(i).getMeasuredWidth() > width - getPaddingStart() - getPaddingEnd()){
                //加上此view后,超过行的总宽度,要换行
                totalHeight += maxViewHeight;//要换行,totalHeight 加上当前行的最大高度

                maxLineWidth = 0;//要换行,当前子view就是下一行的第一个view,并且还未layout上去,因此清空maxLineWidth 值
                maxViewHeight = 0;//要换行,当前子view就是下一行的第一个view,并且还未layout上去,因此maxViewHeight 值置为0
            }
			//定位当前子view
            layoutChild(child,maxLineWidth,totalHeight,maxLineWidth + child.getMeasuredWidth(), totalHeight + child.getMeasuredHeight());
            
			//当前子view已经layout了,更新当前行maxViewHeight 值(无论是否换行,都是此逻辑。如果换行了,maxViewHeight 即为当前子view的高度;如果未换行,就拿当前子view的高度与之前最大的view高度相比 去最大值)
            maxViewHeight = Math.max(maxViewHeight, child.getMeasuredHeight());
            
            //更新maxLineWidth 值(无论是否换行,都是此逻辑。如果换行了,maxLineWidth 被清为0了,下一行只有当前子view,maxLineWidth 就是当前子view的宽度;如果不换行了,maxLineWidth 就是累当前子view的宽度)
            maxLineWidth += child.getMeasuredWidth();
        }
    }

    private void layoutChild(View child,int l,int t,int r,int b){
        child.layout(l + getPaddingStart(),t + getPaddingTop(), r + getPaddingStart(), b + getPaddingTop());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        measureChildren(widthMeasureSpec, heightMeasureSpec);//先测量子view
        setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));//在测量自身

    }

    private int measureWidth(int widthMeasureSpec){
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        Log.e("cyy","measureWidth mode:"+mode+"  size:"+size);
        int width = 0;
        if(mode == MeasureSpec.EXACTLY){ //FlowViewGroup是固定宽度,无需计算
            width = size;
        }else if(mode == MeasureSpec.AT_MOST){ //FlowViewGroup是wrap_content,需计算其宽度
            int n = getChildCount();
            int childTotalWidth = 0;

            for(int i = 0; i < n ; i++){//循环遍历子view
                View child = getChildAt(i);
                int childWidth = child.getMeasuredWidth();

                if(childWidth > size){
                    throw new IllegalArgumentException("Sub view is too large");
                }

                childTotalWidth += childWidth;//遍历并累加所有子view的宽度和
            }

            if(childTotalWidth > size){ 
            	//所有子view的宽度和 > FlowViewGroup的宽度,一行显示不下,FlowViewGroup即为wrap_content下传来的最大宽度
                width = size;
            }else{
                 //所有子view的宽度和  < FlowViewGroup的宽度,一行显示得下,FlowViewGroup即为子view的宽度之和
                width = childTotalWidth;
              
            }
        }

        return width;
    }


    private int measureHeight(int heightMeasureSpec){
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);

        int height = 0;
        if(mode == MeasureSpec.EXACTLY){
            height = size;
        }else if(mode == MeasureSpec.AT_MOST){
            int width = getMeasuredWidth();
            int n  = getChildCount();
            int maxViewHeight = 0;//当前行子view的最大高度
            int currentLineWidth = 0;//当前行子view的总宽度
            for(int i = 0; i < n ; i++){//循环遍历子view
                TextView child = (TextView) getChildAt(i);
                currentLineWidth += child.getMeasuredWidth();//当前行的子view的宽度和 + 待排布的子view宽度

                if(currentLineWidth > width){
                    //currentLineWidth 超过了FlowViewGroup的宽度了,表示要换行,当前view需要在下一行显示
                    Log.e("cyy","换行:"+child.getText().toString());
                    height += maxViewHeight;//FlowVIewGroup的高度更新,加上前一行的最大高度。
                    maxViewHeight = child.getMeasuredHeight();//换行了,当前view是下一行的第一个view,下一行的最大高度先假定是此view的高度
                    currentLineWidth = child.getMeasuredWidth();//换行了,下一行只有一个view,就是此view,那么下一行的子view的总宽度就是当前view的宽度
                }else{
                    //不换行 ,宽度已经加过了,主要处理高度
                    Log.e("cyy","不换行:"+child.getText().toString());
                    maxViewHeight = Math.max(maxViewHeight,child.getMeasuredHeight());//当前行的最大高度更新

                }
                if(i == (n -1)){//对最后一个子view需要特殊处理。
                    height += maxViewHeight;//无论最后一个子view是否换行,都需要将maxViewHeight的值加到FlowViewGroup的高度上去。
                }
            }
        }
        height += getPaddingTop() + getPaddingBottom();//经过遍历得到的FlowViewGroup的高度,再加上padding值
        Log.e("cyy","height:"+height);
        return height;
    }


DONE
此系列后续持续更新。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值