/* * Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner, * Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain, * Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter, * Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann, * Samuel Zweifel * * This file is part of Jukefox. * * Jukefox is free software: you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or any later version. Jukefox is * distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * Jukefox. If not, see <http://www.gnu.org/licenses/>. */ package ch.ethz.dcg.pancho3.tablet.widget; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; /** * A view container with layout behavior like that of the Swing FlowLayout. * Originally from * http://nishantvnair.wordpress.com/2010/09/28/flowlayout-in-android/ * * @author Melinda Green * @author Sämy Zehnder */ public class FlowLayout extends ViewGroup { public static class LayoutParams extends ViewGroup.LayoutParams { public final int horizontalSpacing; public final int verticalSpacing; public final boolean center; public LayoutParams() { this(false, 1, 1); // default of 1px spacing } /** * @param horizontalSpacing * Pixels between items, horizontally * @param vertical_spacing * Pixels between items, vertically */ public LayoutParams(boolean center, int horizontalSpacing, int verticalSpacing) { super(MATCH_PARENT, WRAP_CONTENT); this.horizontalSpacing = horizontalSpacing; this.verticalSpacing = verticalSpacing; this.center = center; } } public FlowLayout(Context context) { super(context); } public FlowLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { assert MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED; final int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop() - getPaddingBottom(); int childHeightMeasureSpec; if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); } else { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } final LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams(); int nextChild = 0; Rect lastRowRect = new Rect(0, 0, 0, 0); while (nextChild < getChildCount()) { // Get the dimensions of this line nextChild += getRowRect(lastRowRect, nextChild, lastRowRect.bottom + lp.verticalSpacing, getPaddingLeft(), width, true, childHeightMeasureSpec); } if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { height = lastRowRect.bottom; } else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) { if (lastRowRect.bottom < height) { height = lastRowRect.bottom; } } height += 5; // Fudge to avoid clipping bottom of last row. setMeasuredDimension(width, height); }// end onMeasure() @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int width = r - l; int xpos = getPaddingLeft(); int ypos = getPaddingTop(); if (getChildCount() == 0) { return; } final LayoutParams lp = (LayoutParams) getChildAt(0).getLayoutParams(); int nextChild = 0; while (nextChild < getChildCount()) { // First get the dimensions of this line Rect rowRect = new Rect(); int processedChildren = getRowRect(rowRect, nextChild, ypos, getPaddingLeft(), width, false, 0); if (lp.center) { xpos = (width - rowRect.width()) / 2; // Add padding so that the row is centered } // Calculate the child positions for (int i = 0; i < processedChildren; ++i) { final View child = getChildAt(nextChild); ++nextChild; if (child.getVisibility() != GONE) { final int childw = child.getMeasuredWidth(); final int childh = child.getMeasuredHeight(); int childY = ypos; if (lp.center) { childY = ypos + (rowRect.height() - childh) / 2; } child.layout(xpos, childY, xpos + childw, childY + childh); xpos += childw + lp.horizontalSpacing; } } // Prepare for next line xpos = getPaddingLeft(); ypos = rowRect.bottom + lp.verticalSpacing; } } // end onLayout() /** * Computes the bounding rect of the row started with the given child * element. It is shifted to top/left. No padding or anything is added * around it. The child count for this row is returned. * * @param nextChild * @param top * @param left * @param width * @param doMeasure * @param childHeightMeasureSpec * @return */ private int getRowRect(Rect rect, final int nextChild, final int top, final int left, final int width, final boolean doMeasure, final int childHeightMeasureSpec) { rect.set(left, top, left, top); boolean first = true; int numProcessed = 0; for (int i = nextChild; i < getChildCount(); ++i) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int childw; final int childh; if (doMeasure) { child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), childHeightMeasureSpec); } childw = child.getMeasuredWidth(); childh = child.getMeasuredHeight(); boolean deliedBreak = false; if (rect.right + childw > width) { if (first) { // At least one element has to be processed deliedBreak = true; } else { break; } } if (first) { first = false; rect.right -= lp.horizontalSpacing; } rect.right += lp.horizontalSpacing + childw; rect.bottom = Math.max(rect.bottom, top + childh); if (deliedBreak) { ++numProcessed; break; } } ++numProcessed; } return numProcessed; } @Override protected ViewGroup.LayoutParams generateDefaultLayoutParams() { return new LayoutParams(); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { if (p instanceof LayoutParams) { return true; } return false; } }