自定义Viewgroup实现流式布局(3):实现流式布局

1.https://mp.csdn.net/postedit/87189294

2.https://mp.csdn.net/postedit/87189763

通过前面的1和2已经实现了简单的自定义viewgroup,流式布局和上一个换行的viewgroup相比:

需要判断一行上可以放置多少个子view;某一行view的实际高度是这一行最高的呢个view的高度;还需要考虑子view的margin属性。

支持margin:重写generateLayoutParams方法,返回new MarginLayoutParams对象。

1.根据传入的datas将子view add到viewgroup里面

正常使用肯定是在xml里面写,代码写flowlayout.setData(),类似于listview那样;

那么addview就不能在构造器里面而是在onMeasure里面,而且onMeasure会执行多次,注意removeAllview;

2.onMeasure 计算多少行,每行都存放哪些子view以及viewgroup的宽高

计算子view的宽高,遍历子view,计算在哪换行等。使用List> list 来存储所有view:外层list表示当前有多少行,内层表示每一行都是哪些子view。

3.onLayout显示view

遍历上面的list分别显示每一行,注意考虑margin。

4.子view点击事件并刷新显示

可以使用直接通过子view来控制也可以用数据源来控制。

(1.使用数据源来控制:点击事件的回调中对是否选中的bool值进行更改并调用requestLayout方法刷新显示,但是通过数据源来控制会频繁触发onMeasure和onLayout。这样做可以把控制单选和多选交给使用者通过数据源来处理

(2.使用子view控制:点击事件直接对数据源并更改只对子view更新显示,这样做控制单选和单选就必须在这个Viewgroup内部实现。

5.单选与多选

数据源控制的方式就不说了:点击事件修改数据源,然后requestLayout.

当子view控制时,多选在for循环addview的时候直接datas.get(i).setIsChoosed(!get(i).isChoosed)即可;

单选的时候就必须保证点击完整个view,其他的view全部变成未选中,变成未选中容易但是同时修改view对应的数据就有点麻烦。

两种方法处理:

(1.用add(view,index),这里的index就是getChildAt的index,通过判断getChildAt==点击事件的view,得到触发点击事件view在datas以及childs中对应的下标,for循环datas或者把childs不是这个下标的全部置false和设置不选中。

(2.这个点击事件本身就在datas的for循环内部,再for循环datas把除了这个下标外的所有数据全部置false,然后for循环childs判断当前点击view相等,不相等的全部设置不选中。

6.刷新数据

调用requestLayout方法重新走onmearsure方法

源代码地址:

https://github.com/15539158137/FlowLayoutDemo/tree/master

 

ViewGroup代码如下:

public class Flowlayout extends ViewGroup {
    OnFlowlayoutItemClickListener onFlowlayoutItemClickListener;

    public void setOnFlowlayoutItemClickListener(OnFlowlayoutItemClickListener onFlowlayoutItemClickListener) {
        this.onFlowlayoutItemClickListener = onFlowlayoutItemClickListener;
    }

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

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

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

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public Flowlayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    //外部传入数据
    public void setDatas(List datas) {
        this.dataBeanList = datas;
        //invalidate();这个方法只会触发onDraw
        requestLayout();//这个只会触发onDraw和OnMeasure
    }

    //设置是否可以多选
    private boolean canChooseMulite;

    public void setChooseMulite(boolean canChooseMulite) {
        this.canChooseMulite = canChooseMulite;
    }


    //通过数据源来控制子view的点击事件的方法
    public void notifyDataChanged() {
        //这个会触发onMeasure方法,但是必须保证dataBeanList还是同一个对象才行
        requestLayout();
    }

    private List dataBeanList;

    private void initView() {
        //先清空所有
        this.removeAllViews();
        for (int i = 0; i < dataBeanList.size(); i++) {
            final DataBean dataBean = dataBeanList.get(i);
            final TextView textView = new TextView(getContext());
            textView.setText(dataBean.getFlowItemName());
            //设置开始是否被选中
            if (dataBean.isFlowItemIsChoosed()) {
                textView.setBackgroundResource(R.drawable.shape_tv_red);
            } else {
                textView.setBackgroundResource(R.drawable.shape_tv_blue);
            }
            textView.setBackgroundResource(R.drawable.shape_item_bg);
            textView.setPadding(SizeUtil.dip2px(getContext(), 5), SizeUtil.dip2px(getContext(), 3), SizeUtil.dip2px(getContext(), 5), SizeUtil.dip2px(getContext(), 3));
            MarginLayoutParams marginLayoutParams = new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            marginLayoutParams.rightMargin = SizeUtil.dip2px(getContext(), 10);
            marginLayoutParams.leftMargin = SizeUtil.dip2px(getContext(), 10);
            marginLayoutParams.topMargin = SizeUtil.dip2px(getContext(), 5);
            marginLayoutParams.bottomMargin = SizeUtil.dip2px(getContext(), 5);
            textView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    //可以多选
                    dataBean.setFlowItemIsChoosed(!dataBean.isFlowItemIsChoosed());
                    if (dataBean.isFlowItemIsChoosed()) {
                        textView.setBackgroundResource(R.drawable.shape_tv_red);
                    } else {
                        textView.setBackgroundResource(R.drawable.shape_tv_blue);
                    }
                    if (onFlowlayoutItemClickListener != null) {
                        onFlowlayoutItemClickListener.onItemClick(textView, dataBean);
                    }
                    if (!canChooseMulite) {
                        boolean nowState = !dataBean.isFlowItemIsChoosed();
                        //不管当前这个点击之后的状态是不是选中,其他的都必须是未选中。
                        for (int j = 0; j < dataBeanList.size(); j++) {
                            if (v == getChildAt(j)) {
                                //这个就是当前点击的这个子view
                            } else {
                                dataBeanList.get(j).setFlowItemIsChoosed(false);
                                getChildAt(j).setBackgroundResource(R.drawable.shape_tv_blue);
                            }
                        }
                    }
                }
            });
            textView.setLayoutParams(marginLayoutParams);
            this.addView(textView, i);//后面的i表示index就是getChildAt index
        }
    }

    //所有的view
    List> allViews;
    //每一行上的view
    List oneLineViews;
    //每一行的高度
    List allHeights;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e("onMeasure", "===");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        initView();
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasure = MeasureSpec.getSize(heightMeasureSpec);


        //实际操作中height一定是wrap,如果height写成match或者具体尺寸,也不判断是否能容纳下
        //width不管写成warp还是match还是具体尺寸,都不影响,因为wrap默认就是match,所以实际宽度就是这个计算出来的数值
        int width = widthMeasure;
        int height = heightMeasure;
        //先计算viewgroup横向上实际能够使用的宽度:width-padding
        //可以使用的宽度是
        int canUseWidth = width - getPaddingLeft() - getPaddingRight();
        //for循环view,计算每个view的宽高,当宽度超出就换行
        //list>  内层是这行的view,外层是行数
        int childCount = getChildCount();
        Log.e("当前页面有多少个child", childCount + "");
        //当前一行上累计的宽度
        int nowWidth = 0;
        //这一行的高度
        int nowHeight = 0;
        //
        int totalHeight = 0;
        //所有的view
        allViews = new ArrayList<>();
        //每一行上的view
        oneLineViews = new ArrayList<>();
        //每一行的高度
        allHeights = new ArrayList<>();
        Log.e("横向可容纳的宽度是", canUseWidth + "");
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            // measureChildWithMargins();把viewgroup的margin都加进去计算进去
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();
            nowWidth = nowWidth + childWidth + marginLayoutParams.rightMargin + marginLayoutParams.leftMargin;
            Log.e("当前nowWidth是", nowWidth + "");
            //viewgroup的高度也开始计算了,得到这一行最高的高度然后相加
            int childTotalHeight = childHeight + marginLayoutParams.topMargin + marginLayoutParams.bottomMargin;
            Log.e("当前的下标", i + "");
            if (nowWidth > canUseWidth) {
//这一行放不下了,该换行了
                totalHeight = totalHeight + nowHeight;//把上一行的最大值加上
                allHeights.add(nowHeight);//设置上一行的行高
                Log.e("大于", totalHeight + "");
                //把上一行加进去,新开一行并到当前view加到新的一行去
                allViews.add(oneLineViews);
                oneLineViews = new ArrayList<>();
                oneLineViews.add(child);
                //换行了,下一行的nowwidth和nowheight是这个view的宽高
                nowWidth = childWidth + marginLayoutParams.rightMargin + marginLayoutParams.leftMargin;
                nowHeight = childTotalHeight;
            } else if (nowWidth == canUseWidth) {
                //正好放满
                if (childTotalHeight > nowHeight) {
                    nowHeight = childTotalHeight;
                }
                totalHeight = totalHeight + nowHeight;
                Log.e("等于", totalHeight + "");
                allHeights.add(nowHeight);//设置这一行的行高
                nowHeight = 0;
                //刚好能放下--加到当前行并开新行
                oneLineViews.add(child);
                allViews.add(oneLineViews);
                oneLineViews = new ArrayList<>();
                nowWidth = 0;

            } else {
                //小于,加到当前行里面
                oneLineViews.add(child);
                if (childTotalHeight > nowHeight) {
                    nowHeight = childTotalHeight;
                }
                if (i == childCount - 1) {
                    //如果他单独一行,上面已经new oneline了 而且已经add过了,所以不管是这行之前还有其他view还是他单独一行,只用add一下就可以,
                    allViews.add(oneLineViews);
                    allHeights.add(nowHeight);
                    totalHeight = totalHeight + nowHeight;
                    nowHeight = 0;
                    nowWidth = 0;

                }
                Log.e("小于", totalHeight + "");
            }

        }
        if (widthMode == MeasureSpec.EXACTLY) {
            //返回计算出来的
        } else {
//还是计算出来的
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            //返回计算出来的
        } else {
            height = totalHeight + getPaddingTop() + getPaddingBottom();
        }
        Log.e("计算出来的宽高是", width + "==" + height);
        setMeasuredDimension(width, height);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int startY = 0;
        Log.e("一共有多少行", allViews.size() + "");
        for (int i = 0; i < allViews.size(); i++) {
            Log.e("每一行的行高是", allHeights.get(i) + "");
            List line = allViews.get(i);
            int startX = 0;
            for (int j = 0; j < line.size(); j++) {
                View child = line.get(j);
                MarginLayoutParams marginLayoutParams = (MarginLayoutParams) child.getLayoutParams();
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
                child.layout(startX + marginLayoutParams.leftMargin, startY + marginLayoutParams.topMargin, startX + marginLayoutParams.leftMargin + childWidth, startY + marginLayoutParams.topMargin + childHeight);
                startX = startX + childWidth + marginLayoutParams.leftMargin + marginLayoutParams.rightMargin;
            }
            //当前累计的行高加当前行的
            startY = startY + allHeights.get(i);
        }
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        //return super.generateLayoutParams(attrs);
        //子控件带margin,使用margin
        return new MarginLayoutParams(getContext(), attrs);
    }
}

 

使用方法:

//实例化
        final Flowlayout flowlayout = findViewById(R.id.flowlayout);
        all = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            MyBean dataBean = new MyBean();
            dataBean.setFlowItemName("数据" + i * i * 10000);
            all.add(dataBean);
        }
        //设置数据源
        flowlayout.setDatas(all);
 flowlayout.setOnFlowlayoutItemClickListener(new OnFlowlayoutItemClickListener() {
            @Override
            public void onItemClick(View view, DataBean dataBean) {
                //子view点击的回调
//点击了子view,返回的是子view改变后的状态
                Log.e("点击信息", dataBean.getFlowItemName() + "点击变为" + dataBean.isFlowItemIsChoosed());
                for (DataBean dataBean1 : all) {
                    Log.e("是否被选中", dataBean1.isFlowItemIsChoosed() + dataBean1.getFlowItemName());
                }
            }
        });

 

all.clear();
                for (int i = 0; i < 10; i++) {
                    MyBean dataBean = new MyBean();
                    dataBean.setFlowItemName("数据3333" + i * i * 10000);
                    all.add(dataBean);
                }
                //更新数据的方法
                flowlayout.notifyDataChanged();

 

 //设置单选和多选的方法
                flowlayout.setChooseMulite(isChooseMulite);

 

你可能感兴趣的:(Android)