Created
November 27, 2010 01:22
-
-
Save tomgibara/717450 to your computer and use it in GitHub Desktop.
Square grid layout for Android
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.tomgibara.android.util; | |
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.util.AttributeSet; | |
import android.view.View; | |
import android.view.ViewGroup; | |
/** | |
* A layout that arranges views into a grid of same-sized squares. | |
* | |
* This source code contained in this file is in the Public Domain. | |
* | |
* @author Tom Gibara | |
* | |
*/ | |
public class SquareGridLayout extends ViewGroup { | |
// fields | |
/** | |
* Records the number of views on each side of the square (ie. the number of rows and columns) | |
*/ | |
private int mSize = 1; | |
/** | |
* Records the size of the square in pixels (excluding padding). | |
* This is set during {@link #onMeasure(int, int)} | |
*/ | |
private int mSquareDimensions; | |
// constructors | |
/** | |
* Constructor used to create layout programatically. | |
*/ | |
public SquareGridLayout(Context context) { | |
super(context); | |
} | |
/** | |
* Constructor used to inflate layout from XML. It extracts the size from | |
* the attributes and sets it. | |
*/ | |
/* This requires a resource to be defined like this: | |
* | |
* <resources> | |
* <declare-styleable name="SquareGridLayout"> | |
* <attr name="size" format="integer"/> | |
* </declare-styleable> | |
* </resources> | |
* | |
* So that the attribute can be set like this: | |
* | |
* <com.tomgibara.android.util.SquareGridLayout | |
* xmlns:android="http://schemas.android.com/apk/res/android" | |
* xmlns:util="http://schemas.android.com/apk/res/com.tomgibara.android.background" | |
* util:size="3" | |
* /> | |
*/ | |
public SquareGridLayout(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.SquareGridLayout); | |
setSize(a.getInt(R.styleable.SquareGridLayout_size, 1)); | |
a.recycle(); | |
} | |
// accessors | |
/** | |
* Sets the number of views on each side of the square. | |
* | |
* @param size the size of grid (at least 1) | |
*/ | |
public void setSize(int size) { | |
if (size < 1) throw new IllegalArgumentException("size must be positive"); | |
if (mSize != size) { | |
mSize = size; | |
requestLayout(); | |
} | |
} | |
// View methods | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
// breakdown specs | |
final int mw = MeasureSpec.getMode(widthMeasureSpec); | |
final int mh = MeasureSpec.getMode(heightMeasureSpec); | |
final int sw = MeasureSpec.getSize(widthMeasureSpec); | |
final int sh = MeasureSpec.getSize(heightMeasureSpec); | |
// compute padding | |
final int pw = getPaddingLeft() + getPaddingRight(); | |
final int ph = getPaddingTop() + getPaddingBottom(); | |
// compute largest size of square (both with and without padding) | |
final int s; | |
final int sp; | |
if (mw == MeasureSpec.UNSPECIFIED && mh == MeasureSpec.UNSPECIFIED) { | |
throw new IllegalArgumentException("Layout must be constrained on at least one axis"); | |
} else if (mw == MeasureSpec.UNSPECIFIED) { | |
s = sh; | |
sp = s - ph; | |
} else if (mh == MeasureSpec.UNSPECIFIED) { | |
s = sw; | |
sp = s - pw; | |
} else { | |
if (sw - pw < sh - ph) { | |
s = sw; | |
sp = s - pw; | |
} else { | |
s = sh; | |
sp = s - ph; | |
} | |
} | |
// guard against giving the children a -ve measure spec due to excessive padding | |
final int spp = Math.max(sp, 0); | |
// pass on our rigid dimensions to our children | |
final int size = mSize; | |
for (int y = 0; y < size; y++) { | |
for (int x = 0; x < size; x++) { | |
final View child = getChildAt(y * size + x); | |
if (child == null) continue; | |
// measure each child | |
// we could try to accommodate oversized children, but we don't | |
measureChildWithMargins(child, | |
MeasureSpec.makeMeasureSpec((spp + x) / size, MeasureSpec.EXACTLY), 0, | |
MeasureSpec.makeMeasureSpec((spp + y) / size, MeasureSpec.EXACTLY), 0 | |
); | |
} | |
} | |
//record our dimensions | |
setMeasuredDimension( | |
mw == MeasureSpec.EXACTLY ? sw : sp + pw, | |
mh == MeasureSpec.EXACTLY ? sh : sp + ph | |
); | |
mSquareDimensions = sp; | |
} | |
// ViewGroup methods | |
@Override | |
protected LayoutParams generateDefaultLayoutParams() { | |
return new MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); | |
} | |
@Override | |
protected LayoutParams generateLayoutParams(LayoutParams p) { | |
return new MarginLayoutParams(p); | |
} | |
@Override | |
public LayoutParams generateLayoutParams(AttributeSet attrs) { | |
return new MarginLayoutParams(getContext(), attrs); | |
} | |
@Override | |
protected void onLayout(boolean changed, int l, int t, int r, int b) { | |
// recover the previously computed square dimensions for efficiency | |
final int s = mSquareDimensions; | |
{ | |
// adjust for our padding | |
final int pl = getPaddingLeft(); | |
final int pt = getPaddingTop(); | |
final int pr = getPaddingRight(); | |
final int pb = getPaddingBottom(); | |
// allocate any extra spare space evenly | |
l = pl + (r - pr - l - pl - s) / 2; | |
t = pt + (b - pb - t - pb - s) / 2; | |
} | |
final int size = mSize; | |
for (int y = 0; y < size; y++) { | |
for (int x = 0; x < size; x++) { | |
View child = getChildAt(y * mSize + x); | |
// optimization: we are moving through the children in order | |
// when we hit null, there are no more children to layout so return | |
if (child == null) return; | |
// get the child's layout parameters so that we can honour their margins | |
MarginLayoutParams lps = (MarginLayoutParams) child.getLayoutParams(); | |
// we don't support gravity, so the arithmetic is simplified | |
child.layout( | |
l + (s * x ) / size + lps.leftMargin, | |
t + (s * y ) / size + lps.topMargin, | |
l + (s * (x+1)) / size - lps.rightMargin, | |
t + (s * (y+1)) / size - lps.bottomMargin | |
); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you a lot this was very helpful 👍
Thank you @yanniszark for the quick fix as well.