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 de.vanmar.android.yarrn.R; /* https://github.com/ApmeM/android-flowlayout Copyright (c) 2011, Artem Votincev (apmem.org) http://www.apache.org/licenses/LICENSE-2.0.txt */ 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 = false; 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; 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; } LayoutParams lp = (LayoutParams) child.getLayoutParams(); child.measure( getChildMeasureSpec(widthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight(), lp.width), getChildMeasureSpec(heightMeasureSpec, this.getPaddingTop() + this.getPaddingBottom(), lp.height) ); int hSpacing = this.getHorizontalSpacing(lp); int vSpacing = this.getVerticalSpacing(lp); int childWidth = child.getMeasuredWidth(); int childHeight = child.getMeasuredHeight(); int childLength; int childThickness; int spacingLength; int spacingThickness; if (orientation == HORIZONTAL) { childLength = childWidth; childThickness = childHeight; spacingLength = hSpacing; spacingThickness = vSpacing; } else { childLength = childHeight; childThickness = childWidth; spacingLength = vSpacing; spacingThickness = hSpacing; } 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); controlMaxLength = Math.max(controlMaxLength, lineLength); 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; 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(); } } private void drawDebugInfo(Canvas canvas, View child) { if (!debugDraw) { return; } Paint childPaint = this.createPaint(0xffffff00); Paint layoutPaint = this.createPaint(0xff00ff00); Paint newLinePaint = this.createPaint(0xffff0000); LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.horizontalSpacing > 0) { float x = child.getRight(); float y = child.getTop() + child.getHeight() / 2.0f; canvas.drawLine(x, y, x + lp.horizontalSpacing, y, childPaint); canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y - 4.0f, x + lp.horizontalSpacing, y, childPaint); canvas.drawLine(x + lp.horizontalSpacing - 4.0f, y + 4.0f, x + lp.horizontalSpacing, y, childPaint); } else if (this.horizontalSpacing > 0) { float x = child.getRight(); float y = child.getTop() + child.getHeight() / 2.0f; canvas.drawLine(x, y, x + this.horizontalSpacing, y, layoutPaint); canvas.drawLine(x + this.horizontalSpacing - 4.0f, y - 4.0f, x + this.horizontalSpacing, y, layoutPaint); canvas.drawLine(x + this.horizontalSpacing - 4.0f, y + 4.0f, x + this.horizontalSpacing, y, layoutPaint); } if (lp.verticalSpacing > 0) { float x = child.getLeft() + child.getWidth() / 2.0f; float y = child.getBottom(); canvas.drawLine(x, y, x, y + lp.verticalSpacing, childPaint); canvas.drawLine(x - 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint); canvas.drawLine(x + 4.0f, y + lp.verticalSpacing - 4.0f, x, y + lp.verticalSpacing, childPaint); } else if (this.verticalSpacing > 0) { float x = child.getLeft() + child.getWidth() / 2.0f; float y = child.getBottom(); canvas.drawLine(x, y, x, y + this.verticalSpacing, layoutPaint); canvas.drawLine(x - 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint); canvas.drawLine(x + 4.0f, y + this.verticalSpacing - 4.0f, x, y + this.verticalSpacing, layoutPaint); } if (lp.newLine) { if (orientation == HORIZONTAL) { float x = child.getLeft(); float y = child.getTop() + child.getHeight() / 2.0f; canvas.drawLine(x, y - 6.0f, x, y + 6.0f, newLinePaint); } else { float x = child.getLeft() + child.getWidth() / 2.0f; float y = child.getTop(); canvas.drawLine(x - 6.0f, y, x + 6.0f, y, newLinePaint); } } } private Paint createPaint(int color) { Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(color); paint.setStrokeWidth(2.0f); return paint; } 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(); } } } }