话不多说,先看最后实现的效果
实现的最后效果
理解View的测量流程

简单来说测量view会执行ViewRootImplPerformTraveals()方法,在该方法中会依次执行performMeasure()、performLayout()、performDraw()这三个方法,对应起来就是onMeasure(),onLayout(),onDraw()。如果大家不清楚这个过程可以参考Android组件View绘制流程原理分析和Android View的绘制流程.

实现
  • 自定义viewGroup,代码如下:
public class FlowLayout extends ViewGroup {    /**     * 存储每一行的剩余的空间     */    private List lineSpaces = new ArrayList();    /**     * 存储每一行的高度     */    private List lineHeights = new ArrayList<>();    /**     * 存储每一行的view     */    private List> lineViews = new ArrayList<>();    /**     * 提供添加view     */    private List children = new ArrayList<>();    /**     * 每一行是否平分空间     */    private boolean isAverageInRow = false;    /**     * 每一列是否垂直居中     */    private boolean isAverageInColumn = true;
  • 然后我们就要开始测量,测量什么?我们要测量的是FlowLayout的宽高以及每一个子view的宽高以及子view间距值,这里测量子view的间距值我是重写generateLayoutParams(AttributeSet attrs)来获取子view的间距。
/**     * 重新方法用来获取子view的margin值     *     * @param attrs     * @return     */    @Override    public LayoutParams generateLayoutParams(AttributeSet attrs) {        return new MarginLayoutParams(getContext(), attrs);    }
  • 开始测量自身大小与子view的大小,并记录下来:
@Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        Log.e("onMeasure", "onMeasure");        //清除记录数据        lineSpaces.clear();        lineHeights.clear();        lineViews.clear();        //测量view的宽高        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int viewWidth = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int viewHeight = MeasureSpec.getSize(heightMeasureSpec);        //计算children的数量        int count = this.getChildCount();        //统计子view总共高度        int childrenTotalHeight = 0;        //一行中剩余的空间        int lineLeftSpace = 0;        int lineRealWidth = 0;        int lineRealHeight = 0;        List list = new ArrayList<>();        for (int i = 0; i < count; i++) {            View child = getChildAt(i);            //不可见的View不作处理            if(child.getVisibility()==GONE)continue;            //对子view进行测量            measureChild(child, widthMeasureSpec, heightMeasureSpec);            //获取子view的间距            MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams();            //获取view占据的空间大小            int childViewWidth = child.getMeasuredWidth() + params.leftMargin + params.rightMargin;            int childViewHeight = child.getMeasuredHeight() + params.topMargin + params.bottomMargin;            if (childViewWidth + lineRealWidth <= viewWidth) {//一行                //已占用的空间                lineRealWidth += childViewWidth;                //剩余的空间                lineLeftSpace = viewWidth - lineRealWidth;                //一行的最大高度                lineRealHeight = Math.max(lineRealHeight, childViewHeight);                //将一行中的view加到同意个集合                list.add(child);            } else {//下一行                //统计上一行的总高度                childrenTotalHeight += lineRealHeight;                //上一行的高度                lineHeights.add(lineRealHeight);                //上一行剩余的空间                lineSpaces.add(lineLeftSpace);                //将上一行的元素保存起来                lineViews.add(list);                //重置一行中已占用的空间                lineRealWidth = childViewWidth;                //重置一行中剩余的空间                lineLeftSpace = viewWidth - lineRealWidth;                //重置一行中的高度                lineRealHeight = childViewHeight;                //更换新的集合存储下一行的元素                list = new ArrayList<>();                list.add(child);            }            if (i == count - 1) {//最后一个元素                childrenTotalHeight += lineRealHeight;                //将最后一行的信息保存下来                lineViews.add(list);                lineHeights.add(lineRealHeight);                lineSpaces.add(lineLeftSpace);            }        }        //宽度可以不用考虑 主要考虑高度        if (heightMode == MeasureSpec.EXACTLY) {            setMeasuredDimension(viewWidth, viewHeight);        } else {            setMeasuredDimension(viewWidth, childrenTotalHeight);        }    }
  • 测量完毕就要将子view放到对应的位置:
@Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        Log.e("onLayout", "onLayout   change: " + changed);//        if (true) {        //View最开始左边        int viewLeft = 0;        //View最开始上边        int viewTop = 0;        //每一个view layout的位置        int vl = 0;        int vt = 0;        int vr = 0;        int vb = 0;        //每一行中每一个view多平分的空间        float averageInRow = 0;        //每一列中每一个view距离顶部的高度        float averageInColumn = 0;        //列数        int columns = lineViews.size();        for (int i = 0; i < columns; i++) {            //该行剩余的空间            int lineSpace = lineSpaces.get(i);            //该行的高度            int lineHeight = lineHeights.get(i);            //该行的所有元素            List list = lineViews.get(i);            //每一行的view的个数            int rows = list.size();            //view layout的位置            vl = 0;            vt = 0;            vr = 0;            vb = 0;            //每一行中每一个view多平分的空间<一行只有一个不管>            if (isAverageInRow && rows > 1) {                averageInRow = lineSpace * 1.0f / (rows + 1);            } else {                averageInRow = 0;            }            //获取View的间距属性            MarginLayoutParams params = null;            for (int j = 0; j < rows; j++) {                //对应位置的view元素                View child = list.get(j);                params = (MarginLayoutParams) child.getLayoutParams();                //是否计算每一列中的元素垂直居中的时候多出的距离                if (isAverageInColumn && rows > 1) {                    averageInColumn = (lineHeight - child.getMeasuredHeight() - params.topMargin - params.bottomMargin) / 2;                } else {                    averageInColumn = 0;                }                //左边位置 =起始位置+view左间距+多平分的空间                vl = (int) (viewLeft + params.leftMargin + averageInRow);                //上面的位置 = 起始位置+view上间距+多平分的空间                vt = (int) (viewTop + params.topMargin + averageInColumn);                vr = vl + child.getMeasuredWidth();                vb = vt + child.getMeasuredHeight();                child.layout(vl, vt, vr, vb);                viewLeft += child.getMeasuredWidth() + params.leftMargin + params.rightMargin + averageInRow;            }            viewLeft = 0;            viewTop += lineHeight;        }//        }    }
这样就实现了简单的标签效果,我们可以通过设置实现下面的效果:
行与列不做处理
行不做处理,列垂直居中
行平分剩余空间,列不做处理
最后View链接

更多相关文章

  1. Android——自定义View(学习Android开发与艺术探索)
  2. GrideView简单使用
  3. [android] 调试linux input子系统驱动的用户空间命令 getevent/s
  4. Android(安卓)View框架的measure机制
  5. Android设置textview的字体之间的间距
  6. 关于android textview 中英文混合分行错误问题
  7. Android(安卓)RecyclerView 设置item之间的间距
  8. Android(安卓)View系列(三):View的绘制流程
  9. 增加Android模拟器空间(Internal Storage)

随机推荐

  1. MySql 安装时的1045错误
  2. 数据库Left join , Right Join, Inner Jo
  3. SQL Server 排序函数 ROW_NUMBER和RANK
  4. sql server 常用的几个数据类型
  5. SQLServer EVENTDATA()函数来获取DDL 触
  6. SQLServer Top语句参数化方法
  7. SQL server 随机数函数
  8. SQL SERVER 文件和文件组
  9. mssql 两表合并sql语句
  10. sqlserver 比较两个表的列