/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.support.v4.view.GravityCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewGroupCompat; import android.util.AttributeSet; import android.util.Log; import android.util.LogPrinter; import android.util.Pair; import android.util.Printer; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.support.v7.gridlayout.R; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import static android.view.Gravity.*; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static java.lang.Math.max; import static java.lang.Math.min; /** * A layout that places its children in a rectangular grid. *
* The grid is composed of a set of infinitely thin lines that separate the * viewing area into cells. Throughout the API, grid lines are referenced * by grid indices. A grid with {@code N} columns * has {@code N + 1} grid indices that run from {@code 0} * through {@code N} inclusive. Regardless of how GridLayout is * configured, grid index {@code 0} is fixed to the leading edge of the * container and grid index {@code N} is fixed to its trailing edge * (after padding is taken into account). * *
* The flexibility of a view is therefore influenced by its alignment which is, * in turn, typically defined by setting the * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters. * If either a weight or alignment were defined along a given axis then the component * is taken as flexible in that direction. If no weight or alignment was set, * the component is instead assumed to be inflexible. *
* Multiple components in the same row or column group are * considered to act in parallel. Such a * group is flexible only if all of the components * within it are flexible. Row and column groups that sit either side of a common boundary * are instead considered to act in series. The composite group made of these two * elements is flexible if one of its elements is flexible. *
* To make a column stretch, make sure all of the components inside it define a * weight or a gravity. To prevent a column from stretching, ensure that one of the components * in the column does not define a weight or a gravity. *
* When the principle of flexibility does not provide complete disambiguation, * GridLayout's algorithms favour rows and columns that are closer to its right * and bottom edges. To be more precise, GridLayout treats each of its layout * parameters as a constraint in the a set of variables that define the grid-lines along a * given axis. During layout, GridLayout solves the constraints so as to return the unique * solution to those constraints for which all variables are less-than-or-equal-to * the corresponding value in any other valid solution. * *
* These statements apply equally to rows as well as columns, and to groups of rows or columns. * * *
* See {@link GridLayout.LayoutParams} for a full description of the * layout parameters used by GridLayout. * * @attr name android:orientation * @attr name android:rowCount * @attr name android:columnCount * @attr name android:useDefaultMargins * @attr name android:rowOrderPreserved * @attr name android:columnOrderPreserved */ public class GridLayout extends ViewGroup { // Public constants /** * The horizontal orientation. */ public static final int HORIZONTAL = LinearLayout.HORIZONTAL; /** * The vertical orientation. */ public static final int VERTICAL = LinearLayout.VERTICAL; /** * The constant used to indicate that a value is undefined. * Fields can use this value to indicate that their values * have not yet been set. Similarly, methods can return this value * to indicate that there is no suitable value that the implementation * can return. * The value used for the constant (currently {@link Integer#MIN_VALUE}) is * intended to avoid confusion between valid values whose sign may not be known. */ public static final int UNDEFINED = Integer.MIN_VALUE; /** * This constant is an {@link #setAlignmentMode(int) alignmentMode}. * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment * is made between the edges of each component's raw * view boundary: i.e. the area delimited by the component's: * {@link android.view.View#getTop() top}, * {@link android.view.View#getLeft() left}, * {@link android.view.View#getBottom() bottom} and * {@link android.view.View#getRight() right} properties. *
* For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode, * children that belong to a row group that uses {@link #TOP} alignment will * all return the same value when their {@link android.view.View#getTop()} * method is called. * * @see #setAlignmentMode(int) */ public static final int ALIGN_BOUNDS = 0; /** * This constant is an {@link #setAlignmentMode(int) alignmentMode}. * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS}, * the bounds of each view are extended outwards, according * to their margins, before the edges of the resulting rectangle are aligned. *
* For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode, * the quantity {@code top - layoutParams.topMargin} is the same for all children that * belong to a row group that uses {@link #TOP} alignment. * * @see #setAlignmentMode(int) */ public static final int ALIGN_MARGINS = 1; // Misc constants static final int MAX_SIZE = 100000; static final int DEFAULT_CONTAINER_MARGIN = 0; static final int UNINITIALIZED_HASH = 0; static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName()); static final Printer NO_PRINTER = new Printer() { @Override public void println(String x) { } }; // Defaults private static final int DEFAULT_ORIENTATION = HORIZONTAL; private static final int DEFAULT_COUNT = UNDEFINED; private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false; static final boolean DEFAULT_ORDER_PRESERVED = true; private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; // TypedArray indices private static final int ORIENTATION = R.styleable.GridLayout_orientation; private static final int ROW_COUNT = R.styleable.GridLayout_rowCount; private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount; private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins; private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode; private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved; private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved; // Instance variables final Axis mHorizontalAxis = new Axis(true); final Axis mVerticalAxis = new Axis(false); int mOrientation = DEFAULT_ORIENTATION; boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; int mAlignmentMode = DEFAULT_ALIGNMENT_MODE; int mDefaultGap; int mLastLayoutParamsHashCode = UNINITIALIZED_HASH; Printer mPrinter = LOG_PRINTER; // Constructors /** * {@inheritDoc} */ public GridLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout); try { setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION)); setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS)); setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE)); setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); } finally { a.recycle(); } } /** * {@inheritDoc} */ public GridLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * {@inheritDoc} */ public GridLayout(Context context) { //noinspection NullableProblems this(context, null); } // Implementation /** * Returns the current orientation. * * @return either {@link #HORIZONTAL} or {@link #VERTICAL} * * @see #setOrientation(int) * * @attr name android:orientation */ public int getOrientation() { return mOrientation; } /** * * GridLayout uses the orientation property for two purposes: *
* If your layout contains a {@link android.widget.TextView} (or derivative: * {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is * in multi-line mode (the default) it is normally best to leave GridLayout's * orientation as {@code HORIZONTAL} - because {@code TextView} is capable of * deriving its height for a given width, but not the other way around. *
* Other than the effects above, orientation does not affect the actual layout operation of * GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if * the height of the intended layout greatly exceeds its width. *
* The default value of this property is {@link #HORIZONTAL}. * * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL} * * @see #getOrientation() * * @attr name android:orientation */ public void setOrientation(int orientation) { if (this.mOrientation != orientation) { this.mOrientation = orientation; invalidateStructure(); requestLayout(); } } /** * Returns the current number of rows. This is either the last value that was set * with {@link #setRowCount(int)} or, if no such value was set, the maximum * value of each the upper bounds defined in {@link LayoutParams#rowSpec}. * * @return the current number of rows * * @see #setRowCount(int) * @see LayoutParams#rowSpec * * @attr name android:rowCount */ public int getRowCount() { return mVerticalAxis.getCount(); } /** * RowCount is used only to generate default row/column indices when * they are not specified by a component's layout parameters. * * @param rowCount the number of rows * * @see #getRowCount() * @see LayoutParams#rowSpec * * @attr name android:rowCount */ public void setRowCount(int rowCount) { mVerticalAxis.setCount(rowCount); invalidateStructure(); requestLayout(); } /** * Returns the current number of columns. This is either the last value that was set * with {@link #setColumnCount(int)} or, if no such value was set, the maximum * value of each the upper bounds defined in {@link LayoutParams#columnSpec}. * * @return the current number of columns * * @see #setColumnCount(int) * @see LayoutParams#columnSpec * * @attr name android:columnCount */ public int getColumnCount() { return mHorizontalAxis.getCount(); } /** * ColumnCount is used only to generate default column/column indices when * they are not specified by a component's layout parameters. * * @param columnCount the number of columns. * * @see #getColumnCount() * @see LayoutParams#columnSpec * * @attr name android:columnCount */ public void setColumnCount(int columnCount) { mHorizontalAxis.setCount(columnCount); invalidateStructure(); requestLayout(); } /** * Returns whether or not this GridLayout will allocate default margins when no * corresponding layout parameters are defined. * * @return {@code true} if default margins should be allocated * * @see #setUseDefaultMargins(boolean) * * @attr name android:useDefaultMargins */ public boolean getUseDefaultMargins() { return mUseDefaultMargins; } /** * When {@code true}, GridLayout allocates default margins around children * based on the child's visual characteristics. Each of the * margins so defined may be independently overridden by an assignment * to the appropriate layout parameter. *
* When {@code false}, the default value of all margins is zero. *
* When setting to {@code true}, consider setting the value of the * {@link #setAlignmentMode(int) alignmentMode} * property to {@link #ALIGN_BOUNDS}. *
* The default value of this property is {@code false}. * * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins * * @see #getUseDefaultMargins() * @see #setAlignmentMode(int) * * @see MarginLayoutParams#leftMargin * @see MarginLayoutParams#topMargin * @see MarginLayoutParams#rightMargin * @see MarginLayoutParams#bottomMargin * * @attr name android:useDefaultMargins */ public void setUseDefaultMargins(boolean useDefaultMargins) { this.mUseDefaultMargins = useDefaultMargins; requestLayout(); } /** * Returns the alignment mode. * * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} * * @see #ALIGN_BOUNDS * @see #ALIGN_MARGINS * * @see #setAlignmentMode(int) * * @attr name android:alignmentMode */ public int getAlignmentMode() { return mAlignmentMode; } /** * Sets the alignment mode to be used for all of the alignments between the * children of this container. *
* The default value of this property is {@link #ALIGN_MARGINS}. * * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} * * @see #ALIGN_BOUNDS * @see #ALIGN_MARGINS * * @see #getAlignmentMode() * * @attr name android:alignmentMode */ public void setAlignmentMode(int alignmentMode) { this.mAlignmentMode = alignmentMode; requestLayout(); } /** * Returns whether or not row boundaries are ordered by their grid indices. * * @return {@code true} if row boundaries must appear in the order of their indices, * {@code false} otherwise * * @see #setRowOrderPreserved(boolean) * * @attr name android:rowOrderPreserved */ public boolean isRowOrderPreserved() { return mVerticalAxis.isOrderPreserved(); } /** * When this property is {@code true}, GridLayout is forced to place the row boundaries * so that their associated grid indices are in ascending order in the view. *
* When this property is {@code false} GridLayout is at liberty to place the vertical row * boundaries in whatever order best fits the given constraints. *
* The default value of this property is {@code true}. * @param rowOrderPreserved {@code true} to force GridLayout to respect the order * of row boundaries * * @see #isRowOrderPreserved() * * @attr name android:rowOrderPreserved */ public void setRowOrderPreserved(boolean rowOrderPreserved) { mVerticalAxis.setOrderPreserved(rowOrderPreserved); invalidateStructure(); requestLayout(); } /** * Returns whether or not column boundaries are ordered by their grid indices. * * @return {@code true} if column boundaries must appear in the order of their indices, * {@code false} otherwise * * @see #setColumnOrderPreserved(boolean) * * @attr name android:columnOrderPreserved */ public boolean isColumnOrderPreserved() { return mHorizontalAxis.isOrderPreserved(); } /** * When this property is {@code true}, GridLayout is forced to place the column boundaries * so that their associated grid indices are in ascending order in the view. *
* When this property is {@code false} GridLayout is at liberty to place the horizontal column * boundaries in whatever order best fits the given constraints. *
* The default value of this property is {@code true}.
*
* @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
* of column boundaries.
*
* @see #isColumnOrderPreserved()
*
* @attr name android:columnOrderPreserved
*/
public void setColumnOrderPreserved(boolean columnOrderPreserved) {
mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
invalidateStructure();
requestLayout();
}
/**
* Return the printer that will log diagnostics from this layout.
*
* @see #setPrinter(android.util.Printer)
*
* @return the printer associated with this view
*/
public Printer getPrinter() {
return mPrinter;
}
/**
* Set the printer that will log diagnostics from this layout.
* The default value is created by {@link android.util.LogPrinter}.
*
* @param printer the printer associated with this layout
*
* @see #getPrinter()
*/
public void setPrinter(Printer printer) {
this.mPrinter = (printer == null) ? NO_PRINTER : printer;
}
// Static utility methods
static int max2(int[] a, int valueIfEmpty) {
int result = valueIfEmpty;
for (int i = 0, N = a.length; i < N; i++) {
result = Math.max(result, a[i]);
}
return result;
}
@SuppressWarnings("unchecked")
static
* GridLayout supports both row and column spanning and arbitrary forms of alignment within
* each cell group. The fundamental parameters associated with each cell group are
* gathered into their vertical and horizontal components and stored
* in the {@link #rowSpec} and {@link #columnSpec} layout parameters.
* {@link GridLayout.Spec Specs} are immutable structures
* and may be shared between the layout parameters of different children.
*
* The row and column specs contain the leading and trailing indices along each axis
* and together specify the four grid indices that delimit the cells of this cell group.
*
* The alignment properties of the row and column specs together specify
* both aspects of alignment within the cell group. It is also possible to specify a child's
* alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
* method.
*
* The weight property is also included in Spec and specifies the proportion of any
* excess space that is due to the associated view.
*
*
* Intervals are immutable so may be passed as values and used as keys in hash tables.
* It is not necessary to have multiple instances of Intervals which have the same
* {@link #min} and {@link #max} values.
*
* Intervals are often written as {@code [min, max]} and represent the set of values
* {@code x} such that {@code min <= x < max}.
*/
final static class Interval {
/**
* The minimum value.
*/
public final int min;
/**
* The maximum value.
*/
public final int max;
/**
* Construct a new Interval, {@code interval}, where:
*
* The grid indices are the leading and trailing edges of this cell group.
* See {@link GridLayout} for a description of the conventions used by GridLayout
* for grid indices.
*
* The alignment property specifies how cells should be aligned in this group.
* For row groups, this specifies the vertical alignment.
* For column groups, this specifies the horizontal alignment.
*
* Use the following static methods to create specs:
*
* To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
* @param alignment the alignment
* @param weight the weight
*/
public static Spec spec(int start, int size, Alignment alignment, float weight) {
return new Spec(start != UNDEFINED, start, size, alignment, weight);
}
/**
* Equivalent to: {@code spec(start, 1, alignment, weight)}.
*
* @param start the start
* @param alignment the alignment
* @param weight the weight
*/
public static Spec spec(int start, Alignment alignment, float weight) {
return spec(start, 1, alignment, weight);
}
/**
* Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
* where {@code default_alignment} is specified in
* {@link android.widget.GridLayout.LayoutParams}.
*
* @param start the start
* @param size the size
* @param weight the weight
*/
public static Spec spec(int start, int size, float weight) {
return spec(start, size, UNDEFINED_ALIGNMENT, weight);
}
/**
* Equivalent to: {@code spec(start, 1, weight)}.
*
* @param start the start
* @param weight the weight
*/
public static Spec spec(int start, float weight) {
return spec(start, 1, weight);
}
/**
* Equivalent to: {@code spec(start, size, alignment, 0f)}.
*
* @param start the start
* @param size the size
* @param alignment the alignment
*/
public static Spec spec(int start, int size, Alignment alignment) {
return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
}
/**
* Return a Spec, {@code spec}, where:
*
* To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
* @param alignment the alignment
*
* @see #spec(int, int, Alignment)
*/
public static Spec spec(int start, Alignment alignment) {
return spec(start, 1, alignment);
}
/**
* Return a Spec, {@code spec}, where:
*
* To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start
* @param size the size
*
* @see #spec(int, Alignment)
*/
public static Spec spec(int start, int size) {
return spec(start, size, UNDEFINED_ALIGNMENT);
}
/**
* Return a Spec, {@code spec}, where:
*
* To leave the start index undefined, use the value {@link #UNDEFINED}.
*
* @param start the start index
*
* @see #spec(int, int)
*/
public static Spec spec(int start) {
return spec(start, 1);
}
/**
* Alignments specify where a view should be placed within a cell group and
* what size it should be.
*
* The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec}
* and a {@link LayoutParams#columnSpec columnSpec} each of which contains an
* {@code alignment}. Overall placement of the view in the cell
* group is specified by the two alignments which act along each axis independently.
*
* The GridLayout class defines the most common alignments used in general layout:
* {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #START},
* {@link #END}, {@link #CENTER}, {@link #BASELINE} and {@link #FILL}.
*/
/*
* An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
* to return the appropriate value for the type of alignment being defined.
* The enclosing algorithms position the children
* so that the locations defined by the alignment values
* are the same for all of the views in a group.
*
*/
public static abstract class Alignment {
Alignment() {
}
abstract int getGravityOffset(View view, int cellDelta);
/**
* Returns an alignment value. In the case of vertical alignments the value
* returned should indicate the distance from the top of the view to the
* alignment location.
* For horizontal alignments measurement is made from the left edge of the component.
*
* @param view the view to which this alignment should be applied
* @param viewSize the measured size of the view
* @param mode the basis of alignment: CLIP or OPTICAL
* @return the alignment value
*/
abstract int getAlignmentValue(View view, int viewSize, int mode);
/**
* Returns the size of the view specified by this alignment.
* In the case of vertical alignments this method should return a height; for
* horizontal alignments this method should return the width.
*
* The default implementation returns {@code viewSize}.
*
* @param view the view to which this alignment should be applied
* @param viewSize the measured size of the view
* @param cellSize the size of the cell into which this view will be placed
* @return the aligned size
*/
int getSizeInCell(View view, int viewSize, int cellSize) {
return viewSize;
}
Bounds getBounds() {
return new Bounds();
}
abstract String getDebugString();
@Override
public String toString() {
return "Alignment:" + getDebugString();
}
}
static final Alignment UNDEFINED_ALIGNMENT = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return UNDEFINED;
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
return UNDEFINED;
}
@Override
String getDebugString() {
return "UNDEFINED";
}
};
/**
* Indicates that a view should be aligned with the start
* edges of the other views in its cell group.
*/
private static final Alignment LEADING = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return 0;
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
return 0;
}
@Override
String getDebugString() {
return "LEADING";
}
};
/**
* Indicates that a view should be aligned with the end
* edges of the other views in its cell group.
*/
private static final Alignment TRAILING = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return cellDelta;
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
return viewSize;
}
@Override
String getDebugString() {
return "TRAILING";
}
};
/**
* Indicates that a view should be aligned with the top
* edges of the other views in its cell group.
*/
public static final Alignment TOP = LEADING;
/**
* Indicates that a view should be aligned with the bottom
* edges of the other views in its cell group.
*/
public static final Alignment BOTTOM = TRAILING;
/**
* Indicates that a view should be aligned with the start
* edges of the other views in its cell group.
*/
public static final Alignment START = LEADING;
/**
* Indicates that a view should be aligned with the end
* edges of the other views in its cell group.
*/
public static final Alignment END = TRAILING;
private static Alignment createSwitchingAlignment(final Alignment ltr, final Alignment rtl) {
return new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
boolean isLayoutRtl = ViewCompat.getLayoutDirection(view) ==
ViewCompat.LAYOUT_DIRECTION_RTL;
return (!isLayoutRtl ? ltr : rtl).getGravityOffset(view, cellDelta);
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
boolean isLayoutRtl = ViewCompat.getLayoutDirection(view) ==
ViewCompat.LAYOUT_DIRECTION_RTL;
return (!isLayoutRtl ? ltr : rtl).getAlignmentValue(view, viewSize, mode);
}
@Override
String getDebugString() {
return "SWITCHING[L:" + ltr.getDebugString() + ", R:" + rtl.getDebugString() + "]";
}
};
}
/**
* Indicates that a view should be aligned with the left
* edges of the other views in its cell group.
*/
public static final Alignment LEFT = createSwitchingAlignment(START, END);
/**
* Indicates that a view should be aligned with the right
* edges of the other views in its cell group.
*/
public static final Alignment RIGHT = createSwitchingAlignment(END, START);
/**
* Indicates that a view should be centered with the other views in its cell group.
* This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link
* LayoutParams#columnSpec columnSpecs}.
*/
public static final Alignment CENTER = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return cellDelta >> 1;
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
return viewSize >> 1;
}
@Override
String getDebugString() {
return "CENTER";
}
};
/**
* Indicates that a view should be aligned with the baselines
* of the other views in its cell group.
* This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}.
*
* @see View#getBaseline()
*/
public static final Alignment BASELINE = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return 0; // baseline gravity is top
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
if (view.getVisibility() == GONE) {
return 0;
}
int baseline = view.getBaseline();
return baseline == -1 ? UNDEFINED : baseline;
}
@Override
public Bounds getBounds() {
return new Bounds() {
/*
In a baseline aligned row in which some components define a baseline
and some don't, we need a third variable to properly account for all
the sizes. This tracks the maximum size of all the components -
including those that don't define a baseline.
*/
private int size;
@Override
protected void reset() {
super.reset();
size = Integer.MIN_VALUE;
}
@Override
protected void include(int before, int after) {
super.include(before, after);
size = max(size, before + after);
}
@Override
protected int size(boolean min) {
return max(super.size(min), size);
}
@Override
protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
return max(0, super.getOffset(gl, c, a, size, hrz));
}
};
}
@Override
String getDebugString() {
return "BASELINE";
}
};
/**
* Indicates that a view should expanded to fit the boundaries of its cell group.
* This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and
* {@link LayoutParams#columnSpec columnSpecs}.
*/
public static final Alignment FILL = new Alignment() {
@Override
int getGravityOffset(View view, int cellDelta) {
return 0;
}
@Override
public int getAlignmentValue(View view, int viewSize, int mode) {
return UNDEFINED;
}
@Override
public int getSizeInCell(View view, int viewSize, int cellSize) {
return cellSize;
}
@Override
String getDebugString() {
return "FILL";
}
};
static boolean canStretch(int flexibility) {
return (flexibility & CAN_STRETCH) != 0;
}
static final int INFLEXIBLE = 0;
static final int CAN_STRETCH = 2;
}
WRAP_CONTENT and MATCH_PARENT
*
* Because the default values of the {@link #width} and {@link #height}
* properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly
* declared in the layout parameters of GridLayout's children. In addition,
* GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from
* {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is
* instead controlled by the principle of flexibility,
* as discussed in {@link GridLayout}.
*
* Summary
*
* You should not need to use either of the special size values:
* {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of
* a GridLayout.
*
* Default values
*
*
*
*
* See {@link GridLayout} for a more complete description of the conventions
* used by GridLayout in the interpretation of the properties of this class.
*
* @attr name android:row
* @attr name android:rowSpan
* @attr name android:rowWeight
* @attr name android:column
* @attr name android:columnSpan
* @attr name android:columnWeight
* @attr name android:gravity
*/
public static class LayoutParams extends MarginLayoutParams {
// Default values
private static final int DEFAULT_WIDTH = WRAP_CONTENT;
private static final int DEFAULT_HEIGHT = WRAP_CONTENT;
private static final int DEFAULT_MARGIN = UNDEFINED;
private static final int DEFAULT_ROW = UNDEFINED;
private static final int DEFAULT_COLUMN = UNDEFINED;
private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1);
private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size();
// TypedArray indices
private static final int MARGIN = R.styleable.GridLayout_Layout_android_layout_margin;
private static final int LEFT_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginLeft;
private static final int TOP_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginTop;
private static final int RIGHT_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginRight;
private static final int BOTTOM_MARGIN = R.styleable.GridLayout_Layout_android_layout_marginBottom;
private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
// Instance variables
/**
* The spec that defines the vertical characteristics of the cell group
* described by these layout parameters.
* If an assignment is made to this field after a measurement or layout operation
* has already taken place, a call to
* {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
* must be made to notify GridLayout of the change. GridLayout is normally able
* to detect when code fails to observe this rule, issue a warning and take steps to
* compensate for the omission. This facility is implemented on a best effort basis
* and should not be relied upon in production code - so it is best to include the above
* calls to remove the warnings as soon as it is practical.
*/
public Spec rowSpec = Spec.UNDEFINED;
/**
* The spec that defines the horizontal characteristics of the cell group
* described by these layout parameters.
* If an assignment is made to this field after a measurement or layout operation
* has already taken place, a call to
* {@link ViewGroup#setLayoutParams(ViewGroup.LayoutParams)}
* must be made to notify GridLayout of the change. GridLayout is normally able
* to detect when code fails to observe this rule, issue a warning and take steps to
* compensate for the omission. This facility is implemented on a best effort basis
* and should not be relied upon in production code - so it is best to include the above
* calls to remove the warnings as soon as it is practical.
*/
public Spec columnSpec = Spec.UNDEFINED;
// Constructors
private LayoutParams(
int width, int height,
int left, int top, int right, int bottom,
Spec rowSpec, Spec columnSpec) {
super(width, height);
setMargins(left, top, right, bottom);
this.rowSpec = rowSpec;
this.columnSpec = columnSpec;
}
/**
* Constructs a new LayoutParams instance for this .row
= {@link #UNDEFINED} .rowSpan
= 1 .alignment
= {@link #BASELINE} .weight
= 0 .column
= {@link #UNDEFINED} .columnSpan
= 1 .alignment
= {@link #START} .weight
= 0 rowSpec
* and columnSpec
. All other fields are initialized with
* default values as defined in {@link LayoutParams}.
*
* @param rowSpec the rowSpec
* @param columnSpec the columnSpec
*/
public LayoutParams(Spec rowSpec, Spec columnSpec) {
this(DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN,
rowSpec, columnSpec);
}
/**
* Constructs a new LayoutParams with default values as defined in {@link LayoutParams}.
*/
public LayoutParams() {
this(Spec.UNDEFINED, Spec.UNDEFINED);
}
// Copying constructors
/**
* {@inheritDoc}
*/
public LayoutParams(ViewGroup.LayoutParams params) {
super(params);
}
/**
* {@inheritDoc}
*/
public LayoutParams(MarginLayoutParams params) {
super(params);
}
/**
* Copy constructor. Clones the width, height, margin values, row spec,
* and column spec of the source.
*
* @param source The layout params to copy from.
*/
public LayoutParams(LayoutParams source) {
super(source);
this.rowSpec = source.rowSpec;
this.columnSpec = source.columnSpec;
}
// AttributeSet constructors
/**
* {@inheritDoc}
*
* Values not defined in the attribute set take the default values
* defined in {@link LayoutParams}.
*/
public LayoutParams(Context context, AttributeSet attrs) {
super(context, attrs);
reInitSuper(context, attrs);
init(context, attrs);
}
// Implementation
// Reinitialise the margins using a different default policy than MarginLayoutParams.
// Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state
// so that a layout manager default can be accessed post set up. We need this as, at the
// point of installation, we do not know how many rows/cols there are and therefore
// which elements are positioned next to the container's trailing edges. We need to
// know this as margins around the container's boundary should have different
// defaults to those between peers.
// This method could be parametrized and moved into MarginLayout.
private void reInitSuper(Context context, AttributeSet attrs) {
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
try {
int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN);
this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin);
this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin);
this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin);
this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin);
} finally {
a.recycle();
}
}
private void init(Context context, AttributeSet attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout);
try {
int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY);
int column = a.getInt(COLUMN, DEFAULT_COLUMN);
int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
int row = a.getInt(ROW, DEFAULT_ROW);
int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
} finally {
a.recycle();
}
}
/**
* Describes how the child views are positioned. Default is {@code LEFT | BASELINE}.
* See {@link Gravity}.
*
* @param gravity the new gravity value
*
* @attr name android:gravity
*/
public void setGravity(int gravity) {
rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true));
}
@Override
protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) {
this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH);
this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT);
}
final void setRowSpecSpan(Interval span) {
rowSpec = rowSpec.copyWriteSpan(span);
}
final void setColumnSpecSpan(Interval span) {
columnSpec = columnSpec.copyWriteSpan(span);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LayoutParams that = (LayoutParams) o;
if (!columnSpec.equals(that.columnSpec)) return false;
if (!rowSpec.equals(that.rowSpec)) return false;
return true;
}
@Override
public int hashCode() {
int result = rowSpec.hashCode();
result = 31 * result + columnSpec.hashCode();
return result;
}
}
/*
In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs.
Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles.
*/
final static class Arc {
public final Interval span;
public final MutableInt value;
public boolean valid = true;
public Arc(Interval span, MutableInt value) {
this.span = span;
this.value = value;
}
@Override
public String toString() {
return span + " " + (!valid ? "+>" : "->") + " " + value;
}
}
// A mutable Integer - used to avoid heap allocation during the layout operation
final static class MutableInt {
public int value;
public MutableInt() {
reset();
}
public MutableInt(int value) {
this.value = value;
}
public void reset() {
value = Integer.MIN_VALUE;
}
@Override
public String toString() {
return Integer.toString(value);
}
}
final static class Assoc
*
*
* @param min the minimum value.
* @param max the maximum value.
*/
public Interval(int min, int max) {
this.min = min;
this.max = max;
}
int size() {
return max - min;
}
Interval inverse() {
return new Interval(max, min);
}
/**
* Returns {@code true} if the {@link #getClass class},
* {@link #min} and {@link #max} properties of this Interval and the
* supplied parameter are pairwise equal; {@code false} otherwise.
*
* @param that the object to compare this interval with
*
* @return {@code true} if the specified object is equal to this
* {@code Interval}, {@code false} otherwise.
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
Interval interval = (Interval) that;
if (max != interval.max) {
return false;
}
//noinspection RedundantIfStatement
if (min != interval.min) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = min;
result = 31 * result + max;
return result;
}
@Override
public String toString() {
return "[" + min + ", " + max + "]";
}
}
/**
* A Spec defines the horizontal or vertical characteristics of a group of
* cells. Each spec. defines the grid indices and alignment
* along the appropriate axis.
*
*
*
*/
public static class Spec {
static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
static final float DEFAULT_WEIGHT = 0;
final boolean startDefined;
final Interval span;
final Alignment alignment;
final float weight;
private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
this.startDefined = startDefined;
this.span = span;
this.alignment = alignment;
this.weight = weight;
}
Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
this(startDefined, new Interval(start, start + size), alignment, weight);
}
public Alignment getAbsoluteAlignment(boolean horizontal) {
if (alignment != UNDEFINED_ALIGNMENT) {
return alignment;
}
if (weight == 0f) {
return horizontal ? START : BASELINE;
}
return FILL;
}
final Spec copyWriteSpan(Interval span) {
return new Spec(startDefined, span, alignment, weight);
}
final Spec copyWriteAlignment(Alignment alignment) {
return new Spec(startDefined, span, alignment, weight);
}
final int getFlexibility() {
return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
}
/**
* Returns {@code true} if the {@code class}, {@code alignment} and {@code span}
* properties of this Spec and the supplied parameter are pairwise equal,
* {@code false} otherwise.
*
* @param that the object to compare this spec with
*
* @return {@code true} if the specified object is equal to this
* {@code Spec}; {@code false} otherwise
*/
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null || getClass() != that.getClass()) {
return false;
}
Spec spec = (Spec) that;
if (!alignment.equals(spec.alignment)) {
return false;
}
//noinspection RedundantIfStatement
if (!span.equals(spec.span)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = span.hashCode();
result = 31 * result + alignment.hashCode();
return result;
}
}
/**
* Return a Spec, {@code spec}, where:
*
*
*
*
*
*
*
*
*