博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自定义Viewgroup实现流式布局(3):实现流式布局
阅读量:4058 次
发布时间:2019-05-25

本文共 10957 字,大约阅读时间需要 36 分钟。

1.

2.

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

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

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

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

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

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

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

计算子view的宽高,遍历子view,计算在哪换行等。使用List<List<View>> 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方法

源代码地址:

 

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);

 

转载地址:http://pcrci.baihongyu.com/

你可能感兴趣的文章
<转>文档视图指针互获
查看>>
从mysql中 导出/导入表及数据
查看>>
HQL语句大全(转)
查看>>
几个常用的Javascript字符串处理函数 spilt(),join(),substring()和indexof()
查看>>
javascript传参字符串 与引号的嵌套调用
查看>>
swiper插件的的使用
查看>>
layui插件的使用
查看>>
JS牛客网编译环境的使用
查看>>
9、VUE面经
查看>>
关于进制转换的具体实现代码
查看>>
Golang 数据可视化利器 go-echarts ,实际使用
查看>>
mysql 跨机器查询,使用dblink
查看>>
mysql5.6.34 升级到mysql5.7.32
查看>>
dba 常用查询
查看>>
Oracle 异机恢复
查看>>
Oracle 12C DG 搭建(RAC-RAC/RAC-单机)
查看>>
Truncate 表之恢复
查看>>
Oracle DG failover 后恢复
查看>>
mysql 主从同步配置
查看>>
为什么很多程序员都选择跳槽?
查看>>