package org.apmem.tools.layouts; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import org.apmem.tools.R; /** * 注意:这个东西并未把左边、上边、下边、右边这两块区域给计算进去,所以它的水平和垂直间距对这四边是不起作用的,不过, * 也有优点就是它不会影响周边控件,如果需要为四周写东西,则只需要手动加上即可。 * */ public class FlowLayout extends ViewGroup { public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; private int horizontalSpacing = 0; private int verticalSpacing = 0; private int orientation = 0; // private boolean debugDraw = true; public FlowLayout(Context context) { super(context); this.readStyleParameters(context, null); } public FlowLayout(Context context, AttributeSet attributeSet) { super(context, attributeSet); this.readStyleParameters(context, attributeSet); } public FlowLayout(Context context, AttributeSet attributeSet, int defStyle) { super(context, attributeSet, defStyle); this.readStyleParameters(context, attributeSet); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - this.getPaddingRight() - this.getPaddingLeft(); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec) - this.getPaddingTop() - this.getPaddingBottom(); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); int size;//总宽度,也是它们所能容纳的最大宽度 int mode; if (orientation == HORIZONTAL) { size = sizeWidth; mode = modeWidth; } else { size = sizeHeight; mode = modeHeight; } int lineThicknessWithSpacing = 0;//带间隔的折线高度 int lineThickness = 0;//折线总高度 int lineLengthWithSpacing = 0;//带间隔的折线总长度,其实它只是一个中间值 int lineLength;//其实是总长度,包含了控件和间隔的长度 int prevLinePosition = 0;//表示Y轴上的位置 int controlMaxLength = 0;//控制的最大长度 int controlMaxThickness = 0;//控制的最大高度 final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == GONE) { continue; } child.measure( //如果确切知道大小的话就让其自适应,否则按照最大的给它。 MeasureSpec.makeMeasureSpec(sizeWidth, modeWidth == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeWidth), MeasureSpec.makeMeasureSpec(sizeHeight, modeHeight == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : modeHeight) ); LayoutParams lp = (LayoutParams) child.getLayoutParams(); int hSpacing = this.getHorizontalSpacing(lp);//得到水平间距 int vSpacing = this.getVerticalSpacing(lp);//得到垂直间距 int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); int childLength;//子view的长度 int childThickness;//子view的高度 int spacingLength;//子view的间距长度 int spacingThickness;//子view的间距高度 if (orientation == HORIZONTAL) { childLength = childWidth; childThickness = childHeight; spacingLength = hSpacing; spacingThickness = vSpacing; } else { childLength = childHeight; childThickness = childWidth; spacingLength = vSpacing; spacingThickness = hSpacing; } // 这个lineLengthWithSpacing很精妙,每一行的第一个长度为item实际的宽度,第一个之后就是实际占用的宽度,妈的 //这个看了好久才明白 lineLength = lineLengthWithSpacing + childLength;//已占用的总长度, lineLengthWithSpacing = lineLength + spacingLength;//带间距的已占用总长度 boolean newLine = lp.newLine || (mode != MeasureSpec.UNSPECIFIED && lineLength > size); if (newLine) { prevLinePosition = prevLinePosition + lineThicknessWithSpacing; lineThickness = childThickness; lineLength = childLength; lineThicknessWithSpacing = childThickness + spacingThickness; lineLengthWithSpacing = lineLength + spacingLength; } //带间距的总高度 lineThicknessWithSpacing = Math.max(lineThicknessWithSpacing, childThickness + spacingThickness); //不带间距的总高度 lineThickness = Math.max(lineThickness, childThickness); //以左上角为标准 int posX; int posY; if (orientation == HORIZONTAL) { posX = getPaddingLeft() + lineLength - childLength; posY = getPaddingTop() + prevLinePosition; } else { posX = getPaddingLeft() + prevLinePosition; posY = getPaddingTop() + lineLength - childHeight; } lp.setPosition(posX, posY); System.out.println("lineLength:"+lineLength); controlMaxLength = Math.max(controlMaxLength, lineLength); System.out.println("controlMaxLength:"+controlMaxLength); System.out.println("prevLinePosition:"+prevLinePosition+" lineThickness"+lineThickness); //这个最大高度也很精妙prevLinePosition表示包含了控件高度和间距,但是最后一个则只需要加上 控件的本来高度,正好是完整的。 controlMaxThickness = prevLinePosition + lineThickness; } /* need to take paddings into account */ if (orientation == HORIZONTAL) { controlMaxLength += getPaddingLeft() + getPaddingRight(); controlMaxThickness += getPaddingBottom() + getPaddingTop(); } else { controlMaxLength += getPaddingBottom() + getPaddingTop(); controlMaxThickness += getPaddingLeft() + getPaddingRight(); } if (orientation == HORIZONTAL) { this.setMeasuredDimension(resolveSize(controlMaxLength, widthMeasureSpec), resolveSize(controlMaxThickness, heightMeasureSpec)); } else { this.setMeasuredDimension(resolveSize(controlMaxThickness, widthMeasureSpec), resolveSize(controlMaxLength, heightMeasureSpec)); } } private int getVerticalSpacing(LayoutParams lp) { int vSpacing; if (lp.verticalSpacingSpecified()) { vSpacing = lp.verticalSpacing; } else { vSpacing = this.verticalSpacing; } return vSpacing; } private int getHorizontalSpacing(LayoutParams lp) { int hSpacing; //如果layoutParamters里面有水平间隔就用属性里面有值,如果没有就用布局里面的自定义属性, //即,优化使用参数里面的自定义间距属性的值,否则再使用布局里面自定义属性的值 if (lp.horizontalSpacingSpecified()) { hSpacing = lp.horizontalSpacing; } else { hSpacing = this.horizontalSpacing; } return hSpacing; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.layout(lp.x, lp.y, lp.x + child.getMeasuredWidth(), lp.y + child.getMeasuredHeight()); } } @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean more = super.drawChild(canvas, child, drawingTime); // this.drawDebugInfo(canvas, child); return more; } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); } @Override public LayoutParams generateLayoutParams(AttributeSet attributeSet) { return new LayoutParams(getContext(), attributeSet); } @Override protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } private void readStyleParameters(Context context, AttributeSet attributeSet) { TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout); try { horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_horizontalSpacing, 0); verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_verticalSpacing, 0); orientation = a.getInteger(R.styleable.FlowLayout_orientation, HORIZONTAL); // debugDraw = a.getBoolean(R.styleable.FlowLayout_debugDraw, false); } finally { a.recycle(); } } public static class LayoutParams extends ViewGroup.LayoutParams { private static int NO_SPACING = -1; private int x; private int y; private int horizontalSpacing = NO_SPACING; private int verticalSpacing = NO_SPACING; private boolean newLine = false; public LayoutParams(Context context, AttributeSet attributeSet) { super(context, attributeSet); this.readStyleParameters(context, attributeSet); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams layoutParams) { super(layoutParams); } public boolean horizontalSpacingSpecified() { return horizontalSpacing != NO_SPACING; } public boolean verticalSpacingSpecified() { return verticalSpacing != NO_SPACING; } public void setPosition(int x, int y) { this.x = x; this.y = y; } private void readStyleParameters(Context context, AttributeSet attributeSet) { TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.FlowLayout_LayoutParams); try { horizontalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_horizontalSpacing, NO_SPACING); verticalSpacing = a.getDimensionPixelSize(R.styleable.FlowLayout_LayoutParams_layout_verticalSpacing, NO_SPACING); newLine = a.getBoolean(R.styleable.FlowLayout_LayoutParams_layout_newLine, false); } finally { a.recycle(); } } } }
</pre><pre code_snippet_id="365746" snippet_file_name="blog_20140527_3_2466645" name="code" class="java">
github上搜索flowlayout
https://github.com/ApmeM/android-flowlayout
附网上能用方法,此法错误,不可取 :
<pre name="code" class="java">package com.example.flowlayout; import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.widget.Button; public class FlowLayout1 extends ViewGroup { public FlowLayout1(Context context) { super(context); } public FlowLayout1(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public FlowLayout1(Context context, AttributeSet attrs) { super(context, attrs); } //每个View的上下间距 private int dividerLine = 20; //每个View的左右间距 private int dividerCol = 20; @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } /** * 此方案你写在这,永远是一个Demo级别的应用,因为大小已不可改变,它只能占满两个屏幕。 * 还有,它的上下左右的距离正好是20,这个东西很神奇,你画图才能明白这些东西。 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); int row = 0; int lengthX = l;//这两个表示右下角 int lengthY = t; for (int i = 0; i < count; i++) { final View child = getChildAt(i); int width = child.getMeasuredWidth(); int height = child.getMeasuredHeight(); lengthX += width + dividerCol; lengthY = t + row*(height+dividerLine)+ height + dividerLine; if (lengthX > r) { lengthX = width + l + dividerCol; row++; lengthY = row * (height+dividerLine) + t + height + dividerLine; } child.layout(lengthX - width , lengthY - height, lengthX, lengthY); } } }