Android(安卓)流式布局(标签效果)
16lz
2021-01-26
话不多说,先看最后实现的效果
实现的最后效果理解View的测量流程
简单来说测量view会执行ViewRootImpl的PerformTraveals()方法,在该方法中会依次执行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链接
更多相关文章
- Android——自定义View(学习Android开发与艺术探索)
- GrideView简单使用
- [android] 调试linux input子系统驱动的用户空间命令 getevent/s
- Android(安卓)View框架的measure机制
- Android设置textview的字体之间的间距
- 关于android textview 中英文混合分行错误问题
- Android(安卓)RecyclerView 设置item之间的间距
- Android(安卓)View系列(三):View的绘制流程
- 增加Android模拟器空间(Internal Storage)