简单的流布局实现
来源:互联网 发布:ftp使用端口 编辑:程序博客网 时间:2024/05/16 19:04
流布局在实际项目中应用非常广泛,它的子控件摆放方式为:依次从左至右摆放子控件,如果这一行中剩余的空间不能够再摆放下一个控件,则进行换行。每一行的行高为该行中高度最高的子控件高度。
下图是一个Demo应用中某个页面的截图,其中热门城市部分是流布局的一个实现样例。
流布局的实现通过自定义ViewGroup完成,在自定义ViewGroup中,最重要的是覆写其中的onMeasure()和onLayout()两个方法。前者决定自定义ViewGroup的尺寸,后者决定了ViewGroup中每个子view的摆放。
如果能够在自定义布局中设置每个子View的margin值,那将极大扩展我们使用的灵活性,因此为了做到这一点,首先需要重写generateLayoutParams方法,并在其中返回一个MarginLayoutParams。
@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs){ return new MarginLayoutParams(getContext(), attrs);}
接下来实现onMeasure方法,在该方法中,我们的最终目标是得到整个ViewGroup的宽高。因此我们需要遍历所有的子控件,并根据它们的测量宽高来决定每行的宽高。
这里需要注意以下几点:
1、宽和高不能超过父控件的限制,因此换行的条件取决于父控件分给它们的最大宽度。
2、每个子控件的测量宽度,不包括它的左右margin值,测量高度不包括它的上下margin值,父控件分给它们的宽高包含了父控件的padding值。
3、Visibility为GONE的不需要显示,同样也不需要进行测量
4、整个布局的最终宽高要根据测量模式决定。
根据上面分析,onMeasure的代码如下:
@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); //实际高度时使用 int height = 0; int width = 0; //记录每一行的宽度和高度 int lineWidth = 0; int lineHeight = 0; //内部元素的个数 int count = getChildCount(); for (int i = 0; i < count; i++) { //获得子view View child = getChildAt(i); if (View.GONE == child.getVisibility()) //不需要显示 { continue; } //测量子view的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); //得到子view的param MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams(); //子view占据的宽度和高度 int childWidth = child.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin; int childHeight = child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin; //当前行宽加上子view宽度 大于 容器行宽 减 内边距 if (lineWidth + childWidth > widthSize - getPaddingLeft() - getPaddingRight()) { //换行,布局行宽为 当前行宽 与 之前记录的最大行宽 中 取较大值 width = Math.max(width, lineWidth); //重置 当前行宽 lineWidth = childWidth; //叠加布局行高 height += lineHeight; //重置行高 lineHeight = childHeight; } else //不换行 { lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } //到达最后一个控件,合计控件宽和高,此时得到wrap_content时的宽度和高度 if (i == count - 1) { width = Math.max(lineWidth, width); height += lineHeight; } } //根据测量模式决定最终的宽和高 int finalwidth = 0, finalheight = 0; switch (widthMode) { case MeasureSpec.EXACTLY: finalwidth = widthSize; break; case MeasureSpec.AT_MOST: finalwidth = (width + getPaddingLeft() + getPaddingRight() < widthSize ? width + getPaddingLeft() + getPaddingRight() : widthSize); break; case MeasureSpec.UNSPECIFIED: finalwidth = width + getPaddingLeft() + getPaddingRight(); break; default: finalwidth = width + getPaddingLeft() + getPaddingRight(); } switch (heightMode) { case MeasureSpec.EXACTLY: finalheight = heightSize; break; case MeasureSpec.AT_MOST: finalheight = (height + getPaddingTop() + getPaddingBottom() < heightSize ? height + getPaddingTop() + getPaddingBottom() : heightSize); break; case MeasureSpec.UNSPECIFIED: finalheight = height + getPaddingTop() + getPaddingBottom(); break; default: finalheight = height + getPaddingTop() + getPaddingBottom(); } //设置宽高值 setMeasuredDimension(finalwidth, finalheight);}
最后实现onLayout方法,在这个方法中,我们要决定每个子view摆放的位置。在摆放子view时,我们需要知道子view的宽高以及左上角的位置,其中宽和高都比较容易得到,左上角的位置需要我们在摆放子控件的过程中不断更新。
我这里采用的方法如下:首先遍历所有的子控件,在这个过程中,决定每一行分别要放哪些控件,并将他们存起来。此外,由于行高要在换行时才能决定,因此需要在换行时存下这一行的行高。在完成这些之后,根据刚刚记录下的信息,填充每一行的控件。这里直接把代码贴上来。
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mAllViews.clear(); mLineHeight.clear(); int width = getWidth(); //因为已经测量完成,所以可以直接获取 int lineWidth = 0; int lineHeight = 0; //每一行的View List<View> lineViews = new ArrayList<View>(); int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (View.GONE == child.getVisibility()) //不需要显示 { continue; } MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams(); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); //需要换行 if (childWidth + mlp.leftMargin + mlp.rightMargin + lineWidth > width - getPaddingLeft() - getPaddingRight()) { //记录行高 mLineHeight.add(lineHeight); //记录当前行views mAllViews.add(lineViews); //重置行宽和行高 lineWidth = 0; lineHeight = mlp.topMargin + childHeight + mlp.bottomMargin; //重置集合,注意这里不能clear,因为已经加到队列里了 lineViews = new ArrayList<View>(); } //这里不能加else块哦 lineHeight = Math.max(lineHeight, mlp.topMargin + childHeight + mlp.bottomMargin); lineWidth += childWidth + mlp.leftMargin + mlp.rightMargin; lineViews.add(child); } //额外处理最后一行 mLineHeight.add(lineHeight); mAllViews.add(lineViews); //设置子view的位置 int left = getPaddingLeft(); //view的开始位置 int top = getPaddingTop(); //view的顶部位置 //行数 int lineCount = mAllViews.size(); for (int i = 0; i < lineCount; i++) { //当前行的所有的View lineViews = mAllViews.get(i); //当前行Height lineHeight = mLineHeight.get(i); //当前行view的个数 int lineViewCount = lineViews.size(); for (int j = 0; j < lineViewCount; j++) { View child = lineViews.get(j); MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams(); //控件的四个顶点 int leftchild = left + mlp.leftMargin; int topchild = top + mlp.topMargin; int rightchild = leftchild + child.getMeasuredWidth(); int bottomchild = topchild + child.getMeasuredHeight(); //为子View进行布局 child.layout(leftchild, topchild, rightchild, bottomchild); left += mlp.leftMargin + child.getMeasuredWidth() + mlp.rightMargin; //每一行内的高度起始值是一样的,因此不用改变 } //行间操作 left = getPaddingLeft(); top += lineHeight; } }
这个过程相对来说比较冗长,不过理解起来并没有什么困难,需要注意的是一些边界条件,比如:最后一行和每行最后一个元素的处理,换行时更新控件的左上角坐标等等。
按照以往习惯,附上源代码链接:流布局源代码
- 简单的流布局实现
- 简单实现瀑布流布局
- 一个简单的瀑布流布局的实现
- 瀑布流布局的实现
- 瀑布流布局的简单应用
- (iOS)简单的瀑布流布局
- 瀑布流布局的实现(一)
- 瀑布流布局的几种实现
- android 实现自动换行的流布局
- 底部PopupWindow+流布局的实现
- 瀑布流布局的原理及实现
- RecyclerView瀑布流布局的实现
- 流布局的简单实现:FlowView(标签流什么的都不用担心啦)
- 实现瀑布流布局
- 瀑布流布局简单应用
- UICollectionView - 流布局 的 初识与简单使用
- 很简单的Vue.js瀑布流布局
- UIColletionView瀑布流布局实现思路以及封装的实现
- java 堆排序
- Quartz2D初识和应用
- Ajax工作原理
- Java学习-17天
- 搭建 Nexus2.7.2-03 + Maven3.1.1----学习笔记
- 简单的流布局实现
- jQuery.animate() 函数详解
- 修改服务启动级别,以及chkconfig 使用
- C++实验4-输出星号图
- 多线程之Java线程阻塞与唤醒
- Objective-C Runtime 运行时之四:Method Swizzling
- Java中Synchronized的用法
- Android 监听ScrollView的滑动
- Java synchronized详解