技术的进步,总是由需求推动的 ——安卓君
1、前言
流式布局是app开发中必不可少的布局方式,例如照片墙,筛选标签等等。基本上每个app里面都可以看到这样的布局,但是android API中并没有提供实现流式布局方式的控件,因此自己实现一个流式布局的控件就非常有必要了。本文分享笔者实现FlowLayout思考过程,希望能够抛转引玉,为大家提供一个新的思路,文章末尾有代码链接。
2、效果
下面是效果图:
SimpleFlowLayout示例图3、分析
实现自定义控件肯定要从View的绘制原理开始思考,关于View的绘制原理这里就不做介绍了。首先可以确定的是我们要实现的是一个ViewGroup,多个标签(子控件(View)) 被放置到ViewGroup中。所以要实现的有:
-
1.测量父控件(ViewGroup)的大小
-
2.放置子控件(View)
体现在代码中就是实现父控件的两个方法:
-
1.onMeasure()
-
2.onLayout()
3.1、计算父控件的宽高
3.1.1 计算父控件宽度
流式布局宽度一般都是指定的,match_parent 或者具体数值。测量模式为MeasureSpec.EXACTLY
,可以通过MeasureSpec.getSize(widthMeasureSpec);
方法获取。如果不是指定宽度,就无法换行摆放子控件,流式布局也就不存在了。
3.1.2 计算父控件的高度
结合效果图看,父控件的高度由子控件行数决定的,本文假设每个子控件高度一致,行间距一致,间隔一致。
父控件高度 = paddingTop + paddingBottom + 行高*行数 + 行间距*(行数 - 1)
计算高度
可以看到计算高度的关键是,所以剩下的问题就是如何计算行数。
如何计算行数?
计算行数的关键在于知道什么时候换行,我们先看看效果图每一行子控件实际占据的宽度
计算宽度
在FlowLayout中,一行子控件实际占据宽度为
行宽度 = 子控件宽度 + 间隔 ... (间隔数比子控件少一个)
所以我们可以很容易得出这样一个换行条件
paddingLeft + 行宽度 + 下一个子控件的宽度 + paddingRight > 父控件宽度
我们可以通过for循环遍历子控件集合计算出总行数,当我们得出行数时,就可以计算出父控件的高度,测量宽高的工作就完成了。
3.2 放置子控件
放置子控件,最终调用
layout(int l, int t, int r, int b)
所以只要得到每个子控件的位置信息就可以最终展现出流式布局,很多人在实现FlowLayout时,会在这里重新测量再计算子控件的位置,我觉得比较繁琐,而且重复的测量也耗费资源。我想到,在onMeasure()方法中,需要遍历子控件计算宽高,那么为什么不在遍历的时候,计算出每个子控件的位置,再通过setTag()
方法把位置信息赋值给子控件呢?那样的话在执行onLayout()
方法,放置子控件的时候,就可以通过遍历子控件,getTag()
得到每个子控件的位置信息,就可以实现所有子控件的放置了。
如何计算每个子控件的位置?
计算子控件位置如上图所示,只要计算出子控件的宽高,我们很容易就能得出left,top,bottom,right值。
4.实现单选和多选
如何实现子控件的单选功能?
可以借鉴RadioGroup实现原理,也可以换一种思路,直接继承RadioGroup就可以实现单选的功能,添加RadioButton作为子控件,相当于把RadioGroup改造成具有流式布局功能的控件。
如何实现子控件的多选功能?
如果添加的子控件都是CheckBox,就可以实现多选的功能。
5.总结
FlowLayout算不上非常复杂的控件,原理也很简单,一个是计算父容器的宽高,一个是获取子控件的位置。本文从笔者实际业务出发,行间距,间隔都是在自定义属性中设置的固定值,实现起来也简单。自认为本文特别之处在于,提供新的思路,让流式布局实现起来更简单优雅
在测量子控件时,计算每个控件的位置,并设置到子控件
本文主要分享思考过程、实现方法,不能说多完美,希望能带给大家一点启发。作者欢迎评论,探讨!
源码地址:https://github.com/f1mert/SimpleFlowLayout 欢迎点击!
网友评论