Android流式布局FlowLayout
2016-11-03 11:29
281 查看
背景介绍:
最近需要做一个类似于Java awt包里的FlowLayout的流式布局,比如下图Android原生控件中没有找到现成的,习惯先Google之,找到鸿洋_ 大神的: Android 自定义ViewGroup 实战篇 -> 实现FlowLayout。
写的太好了,基本实现了流式布局的功能,不过还有些功能没有实现:
flowLayout自身的padding
子view的visibility为gone时隐藏
兼容子view的居中显示
在大神的基础上进行继续开发,实现以上功能,并优化减少在onLayout方法中的一次遍历getChildAt。
FlowLayout的使用方式与系统的LinearLayout、RelativeLayout等基本一样(xml方式和java代码方式创建使用、动态修改、设置事件等)
上代码:
public class FlowLayout extends ViewGroup { private boolean centerHorizontal;//是否水平居中显示 public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); initFromAttributes(context, attrs); } public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initFromAttributes(context, attrs); } private void initFromAttributes(Context context, AttributeSet attrs) { /** //res/values/attr.xml //<declare-styleable name="FlowLayout"> // <!-- 是否横向居中 --> // <attr name="centerHorizontal" format="boolean" /> //</declare-styleable> //下面的代码配合res/values/attr.xml可以支持通过在xml中FlowLayout节点 //添加app:centerHorizontal="true"的方式设置水平居中属性 final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout); centerHorizontal = a.getBoolean(R.styleable.FlowLayout_centerHorizontal, centerHorizontal); a.recycle(); */ } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p.width, p.height); } public boolean isCenterHorizontal() { return centerHorizontal; } // 动态设置子view的居中状态 public void setCenterHorizontal(boolean centerHorizontal) { if (this.centerHorizontal ^ centerHorizontal) { this.centerHorizontal = centerHorizontal; requestLayout(); } } /** * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 获得它的父容器为它设置的测量模式和大小 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 如果是warp_content情况下,记录宽和高 final int paddingWidth = getPaddingLeft() + getPaddingRight(); final int paddingHeight = getPaddingTop() + getPaddingBottom(); int width = paddingWidth; int height = paddingHeight; // 记录每一行的宽度,width不断取最大宽度 int lineWidth = paddingWidth; // 每一行的高度,累加至height int lineHeight = 0; mAllViews.clear(); mLineHeight.clear(); mLineWidth.clear(); // 存储每一行所有的childView List<View> lineViews = new ArrayList<>(); int cCount = getChildCount(); // 遍历每个子元素 for (int i = 0; i < cCount; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } // 得到child的lp LayoutParams lp = (LayoutParams) child.getLayoutParams(); // 测量每一个child的宽和高 final int marginWidth = lp.rightMargin + lp.leftMargin; final int marginHeight = lp.topMargin + lp.bottomMargin; // 子控件测量的时候需要将父控件的padding和当前子控件的margin算进去 child.measure( getChildMeasureSpec(widthMeasureSpec, paddingWidth + marginWidth, lp.width), getChildMeasureSpec(heightMeasureSpec, paddingHeight + marginHeight, lp.height) ); // 当前子控件实际占据的宽度 int childWidth = child.getMeasuredWidth() + marginWidth; // 当前子控件实际占据的高度 int childHeight = child.getMeasuredHeight() + marginHeight; //如果加入当前child,则超出最大宽度,则得到目前最大宽度给width,累加height 然后开启新行 if (lineWidth + childWidth > sizeWidth) { width = Math.max(width, lineWidth);// 取最大的 height += lineHeight;// 叠加当前高度 // 记录这一行所有的View、总的宽度以及最大高度 mLineHeight.add(lineHeight); mLineWidth.add(lineWidth); // 将当前行的childView保存,然后开启新的ArrayList保存下一行的childView mAllViews.add(lineViews); lineViews = new ArrayList<>(); // 重新开启新行,开始记录 lineWidth = paddingWidth; lineHeight = 0; } // 否则累加值lineWidth,lineHeight取最大高度 lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); // 如果是最后一个,则将当前记录的最大宽度和当前lineWidth做比较 if (i == cCount - 1) { width = Math.max(width, lineWidth); height += lineHeight; } lineViews.add(child); } // 记录最后一行 mLineHeight.add(lineHeight); mLineWidth.add(lineWidth); mAllViews.add(lineViews); setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height); } /** * 存储所有的View,按行记录 */ private List<List<View>> mAllViews = new ArrayList<>(); /** * 记录每一行的最大高度 */ private List<Integer> mLineHeight = new ArrayList<>(); private List<Integer> mLineWidth = new ArrayList<>(); @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int paddingLeft = getPaddingLeft(); final int paddingRight = getPaddingRight(); final int paddingTop = getPaddingTop(); final int paddingBottom = getPaddingBottom(); int width = getWidth() - paddingLeft - paddingRight; int lineHeight; List<View> lineViews; int left = paddingLeft; int top = paddingTop; // 得到总行数 int lineNums = mAllViews.size(); for (int i = 0; i < lineNums; i++) { // 每一行的所有的views lineViews = mAllViews.get(i); // 当前行的最大高度 lineHeight = mLineHeight.get(i); if (centerHorizontal) { int leftRightSpace = width - (mLineWidth.get(i) - paddingLeft - paddingRight);//左右两端空白空间 left += leftRightSpace / 2; } // 遍历当前行所有的View for (int j = 0; j < lineViews.size(); j++) { View child = lineViews.get(j); if (child.getVisibility() == View.GONE) { continue; } LayoutParams lp = (LayoutParams) child.getLayoutParams(); //计算childView的left,top,right,bottom int lc = left + lp.leftMargin; int tc = top + lp.topMargin; int rc = lc + child.getMeasuredWidth(); int bc = tc + child.getMeasuredHeight(); rc = Math.min(rc, getWidth() - paddingRight - lp.rightMargin); bc = Math.min(bc, getHeight() - paddingBottom - lp.bottomMargin); child.layout(lc, tc, rc, bc);//布局子控件 left += child.getMeasuredWidth() + lp.rightMargin + lp.leftMargin; } left = paddingLeft; top += lineHeight; } } public static class LayoutParams extends MarginLayoutParams { public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(MarginLayoutParams source) { super(source); } public LayoutParams(int width, int height) { super(width, height); } } }
相关文章推荐
- Android流式布局-FlowLayout
- android流式布局FlowLayout
- Android流式布局FlowLayout
- Android流式布局FlowLayout,一款针对Tag的布局
- Android流式布局FlowLayout的实现,Android布局的内部机制onMeasure、onLayout
- android流式布局控件
- <android>自己写一个流式布局吧(FlowLayout)
- 使用Adapter设计模式打造一个流式布局FlowLayout
- Swift 自定义 UICollectionViewFlowLayout 实现横向布局分页
- Android自定义控件--流式布局(FlowLayout)--自动适配
- 自定义流式布局FlowLayout
- java例程练习(布局管理器[FlowLayout])
- Android开发流式布局FlowLayout
- 含有过滤功能的android流式布局
- [黑马] -- 第1天 Swing界面---流式布局FlowLayout
- FlowLayout,流式布局
- 自定义布局和自定义流水布局(CollectionViewLayout和CollectionViewFlowLayout)实例
- JAVA--编写一个JFrame,标题为“计算的窗口”,在该窗口中组件的布局是FlowLayout。窗口中添加两个文本区,当我们在一个文本区中输入若干个数时,另一个文本区同时对输入的数进行求和运算并求
- 类似于文档流的界面布局器:改进版的FlowLayout
- 源码推荐(12.01B):一行代码搞定自动布局,自定义UICollectionViewFlowLayout