├── .gitignore ├── LICENSE ├── README.md ├── library ├── AndroidManifest.xml ├── proguard.cfg ├── project.properties ├── res │ └── values │ │ ├── attrs.xml │ │ └── dimens.xml └── src │ └── com │ └── gridlayout │ ├── GridLayout.java │ └── Space.java └── samples ├── AndroidManifest.xml ├── proguard.cfg ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-ldpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── layout │ ├── activity_gridlayout_visibility.xml │ └── activity_simple_gridlayout.xml └── values │ └── strings.xml └── src └── com └── gridlayout └── samples ├── GridLayoutSamplesActivity.java ├── GridLayoutVisibilityActivity.java └── SimpleGridLayoutActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | .settings/ 4 | .project 5 | .classpath 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GridLayout Library 2 | ================== 3 | 4 | This library provides a version of [GridLayout](http://developer.android.com/reference/android/widget/GridLayout.html) that works across all versions of Android 1.5+. As a side effect, this library also includes the lightweight [Space](http://developer.android.com/reference/android/widget/Space.html) as well. 5 | 6 | For an introduction to GridLayout/Space, [check out the Android Developers Blog post about the benefits and usage of GridLayout and Space](http://android-developers.blogspot.com/2011/11/new-layout-widgets-space-and-gridlayout.html). 7 | 8 | Compilation 9 | =========== 10 | 11 | Compiling this library requires that the build SDK version be set to 3.0 or above. (It uses some more modern methods if they are available.) 12 | 13 | Usage 14 | ===== 15 | 16 | This is a copy of the GridLayout from Android, so its usage is very similar. The only difference is that some attributes you will need to use your project's namespace instead of the android namespace: 17 | 18 | 26 | 27 | Caveats 28 | ======= 29 | 30 | Due to the inability to detect changes in child visibility in older versions of ViewGroup, it is necessary to call `GridLayout.notifyChildVisibilityChanged()` whenever you change the visibility of a child View of a GridLayout. 31 | 32 | If you never change the visibility of children, you don't have to worry about this. -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /library/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-15 12 | android.library=true 13 | -------------------------------------------------------------------------------- /library/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | 41 | 42 | 47 | 48 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 68 | 69 | 71 | 72 | 76 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /library/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16dip 6 | 7 | -------------------------------------------------------------------------------- /library/src/com/gridlayout/GridLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.gridlayout; 18 | 19 | import static android.view.Gravity.AXIS_PULL_AFTER; 20 | import static android.view.Gravity.AXIS_PULL_BEFORE; 21 | import static android.view.Gravity.AXIS_SPECIFIED; 22 | import static android.view.Gravity.AXIS_X_SHIFT; 23 | import static android.view.Gravity.AXIS_Y_SHIFT; 24 | import static android.view.Gravity.HORIZONTAL_GRAVITY_MASK; 25 | import static android.view.Gravity.VERTICAL_GRAVITY_MASK; 26 | import static android.view.View.MeasureSpec.EXACTLY; 27 | import static android.view.View.MeasureSpec.makeMeasureSpec; 28 | import static java.lang.Math.max; 29 | import static java.lang.Math.min; 30 | 31 | import java.lang.reflect.Array; 32 | import java.util.ArrayList; 33 | import java.util.Arrays; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | import android.content.Context; 39 | import android.content.res.TypedArray; 40 | import android.graphics.Canvas; 41 | import android.graphics.Color; 42 | import android.graphics.Paint; 43 | import android.util.AttributeSet; 44 | import android.util.Log; 45 | import android.view.Gravity; 46 | import android.view.View; 47 | import android.view.ViewGroup; 48 | import android.widget.LinearLayout; 49 | 50 | /** 51 | * This is a fully backwards-compatible version of GridLayout, which works 52 | * all the way back to Android 1.5. 53 | * 54 | * IMPORTANT: There is one difference between this GridLayout and the one 55 | * the default. When you change the visibility of a child View, you must 56 | * also call GridLayout.notifyChildVisibilityChanged(). This workaround 57 | * exists because there is no other way to detect child visibility changes 58 | * in a ViewGroup on older versions of Android. 59 | * 60 | * Projects using this class must use at least Android SDK 11+. (It is 61 | * compatible back to 1.5, but wraps potentially beneficial methods for 62 | * newer versions of Android.) 63 | * 64 | * @author Daniel Lew (danlew42@gmail.com) 65 | */ 66 | public class GridLayout extends ViewGroup { 67 | 68 | // Public constants 69 | 70 | /** 71 | * The horizontal orientation. 72 | */ 73 | public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 74 | 75 | /** 76 | * The vertical orientation. 77 | */ 78 | public static final int VERTICAL = LinearLayout.VERTICAL; 79 | 80 | /** 81 | * The constant used to indicate that a value is undefined. 82 | * Fields can use this value to indicate that their values 83 | * have not yet been set. Similarly, methods can return this value 84 | * to indicate that there is no suitable value that the implementation 85 | * can return. 86 | * The value used for the constant (currently {@link Integer#MIN_VALUE}) is 87 | * intended to avoid confusion between valid values whose sign may not be known. 88 | */ 89 | public static final int UNDEFINED = Integer.MIN_VALUE; 90 | 91 | /** 92 | * This constant is an {@link #setAlignmentMode(int) alignmentMode}. 93 | * When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment 94 | * is made between the edges of each component's raw 95 | * view boundary: i.e. the area delimited by the component's: 96 | * {@link android.view.View#getTop() top}, 97 | * {@link android.view.View#getLeft() left}, 98 | * {@link android.view.View#getBottom() bottom} and 99 | * {@link android.view.View#getRight() right} properties. 100 | *

101 | * For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode, 102 | * children that belong to a row group that uses {@link #TOP} alignment will 103 | * all return the same value when their {@link android.view.View#getTop()} 104 | * method is called. 105 | * 106 | * @see #setAlignmentMode(int) 107 | */ 108 | public static final int ALIGN_BOUNDS = 0; 109 | 110 | /** 111 | * This constant is an {@link #setAlignmentMode(int) alignmentMode}. 112 | * When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS}, 113 | * the bounds of each view are extended outwards, according 114 | * to their margins, before the edges of the resulting rectangle are aligned. 115 | *

116 | * For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode, 117 | * the quantity {@code top - layoutParams.topMargin} is the same for all children that 118 | * belong to a row group that uses {@link #TOP} alignment. 119 | * 120 | * @see #setAlignmentMode(int) 121 | */ 122 | public static final int ALIGN_MARGINS = 1; 123 | 124 | // Misc constants 125 | 126 | static final String TAG = GridLayout.class.getName(); 127 | static final boolean DEBUG = false; 128 | static final int PRF = 1; 129 | static final int MAX_SIZE = 100000; 130 | static final int DEFAULT_CONTAINER_MARGIN = 0; 131 | 132 | // Defaults 133 | 134 | private static final int DEFAULT_ORIENTATION = HORIZONTAL; 135 | private static final int DEFAULT_COUNT = UNDEFINED; 136 | private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false; 137 | private static final boolean DEFAULT_ORDER_PRESERVED = true; 138 | private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS; 139 | 140 | // TypedArray indices 141 | 142 | private static final int ORIENTATION = R.styleable.GridLayout_android_orientation; 143 | private static final int ROW_COUNT = R.styleable.GridLayout_rowCount; 144 | private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount; 145 | private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins; 146 | private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode; 147 | private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved; 148 | private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved; 149 | 150 | // Instance variables 151 | 152 | final Axis horizontalAxis = new Axis(true); 153 | final Axis verticalAxis = new Axis(false); 154 | boolean layoutParamsValid = false; 155 | int orientation = DEFAULT_ORIENTATION; 156 | boolean useDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS; 157 | int alignmentMode = DEFAULT_ALIGNMENT_MODE; 158 | int defaultGap; 159 | 160 | // Constructors 161 | 162 | /** 163 | * {@inheritDoc} 164 | */ 165 | public GridLayout(Context context, AttributeSet attrs, int defStyle) { 166 | super(context, attrs, defStyle); 167 | if (DEBUG) { 168 | setWillNotDraw(false); 169 | } 170 | defaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap); 171 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout); 172 | try { 173 | setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT)); 174 | setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT)); 175 | setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION)); 176 | setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS)); 177 | setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE)); 178 | setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); 179 | setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED)); 180 | } finally { 181 | a.recycle(); 182 | } 183 | 184 | // Set our own custom hierarchy listener, so we can properly implement onViewAdded() and onViewRemoved() 185 | super.setOnHierarchyChangeListener(GRIDLAYOUT_LISTENER); 186 | } 187 | 188 | /** 189 | * {@inheritDoc} 190 | */ 191 | public GridLayout(Context context, AttributeSet attrs) { 192 | this(context, attrs, 0); 193 | } 194 | 195 | /** 196 | * {@inheritDoc} 197 | */ 198 | public GridLayout(Context context) { 199 | //noinspection NullableProblems 200 | this(context, null); 201 | } 202 | 203 | // Implementation 204 | 205 | /** 206 | * Returns the current orientation. 207 | * 208 | * @return either {@link #HORIZONTAL} or {@link #VERTICAL} 209 | * 210 | * @see #setOrientation(int) 211 | * 212 | * @attr ref android.R.styleable#GridLayout_orientation 213 | */ 214 | public int getOrientation() { 215 | return orientation; 216 | } 217 | 218 | /** 219 | * Orientation is used only to generate default row/column indices when 220 | * they are not specified by a component's layout parameters. 221 | *

222 | * The default value of this property is {@link #HORIZONTAL}. 223 | * 224 | * @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL} 225 | * 226 | * @see #getOrientation() 227 | * 228 | * @attr ref android.R.styleable#GridLayout_orientation 229 | */ 230 | public void setOrientation(int orientation) { 231 | if (this.orientation != orientation) { 232 | this.orientation = orientation; 233 | invalidateStructure(); 234 | requestLayout(); 235 | } 236 | } 237 | 238 | /** 239 | * Returns the current number of rows. This is either the last value that was set 240 | * with {@link #setRowCount(int)} or, if no such value was set, the maximum 241 | * value of each the upper bounds defined in {@link LayoutParams#rowSpec}. 242 | * 243 | * @return the current number of rows 244 | * 245 | * @see #setRowCount(int) 246 | * @see LayoutParams#rowSpec 247 | * 248 | * @attr ref android.R.styleable#GridLayout_rowCount 249 | */ 250 | public int getRowCount() { 251 | return verticalAxis.getCount(); 252 | } 253 | 254 | /** 255 | * RowCount is used only to generate default row/column indices when 256 | * they are not specified by a component's layout parameters. 257 | * 258 | * @param rowCount the number of rows 259 | * 260 | * @see #getRowCount() 261 | * @see LayoutParams#rowSpec 262 | * 263 | * @attr ref android.R.styleable#GridLayout_rowCount 264 | */ 265 | public void setRowCount(int rowCount) { 266 | verticalAxis.setCount(rowCount); 267 | invalidateStructure(); 268 | requestLayout(); 269 | } 270 | 271 | /** 272 | * Returns the current number of columns. This is either the last value that was set 273 | * with {@link #setColumnCount(int)} or, if no such value was set, the maximum 274 | * value of each the upper bounds defined in {@link LayoutParams#columnSpec}. 275 | * 276 | * @return the current number of columns 277 | * 278 | * @see #setColumnCount(int) 279 | * @see LayoutParams#columnSpec 280 | * 281 | * @attr ref android.R.styleable#GridLayout_columnCount 282 | */ 283 | public int getColumnCount() { 284 | return horizontalAxis.getCount(); 285 | } 286 | 287 | /** 288 | * ColumnCount is used only to generate default column/column indices when 289 | * they are not specified by a component's layout parameters. 290 | * 291 | * @param columnCount the number of columns. 292 | * 293 | * @see #getColumnCount() 294 | * @see LayoutParams#columnSpec 295 | * 296 | * @attr ref android.R.styleable#GridLayout_columnCount 297 | */ 298 | public void setColumnCount(int columnCount) { 299 | horizontalAxis.setCount(columnCount); 300 | invalidateStructure(); 301 | requestLayout(); 302 | } 303 | 304 | /** 305 | * Returns whether or not this GridLayout will allocate default margins when no 306 | * corresponding layout parameters are defined. 307 | * 308 | * @return {@code true} if default margins should be allocated 309 | * 310 | * @see #setUseDefaultMargins(boolean) 311 | * 312 | * @attr ref android.R.styleable#GridLayout_useDefaultMargins 313 | */ 314 | public boolean getUseDefaultMargins() { 315 | return useDefaultMargins; 316 | } 317 | 318 | /** 319 | * When {@code true}, GridLayout allocates default margins around children 320 | * based on the child's visual characteristics. Each of the 321 | * margins so defined may be independently overridden by an assignment 322 | * to the appropriate layout parameter. 323 | *

324 | * When {@code false}, the default value of all margins is zero. 325 | *

326 | * When setting to {@code true}, consider setting the value of the 327 | * {@link #setAlignmentMode(int) alignmentMode} 328 | * property to {@link #ALIGN_BOUNDS}. 329 | *

330 | * The default value of this property is {@code false}. 331 | * 332 | * @param useDefaultMargins use {@code true} to make GridLayout allocate default margins 333 | * 334 | * @see #getUseDefaultMargins() 335 | * @see #setAlignmentMode(int) 336 | * 337 | * @see MarginLayoutParams#leftMargin 338 | * @see MarginLayoutParams#topMargin 339 | * @see MarginLayoutParams#rightMargin 340 | * @see MarginLayoutParams#bottomMargin 341 | * 342 | * @attr ref android.R.styleable#GridLayout_useDefaultMargins 343 | */ 344 | public void setUseDefaultMargins(boolean useDefaultMargins) { 345 | this.useDefaultMargins = useDefaultMargins; 346 | requestLayout(); 347 | } 348 | 349 | /** 350 | * Returns the alignment mode. 351 | * 352 | * @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} 353 | * 354 | * @see #ALIGN_BOUNDS 355 | * @see #ALIGN_MARGINS 356 | * 357 | * @see #setAlignmentMode(int) 358 | * 359 | * @attr ref android.R.styleable#GridLayout_alignmentMode 360 | */ 361 | public int getAlignmentMode() { 362 | return alignmentMode; 363 | } 364 | 365 | /** 366 | * Sets the alignment mode to be used for all of the alignments between the 367 | * children of this container. 368 | *

369 | * The default value of this property is {@link #ALIGN_MARGINS}. 370 | * 371 | * @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS} 372 | * 373 | * @see #ALIGN_BOUNDS 374 | * @see #ALIGN_MARGINS 375 | * 376 | * @see #getAlignmentMode() 377 | * 378 | * @attr ref android.R.styleable#GridLayout_alignmentMode 379 | */ 380 | public void setAlignmentMode(int alignmentMode) { 381 | this.alignmentMode = alignmentMode; 382 | requestLayout(); 383 | } 384 | 385 | /** 386 | * Returns whether or not row boundaries are ordered by their grid indices. 387 | * 388 | * @return {@code true} if row boundaries must appear in the order of their indices, 389 | * {@code false} otherwise 390 | * 391 | * @see #setRowOrderPreserved(boolean) 392 | * 393 | * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 394 | */ 395 | public boolean isRowOrderPreserved() { 396 | return verticalAxis.isOrderPreserved(); 397 | } 398 | 399 | /** 400 | * When this property is {@code true}, GridLayout is forced to place the row boundaries 401 | * so that their associated grid indices are in ascending order in the view. 402 | *

403 | * When this property is {@code false} GridLayout is at liberty to place the vertical row 404 | * boundaries in whatever order best fits the given constraints. 405 | *

406 | * The default value of this property is {@code true}. 407 | 408 | * @param rowOrderPreserved {@code true} to force GridLayout to respect the order 409 | * of row boundaries 410 | * 411 | * @see #isRowOrderPreserved() 412 | * 413 | * @attr ref android.R.styleable#GridLayout_rowOrderPreserved 414 | */ 415 | public void setRowOrderPreserved(boolean rowOrderPreserved) { 416 | verticalAxis.setOrderPreserved(rowOrderPreserved); 417 | invalidateStructure(); 418 | requestLayout(); 419 | } 420 | 421 | /** 422 | * Returns whether or not column boundaries are ordered by their grid indices. 423 | * 424 | * @return {@code true} if column boundaries must appear in the order of their indices, 425 | * {@code false} otherwise 426 | * 427 | * @see #setColumnOrderPreserved(boolean) 428 | * 429 | * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 430 | */ 431 | public boolean isColumnOrderPreserved() { 432 | return horizontalAxis.isOrderPreserved(); 433 | } 434 | 435 | /** 436 | * When this property is {@code true}, GridLayout is forced to place the column boundaries 437 | * so that their associated grid indices are in ascending order in the view. 438 | *

439 | * When this property is {@code false} GridLayout is at liberty to place the horizontal column 440 | * boundaries in whatever order best fits the given constraints. 441 | *

442 | * The default value of this property is {@code true}. 443 | * 444 | * @param columnOrderPreserved use {@code true} to force GridLayout to respect the order 445 | * of column boundaries. 446 | * 447 | * @see #isColumnOrderPreserved() 448 | * 449 | * @attr ref android.R.styleable#GridLayout_columnOrderPreserved 450 | */ 451 | public void setColumnOrderPreserved(boolean columnOrderPreserved) { 452 | horizontalAxis.setOrderPreserved(columnOrderPreserved); 453 | invalidateStructure(); 454 | requestLayout(); 455 | } 456 | 457 | // Static utility methods 458 | 459 | static int max2(int[] a, int valueIfEmpty) { 460 | int result = valueIfEmpty; 461 | for (int i = 0, N = a.length; i < N; i++) { 462 | result = Math.max(result, a[i]); 463 | } 464 | return result; 465 | } 466 | 467 | @SuppressWarnings("unchecked") 468 | static T[] append(T[] a, T[] b) { 469 | T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length); 470 | System.arraycopy(a, 0, result, 0, a.length); 471 | System.arraycopy(b, 0, result, a.length, b.length); 472 | return result; 473 | } 474 | 475 | static Alignment getAlignment(int gravity, boolean horizontal) { 476 | int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK; 477 | int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT; 478 | int flags = (gravity & mask) >> shift; 479 | switch (flags) { 480 | case (AXIS_SPECIFIED | AXIS_PULL_BEFORE): 481 | return LEADING; 482 | case (AXIS_SPECIFIED | AXIS_PULL_AFTER): 483 | return TRAILING; 484 | case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER): 485 | return FILL; 486 | case AXIS_SPECIFIED: 487 | return CENTER; 488 | default: 489 | return UNDEFINED_ALIGNMENT; 490 | } 491 | } 492 | 493 | /** @noinspection UnusedParameters*/ 494 | private int getDefaultMargin(View c, boolean horizontal, boolean leading) { 495 | if (c.getClass() == Space.class) { 496 | return 0; 497 | } 498 | return defaultGap / 2; 499 | } 500 | 501 | private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) { 502 | return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading); 503 | } 504 | 505 | private int getDefaultMarginValue(View c, LayoutParams p, boolean horizontal, boolean leading) { 506 | if (!useDefaultMargins) { 507 | return 0; 508 | } 509 | Spec spec = horizontal ? p.columnSpec : p.rowSpec; 510 | Axis axis = horizontal ? horizontalAxis : verticalAxis; 511 | Interval span = spec.span; 512 | boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount()); 513 | 514 | return getDefaultMargin(c, isAtEdge, horizontal, leading); 515 | } 516 | 517 | int getMargin1(View view, boolean horizontal, boolean leading) { 518 | LayoutParams lp = getLayoutParams(view); 519 | int margin = horizontal ? 520 | (leading ? lp.leftMargin : lp.rightMargin) : 521 | (leading ? lp.topMargin : lp.bottomMargin); 522 | return margin == UNDEFINED ? getDefaultMarginValue(view, lp, horizontal, leading) : margin; 523 | } 524 | 525 | private int getMargin(View view, boolean horizontal, boolean leading) { 526 | if (alignmentMode == ALIGN_MARGINS) { 527 | return getMargin1(view, horizontal, leading); 528 | } else { 529 | Axis axis = horizontal ? horizontalAxis : verticalAxis; 530 | int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins(); 531 | LayoutParams lp = getLayoutParams(view); 532 | Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 533 | int index = leading ? spec.span.min : spec.span.max; 534 | return margins[index]; 535 | } 536 | } 537 | 538 | private int getTotalMargin(View child, boolean horizontal) { 539 | return getMargin(child, horizontal, true) + getMargin(child, horizontal, false); 540 | } 541 | 542 | private static boolean fits(int[] a, int value, int start, int end) { 543 | if (end > a.length) { 544 | return false; 545 | } 546 | for (int i = start; i < end; i++) { 547 | if (a[i] > value) { 548 | return false; 549 | } 550 | } 551 | return true; 552 | } 553 | 554 | private static void procrusteanFill(int[] a, int start, int end, int value) { 555 | int length = a.length; 556 | Arrays.fill(a, Math.min(start, length), Math.min(end, length), value); 557 | } 558 | 559 | private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) { 560 | lp.setRowSpecSpan(new Interval(row, row + rowSpan)); 561 | lp.setColumnSpecSpan(new Interval(col, col + colSpan)); 562 | } 563 | 564 | // Logic to avert infinite loops by ensuring that the cells can be placed somewhere. 565 | private static int clip(Interval minorRange, boolean minorWasDefined, int count) { 566 | int size = minorRange.size(); 567 | if (count == 0) { 568 | return size; 569 | } 570 | int min = minorWasDefined ? min(minorRange.min, count) : 0; 571 | return min(size, count - min); 572 | } 573 | 574 | // install default indices for cells that don't define them 575 | private void validateLayoutParams() { 576 | final boolean horizontal = (orientation == HORIZONTAL); 577 | final Axis axis = horizontal ? horizontalAxis : verticalAxis; 578 | final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0; 579 | 580 | int major = 0; 581 | int minor = 0; 582 | int[] maxSizes = new int[count]; 583 | 584 | for (int i = 0, N = getChildCount(); i < N; i++) { 585 | LayoutParams lp = getLayoutParams1(getChildAt(i)); 586 | 587 | final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec; 588 | final Interval majorRange = majorSpec.span; 589 | final boolean majorWasDefined = majorSpec.startDefined; 590 | final int majorSpan = majorRange.size(); 591 | if (majorWasDefined) { 592 | major = majorRange.min; 593 | } 594 | 595 | final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec; 596 | final Interval minorRange = minorSpec.span; 597 | final boolean minorWasDefined = minorSpec.startDefined; 598 | final int minorSpan = clip(minorRange, minorWasDefined, count); 599 | if (minorWasDefined) { 600 | minor = minorRange.min; 601 | } 602 | 603 | if (count != 0) { 604 | // Find suitable row/col values when at least one is undefined. 605 | if (!majorWasDefined || !minorWasDefined) { 606 | while (!fits(maxSizes, major, minor, minor + minorSpan)) { 607 | if (minorWasDefined) { 608 | major++; 609 | } else { 610 | if (minor + minorSpan <= count) { 611 | minor++; 612 | } else { 613 | minor = 0; 614 | major++; 615 | } 616 | } 617 | } 618 | } 619 | procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan); 620 | } 621 | 622 | if (horizontal) { 623 | setCellGroup(lp, major, majorSpan, minor, minorSpan); 624 | } else { 625 | setCellGroup(lp, minor, minorSpan, major, majorSpan); 626 | } 627 | 628 | minor = minor + minorSpan; 629 | } 630 | invalidateStructure(); 631 | } 632 | 633 | private void invalidateStructure() { 634 | layoutParamsValid = false; 635 | horizontalAxis.invalidateStructure(); 636 | verticalAxis.invalidateStructure(); 637 | // This can end up being done twice. Better twice than not at all. 638 | invalidateValues(); 639 | } 640 | 641 | private void invalidateValues() { 642 | // Need null check because requestLayout() is called in View's initializer, 643 | // before we are set up. 644 | if (horizontalAxis != null && verticalAxis != null) { 645 | horizontalAxis.invalidateValues(); 646 | verticalAxis.invalidateValues(); 647 | } 648 | } 649 | 650 | private LayoutParams getLayoutParams1(View c) { 651 | return (LayoutParams) c.getLayoutParams(); 652 | } 653 | 654 | final LayoutParams getLayoutParams(View c) { 655 | if (!layoutParamsValid) { 656 | validateLayoutParams(); 657 | layoutParamsValid = true; 658 | } 659 | return getLayoutParams1(c); 660 | } 661 | 662 | @Override 663 | protected LayoutParams generateDefaultLayoutParams() { 664 | return new LayoutParams(); 665 | } 666 | 667 | @Override 668 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 669 | return new LayoutParams(getContext(), attrs); 670 | } 671 | 672 | @Override 673 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 674 | return new LayoutParams(p); 675 | } 676 | 677 | // Draw grid 678 | 679 | private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) { 680 | int dx = getPaddingLeft(); 681 | int dy = getPaddingTop(); 682 | graphics.drawLine(dx + x1, dy + y1, dx + x2, dy + y2, paint); 683 | } 684 | 685 | private static void drawRect(Canvas canvas, int x1, int y1, int x2, int y2, Paint paint) { 686 | canvas.drawRect(x1, y1, x2 - 1, y2 - 1, paint); 687 | } 688 | 689 | @Override 690 | protected void onDraw(Canvas canvas) { 691 | super.onDraw(canvas); 692 | 693 | if (DEBUG) { 694 | int height = getHeight() - getPaddingTop() - getPaddingBottom(); 695 | int width = getWidth() - getPaddingLeft() - getPaddingRight(); 696 | 697 | Paint paint = new Paint(); 698 | paint.setStyle(Paint.Style.STROKE); 699 | paint.setColor(Color.argb(50, 255, 255, 255)); 700 | 701 | int[] xs = horizontalAxis.locations; 702 | if (xs != null) { 703 | for (int i = 0, length = xs.length; i < length; i++) { 704 | int x = xs[i]; 705 | drawLine(canvas, x, 0, x, height - 1, paint); 706 | } 707 | } 708 | 709 | int[] ys = verticalAxis.locations; 710 | if (ys != null) { 711 | for (int i = 0, length = ys.length; i < length; i++) { 712 | int y = ys[i]; 713 | drawLine(canvas, 0, y, width - 1, y, paint); 714 | } 715 | } 716 | 717 | // Draw bounds 718 | paint.setColor(Color.BLUE); 719 | for (int i = 0; i < getChildCount(); i++) { 720 | View c = getChildAt(i); 721 | drawRect(canvas, c.getLeft(), c.getTop(), c.getRight(), c.getBottom(), paint); 722 | } 723 | 724 | // Draw margins 725 | paint.setColor(Color.MAGENTA); 726 | for (int i = 0; i < getChildCount(); i++) { 727 | View c = getChildAt(i); 728 | drawRect(canvas, 729 | c.getLeft() - getMargin1(c, true, true), 730 | c.getTop() - getMargin1(c, false, true), 731 | c.getRight() + getMargin1(c, true, false), 732 | c.getBottom() + getMargin1(c, false, false), paint); 733 | } 734 | } 735 | } 736 | 737 | // Measurement 738 | 739 | final boolean isGone(View c) { 740 | return c.getVisibility() == View.GONE; 741 | } 742 | 743 | private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec, 744 | int childWidth, int childHeight) { 745 | int childWidthSpec = getChildMeasureSpec(parentWidthSpec, 746 | getPaddingLeft() + getPaddingRight() + getTotalMargin(child, true), childWidth); 747 | int childHeightSpec = getChildMeasureSpec(parentHeightSpec, 748 | getPaddingTop() + getPaddingBottom() + getTotalMargin(child, false), childHeight); 749 | child.measure(childWidthSpec, childHeightSpec); 750 | } 751 | 752 | private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) { 753 | for (int i = 0, N = getChildCount(); i < N; i++) { 754 | View c = getChildAt(i); 755 | if (isGone(c)) continue; 756 | LayoutParams lp = getLayoutParams(c); 757 | if (firstPass) { 758 | measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); 759 | } else { 760 | boolean horizontal = (orientation == HORIZONTAL); 761 | Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 762 | if (spec.alignment == FILL) { 763 | Interval span = spec.span; 764 | Axis axis = horizontal ? horizontalAxis : verticalAxis; 765 | int[] locations = axis.getLocations(); 766 | int cellSize = locations[span.max] - locations[span.min]; 767 | int viewSize = cellSize - getTotalMargin(c, horizontal); 768 | if (horizontal) { 769 | measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height); 770 | } else { 771 | measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize); 772 | } 773 | } 774 | } 775 | } 776 | } 777 | 778 | @Override 779 | protected void onMeasure(int widthSpec, int heightSpec) { 780 | /** If we have been called by {@link View#measure(int, int)}, one of width or height 781 | * is likely to have changed. We must invalidate if so. */ 782 | invalidateValues(); 783 | 784 | measureChildrenWithMargins(widthSpec, heightSpec, true); 785 | 786 | int width, height; 787 | 788 | // Use the orientation property to decide which axis should be laid out first. 789 | if (orientation == HORIZONTAL) { 790 | width = horizontalAxis.getMeasure(widthSpec); 791 | measureChildrenWithMargins(widthSpec, heightSpec, false); 792 | height = verticalAxis.getMeasure(heightSpec); 793 | } else { 794 | height = verticalAxis.getMeasure(heightSpec); 795 | measureChildrenWithMargins(widthSpec, heightSpec, false); 796 | width = horizontalAxis.getMeasure(widthSpec); 797 | } 798 | 799 | int hPadding = getPaddingLeft() + getPaddingRight(); 800 | int vPadding = getPaddingTop() + getPaddingBottom(); 801 | 802 | int measuredWidth = Math.max(hPadding + width, getSuggestedMinimumWidth()); 803 | int measuredHeight = Math.max(vPadding + height, getSuggestedMinimumHeight()); 804 | 805 | if (mResolveSizeAndStateAvailable) { 806 | measuredWidth = ResolveSizeAndStateWrapper.resolveSizeAndState(measuredWidth, widthSpec, 0); 807 | measuredHeight = ResolveSizeAndStateWrapper.resolveSizeAndState(measuredHeight, heightSpec, 0); 808 | } 809 | else { 810 | measuredWidth = resolveSize(measuredWidth, widthSpec); 811 | measuredHeight = resolveSize(measuredHeight, heightSpec); 812 | } 813 | 814 | setMeasuredDimension(measuredWidth, measuredHeight); 815 | } 816 | 817 | private int protect(int alignment) { 818 | return (alignment == UNDEFINED) ? 0 : alignment; 819 | } 820 | 821 | private int getMeasurement(View c, boolean horizontal) { 822 | return horizontal ? c.getMeasuredWidth() : c.getMeasuredHeight(); 823 | } 824 | 825 | final int getMeasurementIncludingMargin(View c, boolean horizontal) { 826 | if (isGone(c)) { 827 | return 0; 828 | } 829 | return getMeasurement(c, horizontal) + getTotalMargin(c, horizontal); 830 | } 831 | 832 | @Override 833 | public void requestLayout() { 834 | super.requestLayout(); 835 | invalidateValues(); 836 | } 837 | 838 | final Alignment getAlignment(Alignment alignment, boolean horizontal) { 839 | return (alignment != UNDEFINED_ALIGNMENT) ? alignment : 840 | (horizontal ? LEFT : BASELINE); 841 | } 842 | 843 | // Layout container 844 | 845 | /** 846 | * {@inheritDoc} 847 | */ 848 | /* 849 | The layout operation is implemented by delegating the heavy lifting to the 850 | to the mHorizontalAxis and mVerticalAxis instances of the internal Axis class. 851 | Together they compute the locations of the vertical and horizontal lines of 852 | the grid (respectively!). 853 | 854 | This method is then left with the simpler task of applying margins, gravity 855 | and sizing to each child view and then placing it in its cell. 856 | */ 857 | @Override 858 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 859 | int targetWidth = right - left; 860 | int targetHeight = bottom - top; 861 | 862 | int paddingLeft = getPaddingLeft(); 863 | int paddingTop = getPaddingTop(); 864 | int paddingRight = getPaddingRight(); 865 | int paddingBottom = getPaddingBottom(); 866 | 867 | horizontalAxis.layout(targetWidth - paddingLeft - paddingRight); 868 | verticalAxis.layout(targetHeight - paddingTop - paddingBottom); 869 | 870 | int[] hLocations = horizontalAxis.getLocations(); 871 | int[] vLocations = verticalAxis.getLocations(); 872 | 873 | for (int i = 0, N = getChildCount(); i < N; i++) { 874 | View c = getChildAt(i); 875 | if (isGone(c)) continue; 876 | LayoutParams lp = getLayoutParams(c); 877 | Spec columnSpec = lp.columnSpec; 878 | Spec rowSpec = lp.rowSpec; 879 | 880 | Interval colSpan = columnSpec.span; 881 | Interval rowSpan = rowSpec.span; 882 | 883 | int x1 = hLocations[colSpan.min]; 884 | int y1 = vLocations[rowSpan.min]; 885 | 886 | int x2 = hLocations[colSpan.max]; 887 | int y2 = vLocations[rowSpan.max]; 888 | 889 | int cellWidth = x2 - x1; 890 | int cellHeight = y2 - y1; 891 | 892 | int pWidth = getMeasurement(c, true); 893 | int pHeight = getMeasurement(c, false); 894 | 895 | Alignment hAlign = getAlignment(columnSpec.alignment, true); 896 | Alignment vAlign = getAlignment(rowSpec.alignment, false); 897 | 898 | int dx, dy; 899 | 900 | Bounds colBounds = horizontalAxis.getGroupBounds().getValue(i); 901 | Bounds rowBounds = verticalAxis.getGroupBounds().getValue(i); 902 | 903 | // Gravity offsets: the location of the alignment group relative to its cell group. 904 | //noinspection NullableProblems 905 | int c2ax = protect(hAlign.getAlignmentValue(null, cellWidth - colBounds.size(true))); 906 | //noinspection NullableProblems 907 | int c2ay = protect(vAlign.getAlignmentValue(null, cellHeight - rowBounds.size(true))); 908 | 909 | int leftMargin = getMargin(c, true, true); 910 | int topMargin = getMargin(c, false, true); 911 | int rightMargin = getMargin(c, true, false); 912 | int bottomMargin = getMargin(c, false, false); 913 | 914 | // Same calculation as getMeasurementIncludingMargin() 915 | int mWidth = leftMargin + pWidth + rightMargin; 916 | int mHeight = topMargin + pHeight + bottomMargin; 917 | 918 | // Alignment offsets: the location of the view relative to its alignment group. 919 | int a2vx = colBounds.getOffset(c, hAlign, mWidth); 920 | int a2vy = rowBounds.getOffset(c, vAlign, mHeight); 921 | 922 | dx = c2ax + a2vx + leftMargin; 923 | dy = c2ay + a2vy + topMargin; 924 | 925 | cellWidth -= leftMargin + rightMargin; 926 | cellHeight -= topMargin + bottomMargin; 927 | 928 | int type = PRF; 929 | int width = hAlign.getSizeInCell(c, pWidth, cellWidth, type); 930 | int height = vAlign.getSizeInCell(c, pHeight, cellHeight, type); 931 | 932 | int cx = paddingLeft + x1 + dx; 933 | int cy = paddingTop + y1 + dy; 934 | if (width != c.getMeasuredWidth() || height != c.getMeasuredHeight()) { 935 | c.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); 936 | } 937 | c.layout(cx, cy, cx + width, cy + height); 938 | } 939 | } 940 | 941 | // Inner classes 942 | 943 | /* 944 | This internal class houses the algorithm for computing the locations of grid lines; 945 | along either the horizontal or vertical axis. A GridLayout uses two instances of this class - 946 | distinguished by the "horizontal" flag which is true for the horizontal axis and false 947 | for the vertical one. 948 | */ 949 | final class Axis { 950 | private static final int NEW = 0; 951 | private static final int PENDING = 1; 952 | private static final int COMPLETE = 2; 953 | 954 | public final boolean horizontal; 955 | 956 | public int definedCount = UNDEFINED; 957 | private int maxIndex = UNDEFINED; 958 | 959 | PackedMap groupBounds; 960 | public boolean groupBoundsValid = false; 961 | 962 | PackedMap forwardLinks; 963 | public boolean forwardLinksValid = false; 964 | 965 | PackedMap backwardLinks; 966 | public boolean backwardLinksValid = false; 967 | 968 | public int[] leadingMargins; 969 | public boolean leadingMarginsValid = false; 970 | 971 | public int[] trailingMargins; 972 | public boolean trailingMarginsValid = false; 973 | 974 | public Arc[] arcs; 975 | public boolean arcsValid = false; 976 | 977 | public int[] locations; 978 | public boolean locationsValid = false; 979 | 980 | boolean orderPreserved = DEFAULT_ORDER_PRESERVED; 981 | 982 | private MutableInt parentMin = new MutableInt(0); 983 | private MutableInt parentMax = new MutableInt(-MAX_SIZE); 984 | 985 | private Axis(boolean horizontal) { 986 | this.horizontal = horizontal; 987 | } 988 | 989 | private int calculateMaxIndex() { 990 | // the number Integer.MIN_VALUE + 1 comes up in undefined cells 991 | int result = -1; 992 | for (int i = 0, N = getChildCount(); i < N; i++) { 993 | View c = getChildAt(i); 994 | LayoutParams params = getLayoutParams(c); 995 | Spec spec = horizontal ? params.columnSpec : params.rowSpec; 996 | Interval span = spec.span; 997 | result = max(result, span.min); 998 | result = max(result, span.max); 999 | } 1000 | return result == -1 ? UNDEFINED : result; 1001 | } 1002 | 1003 | private int getMaxIndex() { 1004 | if (maxIndex == UNDEFINED) { 1005 | maxIndex = max(0, calculateMaxIndex()); // use zero when there are no children 1006 | } 1007 | return maxIndex; 1008 | } 1009 | 1010 | public int getCount() { 1011 | return max(definedCount, getMaxIndex()); 1012 | } 1013 | 1014 | public void setCount(int count) { 1015 | this.definedCount = count; 1016 | } 1017 | 1018 | public boolean isOrderPreserved() { 1019 | return orderPreserved; 1020 | } 1021 | 1022 | public void setOrderPreserved(boolean orderPreserved) { 1023 | this.orderPreserved = orderPreserved; 1024 | invalidateStructure(); 1025 | } 1026 | 1027 | private PackedMap createGroupBounds() { 1028 | Assoc assoc = Assoc.of(Spec.class, Bounds.class); 1029 | for (int i = 0, N = getChildCount(); i < N; i++) { 1030 | View c = getChildAt(i); 1031 | LayoutParams lp = getLayoutParams(c); 1032 | Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1033 | Bounds bounds = getAlignment(spec.alignment, horizontal).getBounds(); 1034 | assoc.put(spec, bounds); 1035 | } 1036 | return assoc.pack(); 1037 | } 1038 | 1039 | private void computeGroupBounds() { 1040 | Bounds[] values = groupBounds.values; 1041 | for (int i = 0; i < values.length; i++) { 1042 | values[i].reset(); 1043 | } 1044 | for (int i = 0, N = getChildCount(); i < N; i++) { 1045 | View c = getChildAt(i); 1046 | LayoutParams lp = getLayoutParams(c); 1047 | Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1048 | groupBounds.getValue(i).include(c, spec, GridLayout.this, this); 1049 | } 1050 | } 1051 | 1052 | public PackedMap getGroupBounds() { 1053 | if (groupBounds == null) { 1054 | groupBounds = createGroupBounds(); 1055 | } 1056 | if (!groupBoundsValid) { 1057 | computeGroupBounds(); 1058 | groupBoundsValid = true; 1059 | } 1060 | return groupBounds; 1061 | } 1062 | 1063 | // Add values computed by alignment - taking the max of all alignments in each span 1064 | private PackedMap createLinks(boolean min) { 1065 | Assoc result = Assoc.of(Interval.class, MutableInt.class); 1066 | Spec[] keys = getGroupBounds().keys; 1067 | for (int i = 0, N = keys.length; i < N; i++) { 1068 | Interval span = min ? keys[i].span : keys[i].span.inverse(); 1069 | result.put(span, new MutableInt()); 1070 | } 1071 | return result.pack(); 1072 | } 1073 | 1074 | private void computeLinks(PackedMap links, boolean min) { 1075 | MutableInt[] spans = links.values; 1076 | for (int i = 0; i < spans.length; i++) { 1077 | spans[i].reset(); 1078 | } 1079 | 1080 | // Use getter to trigger a re-evaluation 1081 | Bounds[] bounds = getGroupBounds().values; 1082 | for (int i = 0; i < bounds.length; i++) { 1083 | int size = bounds[i].size(min); 1084 | MutableInt valueHolder = links.getValue(i); 1085 | // this effectively takes the max() of the minima and the min() of the maxima 1086 | valueHolder.value = max(valueHolder.value, min ? size : -size); 1087 | } 1088 | } 1089 | 1090 | private PackedMap getForwardLinks() { 1091 | if (forwardLinks == null) { 1092 | forwardLinks = createLinks(true); 1093 | } 1094 | if (!forwardLinksValid) { 1095 | computeLinks(forwardLinks, true); 1096 | forwardLinksValid = true; 1097 | } 1098 | return forwardLinks; 1099 | } 1100 | 1101 | private PackedMap getBackwardLinks() { 1102 | if (backwardLinks == null) { 1103 | backwardLinks = createLinks(false); 1104 | } 1105 | if (!backwardLinksValid) { 1106 | computeLinks(backwardLinks, false); 1107 | backwardLinksValid = true; 1108 | } 1109 | return backwardLinks; 1110 | } 1111 | 1112 | private void include(List arcs, Interval key, MutableInt size, 1113 | boolean ignoreIfAlreadyPresent) { 1114 | /* 1115 | Remove self referential links. 1116 | These appear: 1117 | . as parental constraints when GridLayout has no children 1118 | . when components have been marked as GONE 1119 | */ 1120 | if (key.size() == 0) { 1121 | return; 1122 | } 1123 | // this bit below should really be computed outside here - 1124 | // its just to stop default (row/col > 0) constraints obliterating valid entries 1125 | if (ignoreIfAlreadyPresent) { 1126 | for (Arc arc : arcs) { 1127 | Interval span = arc.span; 1128 | if (span.equals(key)) { 1129 | return; 1130 | } 1131 | } 1132 | } 1133 | arcs.add(new Arc(key, size)); 1134 | } 1135 | 1136 | private void include(List arcs, Interval key, MutableInt size) { 1137 | include(arcs, key, size, true); 1138 | } 1139 | 1140 | // Group arcs by their first vertex, returning an array of arrays. 1141 | // This is linear in the number of arcs. 1142 | Arc[][] groupArcsByFirstVertex(Arc[] arcs) { 1143 | int N = getCount() + 1; // the number of vertices 1144 | Arc[][] result = new Arc[N][]; 1145 | int[] sizes = new int[N]; 1146 | for (Arc arc : arcs) { 1147 | sizes[arc.span.min]++; 1148 | } 1149 | for (int i = 0; i < sizes.length; i++) { 1150 | result[i] = new Arc[sizes[i]]; 1151 | } 1152 | // reuse the sizes array to hold the current last elements as we insert each arc 1153 | Arrays.fill(sizes, 0); 1154 | for (Arc arc : arcs) { 1155 | int i = arc.span.min; 1156 | result[i][sizes[i]++] = arc; 1157 | } 1158 | 1159 | return result; 1160 | } 1161 | 1162 | private Arc[] topologicalSort(final Arc[] arcs) { 1163 | return new Object() { 1164 | Arc[] result = new Arc[arcs.length]; 1165 | int cursor = result.length - 1; 1166 | Arc[][] arcsByVertex = groupArcsByFirstVertex(arcs); 1167 | int[] visited = new int[getCount() + 1]; 1168 | 1169 | void walk(int loc) { 1170 | switch (visited[loc]) { 1171 | case NEW: { 1172 | visited[loc] = PENDING; 1173 | for (Arc arc : arcsByVertex[loc]) { 1174 | walk(arc.span.max); 1175 | result[cursor--] = arc; 1176 | } 1177 | visited[loc] = COMPLETE; 1178 | break; 1179 | } 1180 | case PENDING: { 1181 | assert false; 1182 | break; 1183 | } 1184 | case COMPLETE: { 1185 | break; 1186 | } 1187 | } 1188 | } 1189 | 1190 | Arc[] sort() { 1191 | for (int loc = 0, N = arcsByVertex.length; loc < N; loc++) { 1192 | walk(loc); 1193 | } 1194 | assert cursor == -1; 1195 | return result; 1196 | } 1197 | }.sort(); 1198 | } 1199 | 1200 | private Arc[] topologicalSort(List arcs) { 1201 | return topologicalSort(arcs.toArray(new Arc[arcs.size()])); 1202 | } 1203 | 1204 | private void addComponentSizes(List result, PackedMap links) { 1205 | for (int i = 0; i < links.keys.length; i++) { 1206 | Interval key = links.keys[i]; 1207 | include(result, key, links.values[i], false); 1208 | } 1209 | } 1210 | 1211 | private Arc[] createArcs() { 1212 | List mins = new ArrayList(); 1213 | List maxs = new ArrayList(); 1214 | 1215 | // Add the minimum values from the components. 1216 | addComponentSizes(mins, getForwardLinks()); 1217 | // Add the maximum values from the components. 1218 | addComponentSizes(maxs, getBackwardLinks()); 1219 | 1220 | // Add ordering constraints to prevent row/col sizes from going negative 1221 | if (orderPreserved) { 1222 | // Add a constraint for every row/col 1223 | for (int i = 0; i < getCount(); i++) { 1224 | include(mins, new Interval(i, i + 1), new MutableInt(0)); 1225 | } 1226 | } 1227 | 1228 | // Add the container constraints. Use the version of include that allows 1229 | // duplicate entries in case a child spans the entire grid. 1230 | int N = getCount(); 1231 | include(mins, new Interval(0, N), parentMin, false); 1232 | include(maxs, new Interval(N, 0), parentMax, false); 1233 | 1234 | // Sort 1235 | Arc[] sMins = topologicalSort(mins); 1236 | Arc[] sMaxs = topologicalSort(maxs); 1237 | 1238 | return append(sMins, sMaxs); 1239 | } 1240 | 1241 | private void computeArcs() { 1242 | // getting the links validates the values that are shared by the arc list 1243 | getForwardLinks(); 1244 | getBackwardLinks(); 1245 | } 1246 | 1247 | public Arc[] getArcs() { 1248 | if (arcs == null) { 1249 | arcs = createArcs(); 1250 | } 1251 | if (!arcsValid) { 1252 | computeArcs(); 1253 | arcsValid = true; 1254 | } 1255 | return arcs; 1256 | } 1257 | 1258 | private boolean relax(int[] locations, Arc entry) { 1259 | if (!entry.valid) { 1260 | return false; 1261 | } 1262 | Interval span = entry.span; 1263 | int u = span.min; 1264 | int v = span.max; 1265 | int value = entry.value.value; 1266 | int candidate = locations[u] + value; 1267 | if (candidate > locations[v]) { 1268 | locations[v] = candidate; 1269 | return true; 1270 | } 1271 | return false; 1272 | } 1273 | 1274 | private void init(int[] locations) { 1275 | Arrays.fill(locations, 0); 1276 | } 1277 | 1278 | private String arcsToString(List arcs) { 1279 | String var = horizontal ? "x" : "y"; 1280 | StringBuilder result = new StringBuilder(); 1281 | boolean first = true; 1282 | for (Arc arc : arcs) { 1283 | if (first) { 1284 | first = false; 1285 | } else { 1286 | result = result.append(", "); 1287 | } 1288 | int src = arc.span.min; 1289 | int dst = arc.span.max; 1290 | int value = arc.value.value; 1291 | result.append((src < dst) ? 1292 | var + dst + " - " + var + src + " > " + value : 1293 | var + src + " - " + var + dst + " < " + -value); 1294 | 1295 | } 1296 | return result.toString(); 1297 | } 1298 | 1299 | private void logError(String axisName, Arc[] arcs, boolean[] culprits0) { 1300 | List culprits = new ArrayList(); 1301 | List removed = new ArrayList(); 1302 | for (int c = 0; c < arcs.length; c++) { 1303 | Arc arc = arcs[c]; 1304 | if (culprits0[c]) { 1305 | culprits.add(arc); 1306 | } 1307 | if (!arc.valid) { 1308 | removed.add(arc); 1309 | } 1310 | } 1311 | Log.d(TAG, axisName + " constraints: " + arcsToString(culprits) + " are inconsistent; " 1312 | + "permanently removing: " + arcsToString(removed) + ". "); 1313 | } 1314 | 1315 | /* 1316 | Bellman-Ford variant - modified to reduce typical running time from O(N^2) to O(N) 1317 | 1318 | GridLayout converts its requirements into a system of linear constraints of the 1319 | form: 1320 | 1321 | x[i] - x[j] < a[k] 1322 | 1323 | Where the x[i] are variables and the a[k] are constants. 1324 | 1325 | For example, if the variables were instead labeled x, y, z we might have: 1326 | 1327 | x - y < 17 1328 | y - z < 23 1329 | z - x < 42 1330 | 1331 | This is a special case of the Linear Programming problem that is, in turn, 1332 | equivalent to the single-source shortest paths problem on a digraph, for 1333 | which the O(n^2) Bellman-Ford algorithm the most commonly used general solution. 1334 | 1335 | Other algorithms are faster in the case where no arcs have negative weights 1336 | but allowing negative weights turns out to be the same as accommodating maximum 1337 | size requirements as well as minimum ones. 1338 | 1339 | Bellman-Ford works by iteratively 'relaxing' constraints over all nodes (an O(N) 1340 | process) and performing this step N times. Proof of correctness hinges on the 1341 | fact that there can be no negative weight chains of length > N - unless a 1342 | 'negative weight loop' exists. The algorithm catches this case in a final 1343 | checking phase that reports failure. 1344 | 1345 | By topologically sorting the nodes and checking this condition at each step 1346 | typical layout problems complete after the first iteration and the algorithm 1347 | completes in O(N) steps with very low constants. 1348 | */ 1349 | private void solve(Arc[] arcs, int[] locations) { 1350 | String axisName = horizontal ? "horizontal" : "vertical"; 1351 | int N = getCount() + 1; // The number of vertices is the number of columns/rows + 1. 1352 | boolean[] originalCulprits = null; 1353 | 1354 | for (int p = 0; p < arcs.length; p++) { 1355 | init(locations); 1356 | 1357 | // We take one extra pass over traditional Bellman-Ford (and omit their final step) 1358 | for (int i = 0; i < N; i++) { 1359 | boolean changed = false; 1360 | for (int j = 0, length = arcs.length; j < length; j++) { 1361 | changed |= relax(locations, arcs[j]); 1362 | } 1363 | if (!changed) { 1364 | if (originalCulprits != null) { 1365 | logError(axisName, arcs, originalCulprits); 1366 | } 1367 | return; 1368 | } 1369 | } 1370 | 1371 | boolean[] culprits = new boolean[arcs.length]; 1372 | for (int i = 0; i < N; i++) { 1373 | for (int j = 0, length = arcs.length; j < length; j++) { 1374 | culprits[j] |= relax(locations, arcs[j]); 1375 | } 1376 | } 1377 | 1378 | if (p == 0) { 1379 | originalCulprits = culprits; 1380 | } 1381 | 1382 | for (int i = 0; i < arcs.length; i++) { 1383 | if (culprits[i]) { 1384 | Arc arc = arcs[i]; 1385 | // Only remove max values, min values alone cannot be inconsistent 1386 | if (arc.span.min < arc.span.max) { 1387 | continue; 1388 | } 1389 | arc.valid = false; 1390 | break; 1391 | } 1392 | } 1393 | } 1394 | } 1395 | 1396 | private void computeMargins(boolean leading) { 1397 | int[] margins = leading ? leadingMargins : trailingMargins; 1398 | for (int i = 0, N = getChildCount(); i < N; i++) { 1399 | View c = getChildAt(i); 1400 | if (isGone(c)) continue; 1401 | LayoutParams lp = getLayoutParams(c); 1402 | Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; 1403 | Interval span = spec.span; 1404 | int index = leading ? span.min : span.max; 1405 | margins[index] = max(margins[index], getMargin1(c, horizontal, leading)); 1406 | } 1407 | } 1408 | 1409 | // External entry points 1410 | 1411 | public int[] getLeadingMargins() { 1412 | if (leadingMargins == null) { 1413 | leadingMargins = new int[getCount() + 1]; 1414 | } 1415 | if (!leadingMarginsValid) { 1416 | computeMargins(true); 1417 | leadingMarginsValid = true; 1418 | } 1419 | return leadingMargins; 1420 | } 1421 | 1422 | public int[] getTrailingMargins() { 1423 | if (trailingMargins == null) { 1424 | trailingMargins = new int[getCount() + 1]; 1425 | } 1426 | if (!trailingMarginsValid) { 1427 | computeMargins(false); 1428 | trailingMarginsValid = true; 1429 | } 1430 | return trailingMargins; 1431 | } 1432 | 1433 | private void computeLocations(int[] a) { 1434 | solve(getArcs(), a); 1435 | if (!orderPreserved) { 1436 | // Solve returns the smallest solution to the constraint system for which all 1437 | // values are positive. One value is therefore zero - though if the row/col 1438 | // order is not preserved this may not be the first vertex. For consistency, 1439 | // translate all the values so that they measure the distance from a[0]; the 1440 | // leading edge of the parent. After this transformation some values may be 1441 | // negative. 1442 | int a0 = a[0]; 1443 | for (int i = 0, N = a.length; i < N; i++) { 1444 | a[i] = a[i] - a0; 1445 | } 1446 | } 1447 | } 1448 | 1449 | public int[] getLocations() { 1450 | if (locations == null) { 1451 | int N = getCount() + 1; 1452 | locations = new int[N]; 1453 | } 1454 | if (!locationsValid) { 1455 | computeLocations(locations); 1456 | locationsValid = true; 1457 | } 1458 | return locations; 1459 | } 1460 | 1461 | private int size(int[] locations) { 1462 | // The parental edges are attached to vertices 0 and N - even when order is not 1463 | // being preserved and other vertices fall outside this range. Measure the distance 1464 | // between vertices 0 and N, assuming that locations[0] = 0. 1465 | return locations[getCount()]; 1466 | } 1467 | 1468 | private void setParentConstraints(int min, int max) { 1469 | parentMin.value = min; 1470 | parentMax.value = -max; 1471 | locationsValid = false; 1472 | } 1473 | 1474 | private int getMeasure(int min, int max) { 1475 | setParentConstraints(min, max); 1476 | return size(getLocations()); 1477 | } 1478 | 1479 | public int getMeasure(int measureSpec) { 1480 | int mode = MeasureSpec.getMode(measureSpec); 1481 | int size = MeasureSpec.getSize(measureSpec); 1482 | switch (mode) { 1483 | case MeasureSpec.UNSPECIFIED: { 1484 | return getMeasure(0, MAX_SIZE); 1485 | } 1486 | case MeasureSpec.EXACTLY: { 1487 | return getMeasure(size, size); 1488 | } 1489 | case MeasureSpec.AT_MOST: { 1490 | return getMeasure(0, size); 1491 | } 1492 | default: { 1493 | assert false; 1494 | return 0; 1495 | } 1496 | } 1497 | } 1498 | 1499 | public void layout(int size) { 1500 | setParentConstraints(size, size); 1501 | getLocations(); 1502 | } 1503 | 1504 | public void invalidateStructure() { 1505 | maxIndex = UNDEFINED; 1506 | 1507 | groupBounds = null; 1508 | forwardLinks = null; 1509 | backwardLinks = null; 1510 | 1511 | leadingMargins = null; 1512 | trailingMargins = null; 1513 | arcs = null; 1514 | 1515 | locations = null; 1516 | 1517 | invalidateValues(); 1518 | } 1519 | 1520 | public void invalidateValues() { 1521 | groupBoundsValid = false; 1522 | forwardLinksValid = false; 1523 | backwardLinksValid = false; 1524 | 1525 | leadingMarginsValid = false; 1526 | trailingMarginsValid = false; 1527 | arcsValid = false; 1528 | 1529 | locationsValid = false; 1530 | } 1531 | } 1532 | 1533 | /** 1534 | * Layout information associated with each of the children of a GridLayout. 1535 | *

1536 | * GridLayout supports both row and column spanning and arbitrary forms of alignment within 1537 | * each cell group. The fundamental parameters associated with each cell group are 1538 | * gathered into their vertical and horizontal components and stored 1539 | * in the {@link #rowSpec} and {@link #columnSpec} layout parameters. 1540 | * {@link android.widget.GridLayout.Spec Specs} are immutable structures 1541 | * and may be shared between the layout parameters of different children. 1542 | *

1543 | * The row and column specs contain the leading and trailing indices along each axis 1544 | * and together specify the four grid indices that delimit the cells of this cell group. 1545 | *

1546 | * The alignment properties of the row and column specs together specify 1547 | * both aspects of alignment within the cell group. It is also possible to specify a child's 1548 | * alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)} 1549 | * method. 1550 | * 1551 | *

WRAP_CONTENT and MATCH_PARENT

1552 | * 1553 | * Because the default values of the {@link #width} and {@link #height} 1554 | * properties are both {@link #WRAP_CONTENT}, this value never needs to be explicitly 1555 | * declared in the layout parameters of GridLayout's children. In addition, 1556 | * GridLayout does not distinguish the special size value {@link #MATCH_PARENT} from 1557 | * {@link #WRAP_CONTENT}. A component's ability to expand to the size of the parent is 1558 | * instead controlled by the principle of flexibility, 1559 | * as discussed in {@link GridLayout}. 1560 | * 1561 | *

Summary

1562 | * 1563 | * You should not need to use either of the special size values: 1564 | * {@code WRAP_CONTENT} or {@code MATCH_PARENT} when configuring the children of 1565 | * a GridLayout. 1566 | * 1567 | *

Default values

1568 | * 1569 | *
    1570 | *
  • {@link #width} = {@link #WRAP_CONTENT}
  • 1571 | *
  • {@link #height} = {@link #WRAP_CONTENT}
  • 1572 | *
  • {@link #topMargin} = 0 when 1573 | * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1574 | * {@code false}; otherwise {@link #UNDEFINED}, to 1575 | * indicate that a default value should be computed on demand.
  • 1576 | *
  • {@link #leftMargin} = 0 when 1577 | * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1578 | * {@code false}; otherwise {@link #UNDEFINED}, to 1579 | * indicate that a default value should be computed on demand.
  • 1580 | *
  • {@link #bottomMargin} = 0 when 1581 | * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1582 | * {@code false}; otherwise {@link #UNDEFINED}, to 1583 | * indicate that a default value should be computed on demand.
  • 1584 | *
  • {@link #rightMargin} = 0 when 1585 | * {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins} is 1586 | * {@code false}; otherwise {@link #UNDEFINED}, to 1587 | * indicate that a default value should be computed on demand.
  • 1588 | *
  • {@link #rowSpec}.row = {@link #UNDEFINED}
  • 1589 | *
  • {@link #rowSpec}.rowSpan = 1
  • 1590 | *
  • {@link #rowSpec}.alignment = {@link #BASELINE}
  • 1591 | *
  • {@link #columnSpec}.column = {@link #UNDEFINED}
  • 1592 | *
  • {@link #columnSpec}.columnSpan = 1
  • 1593 | *
  • {@link #columnSpec}.alignment = {@link #LEFT}
  • 1594 | *
1595 | * 1596 | * See {@link GridLayout} for a more complete description of the conventions 1597 | * used by GridLayout in the interpretation of the properties of this class. 1598 | * 1599 | * @attr ref android.R.styleable#GridLayout_Layout_layout_row 1600 | * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan 1601 | * @attr ref android.R.styleable#GridLayout_Layout_layout_column 1602 | * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan 1603 | * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1604 | */ 1605 | public static class LayoutParams extends MarginLayoutParams { 1606 | 1607 | // Default values 1608 | 1609 | private static final int DEFAULT_WIDTH = WRAP_CONTENT; 1610 | private static final int DEFAULT_HEIGHT = WRAP_CONTENT; 1611 | private static final int DEFAULT_MARGIN = UNDEFINED; 1612 | private static final int DEFAULT_ROW = UNDEFINED; 1613 | private static final int DEFAULT_COLUMN = UNDEFINED; 1614 | private static final Interval DEFAULT_SPAN = new Interval(UNDEFINED, UNDEFINED + 1); 1615 | private static final int DEFAULT_SPAN_SIZE = DEFAULT_SPAN.size(); 1616 | 1617 | // TypedArray indices 1618 | 1619 | private static final int MARGIN = R.styleable.ViewGroup_MarginLayout_android_layout_margin; 1620 | private static final int LEFT_MARGIN = R.styleable.ViewGroup_MarginLayout_android_layout_marginLeft; 1621 | private static final int TOP_MARGIN = R.styleable.ViewGroup_MarginLayout_android_layout_marginTop; 1622 | private static final int RIGHT_MARGIN = 1623 | R.styleable.ViewGroup_MarginLayout_android_layout_marginRight; 1624 | private static final int BOTTOM_MARGIN = 1625 | R.styleable.ViewGroup_MarginLayout_android_layout_marginBottom; 1626 | 1627 | private static final int COLUMN = R.styleable.GridLayout_Layout_android_layout_column; 1628 | private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan; 1629 | 1630 | private static final int ROW = R.styleable.GridLayout_Layout_layout_row; 1631 | private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan; 1632 | 1633 | private static final int GRAVITY = R.styleable.GridLayout_Layout_android_layout_gravity; 1634 | 1635 | // Instance variables 1636 | 1637 | /** 1638 | * The spec that defines the vertical characteristics of the cell group 1639 | * described by these layout parameters. 1640 | */ 1641 | public Spec rowSpec = Spec.UNDEFINED; 1642 | 1643 | /** 1644 | * The spec that defines the horizontal characteristics of the cell group 1645 | * described by these layout parameters. 1646 | */ 1647 | public Spec columnSpec = Spec.UNDEFINED; 1648 | 1649 | // Constructors 1650 | 1651 | private LayoutParams( 1652 | int width, int height, 1653 | int left, int top, int right, int bottom, 1654 | Spec rowSpec, Spec columnSpec) { 1655 | super(width, height); 1656 | setMargins(left, top, right, bottom); 1657 | this.rowSpec = rowSpec; 1658 | this.columnSpec = columnSpec; 1659 | } 1660 | 1661 | /** 1662 | * Constructs a new LayoutParams instance for this rowSpec 1663 | * and columnSpec. All other fields are initialized with 1664 | * default values as defined in {@link LayoutParams}. 1665 | * 1666 | * @param rowSpec the rowSpec 1667 | * @param columnSpec the columnSpec 1668 | */ 1669 | public LayoutParams(Spec rowSpec, Spec columnSpec) { 1670 | this(DEFAULT_WIDTH, DEFAULT_HEIGHT, 1671 | DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, 1672 | rowSpec, columnSpec); 1673 | } 1674 | 1675 | /** 1676 | * Constructs a new LayoutParams with default values as defined in {@link LayoutParams}. 1677 | */ 1678 | public LayoutParams() { 1679 | this(Spec.UNDEFINED, Spec.UNDEFINED); 1680 | } 1681 | 1682 | // Copying constructors 1683 | 1684 | /** 1685 | * {@inheritDoc} 1686 | */ 1687 | public LayoutParams(ViewGroup.LayoutParams params) { 1688 | super(params); 1689 | } 1690 | 1691 | /** 1692 | * {@inheritDoc} 1693 | */ 1694 | public LayoutParams(MarginLayoutParams params) { 1695 | super(params); 1696 | } 1697 | 1698 | /** 1699 | * {@inheritDoc} 1700 | */ 1701 | public LayoutParams(LayoutParams that) { 1702 | super(that); 1703 | this.rowSpec = that.rowSpec; 1704 | this.columnSpec = that.columnSpec; 1705 | } 1706 | 1707 | // AttributeSet constructors 1708 | 1709 | /** 1710 | * {@inheritDoc} 1711 | * 1712 | * Values not defined in the attribute set take the default values 1713 | * defined in {@link LayoutParams}. 1714 | */ 1715 | public LayoutParams(Context context, AttributeSet attrs) { 1716 | super(context, attrs); 1717 | reInitSuper(context, attrs); 1718 | init(context, attrs); 1719 | } 1720 | 1721 | // Implementation 1722 | 1723 | // Reinitialise the margins using a different default policy than MarginLayoutParams. 1724 | // Here we use the value UNDEFINED (as distinct from zero) to represent the undefined state 1725 | // so that a layout manager default can be accessed post set up. We need this as, at the 1726 | // point of installation, we do not know how many rows/cols there are and therefore 1727 | // which elements are positioned next to the container's trailing edges. We need to 1728 | // know this as margins around the container's boundary should have different 1729 | // defaults to those between peers. 1730 | 1731 | // This method could be parametrized and moved into MarginLayout. 1732 | private void reInitSuper(Context context, AttributeSet attrs) { 1733 | TypedArray a = 1734 | context.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); 1735 | try { 1736 | int margin = a.getDimensionPixelSize(MARGIN, DEFAULT_MARGIN); 1737 | 1738 | this.leftMargin = a.getDimensionPixelSize(LEFT_MARGIN, margin); 1739 | this.topMargin = a.getDimensionPixelSize(TOP_MARGIN, margin); 1740 | this.rightMargin = a.getDimensionPixelSize(RIGHT_MARGIN, margin); 1741 | this.bottomMargin = a.getDimensionPixelSize(BOTTOM_MARGIN, margin); 1742 | } finally { 1743 | a.recycle(); 1744 | } 1745 | } 1746 | 1747 | private void init(Context context, AttributeSet attrs) { 1748 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GridLayout_Layout); 1749 | try { 1750 | int gravity = a.getInt(GRAVITY, Gravity.NO_GRAVITY); 1751 | 1752 | int column = a.getInt(COLUMN, DEFAULT_COLUMN); 1753 | int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE); 1754 | this.columnSpec = spec(column, colSpan, getAlignment(gravity, true)); 1755 | 1756 | int row = a.getInt(ROW, DEFAULT_ROW); 1757 | int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE); 1758 | this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false)); 1759 | } finally { 1760 | a.recycle(); 1761 | } 1762 | } 1763 | 1764 | /** 1765 | * Describes how the child views are positioned. Default is {@code LEFT | BASELINE}. 1766 | * See {@link android.view.Gravity}. 1767 | * 1768 | * @param gravity the new gravity value 1769 | * 1770 | * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity 1771 | */ 1772 | public void setGravity(int gravity) { 1773 | rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false)); 1774 | columnSpec = columnSpec.copyWriteAlignment(getAlignment(gravity, true)); 1775 | } 1776 | 1777 | @Override 1778 | protected void setBaseAttributes(TypedArray attributes, int widthAttr, int heightAttr) { 1779 | this.width = attributes.getLayoutDimension(widthAttr, DEFAULT_WIDTH); 1780 | this.height = attributes.getLayoutDimension(heightAttr, DEFAULT_HEIGHT); 1781 | } 1782 | 1783 | final void setRowSpecSpan(Interval span) { 1784 | rowSpec = rowSpec.copyWriteSpan(span); 1785 | } 1786 | 1787 | final void setColumnSpecSpan(Interval span) { 1788 | columnSpec = columnSpec.copyWriteSpan(span); 1789 | } 1790 | } 1791 | 1792 | /* 1793 | In place of a HashMap from span to Int, use an array of key/value pairs - stored in Arcs. 1794 | Add the mutables completesCycle flag to avoid creating another hash table for detecting cycles. 1795 | */ 1796 | final static class Arc { 1797 | public final Interval span; 1798 | public final MutableInt value; 1799 | public boolean valid = true; 1800 | 1801 | public Arc(Interval span, MutableInt value) { 1802 | this.span = span; 1803 | this.value = value; 1804 | } 1805 | 1806 | @Override 1807 | public String toString() { 1808 | return span + " " + (!valid ? "+>" : "->") + " " + value; 1809 | } 1810 | } 1811 | 1812 | // A mutable Integer - used to avoid heap allocation during the layout operation 1813 | 1814 | final static class MutableInt { 1815 | public int value; 1816 | 1817 | public MutableInt() { 1818 | reset(); 1819 | } 1820 | 1821 | public MutableInt(int value) { 1822 | this.value = value; 1823 | } 1824 | 1825 | public void reset() { 1826 | value = Integer.MIN_VALUE; 1827 | } 1828 | 1829 | @Override 1830 | public String toString() { 1831 | return Integer.toString(value); 1832 | } 1833 | } 1834 | 1835 | final static class Assoc extends ArrayList> { 1836 | private final Class keyType; 1837 | private final Class valueType; 1838 | 1839 | private Assoc(Class keyType, Class valueType) { 1840 | this.keyType = keyType; 1841 | this.valueType = valueType; 1842 | } 1843 | 1844 | public static Assoc of(Class keyType, Class valueType) { 1845 | return new Assoc(keyType, valueType); 1846 | } 1847 | 1848 | public void put(K key, V value) { 1849 | add(Pair.create(key, value)); 1850 | } 1851 | 1852 | @SuppressWarnings(value = "unchecked") 1853 | public PackedMap pack() { 1854 | int N = size(); 1855 | K[] keys = (K[]) Array.newInstance(keyType, N); 1856 | V[] values = (V[]) Array.newInstance(valueType, N); 1857 | for (int i = 0; i < N; i++) { 1858 | keys[i] = get(i).first; 1859 | values[i] = get(i).second; 1860 | } 1861 | return new PackedMap(keys, values); 1862 | } 1863 | } 1864 | 1865 | /* 1866 | This data structure is used in place of a Map where we have an index that refers to the order 1867 | in which each key/value pairs were added to the map. In this case we store keys and values 1868 | in arrays of a length that is equal to the number of unique keys. We also maintain an 1869 | array of indexes from insertion order to the compacted arrays of keys and values. 1870 | 1871 | Note that behavior differs from that of a LinkedHashMap in that repeated entries 1872 | *do* get added multiples times. So the length of index is equals to the number of 1873 | items added. 1874 | 1875 | This is useful in the GridLayout class where we can rely on the order of children not 1876 | changing during layout - to use integer-based lookup for our internal structures 1877 | rather than using (and storing) an implementation of Map. 1878 | */ 1879 | @SuppressWarnings(value = "unchecked") 1880 | final static class PackedMap { 1881 | public final int[] index; 1882 | public final K[] keys; 1883 | public final V[] values; 1884 | 1885 | private PackedMap(K[] keys, V[] values) { 1886 | this.index = createIndex(keys); 1887 | 1888 | this.keys = compact(keys, index); 1889 | this.values = compact(values, index); 1890 | } 1891 | 1892 | public V getValue(int i) { 1893 | return values[index[i]]; 1894 | } 1895 | 1896 | private static int[] createIndex(K[] keys) { 1897 | int size = keys.length; 1898 | int[] result = new int[size]; 1899 | 1900 | Map keyToIndex = new HashMap(); 1901 | for (int i = 0; i < size; i++) { 1902 | K key = keys[i]; 1903 | Integer index = keyToIndex.get(key); 1904 | if (index == null) { 1905 | index = keyToIndex.size(); 1906 | keyToIndex.put(key, index); 1907 | } 1908 | result[i] = index; 1909 | } 1910 | return result; 1911 | } 1912 | 1913 | /* 1914 | Create a compact array of keys or values using the supplied index. 1915 | */ 1916 | private static K[] compact(K[] a, int[] index) { 1917 | int size = a.length; 1918 | Class componentType = a.getClass().getComponentType(); 1919 | K[] result = (K[]) Array.newInstance(componentType, max2(index, -1) + 1); 1920 | 1921 | // this overwrite duplicates, retaining the last equivalent entry 1922 | for (int i = 0; i < size; i++) { 1923 | result[index[i]] = a[i]; 1924 | } 1925 | return result; 1926 | } 1927 | } 1928 | 1929 | /* 1930 | For each group (with a given alignment) we need to store the amount of space required 1931 | before the alignment point and the amount of space required after it. One side of this 1932 | calculation is always 0 for LEADING and TRAILING alignments but we don't make use of this. 1933 | For CENTER and BASELINE alignments both sides are needed and in the BASELINE case no 1934 | simple optimisations are possible. 1935 | 1936 | The general algorithm therefore is to create a Map (actually a PackedMap) from 1937 | group to Bounds and to loop through all Views in the group taking the maximum 1938 | of the values for each View. 1939 | */ 1940 | static class Bounds { 1941 | public int before; 1942 | public int after; 1943 | public int flexibility; // we're flexible iff all included specs are flexible 1944 | 1945 | private Bounds() { 1946 | reset(); 1947 | } 1948 | 1949 | protected void reset() { 1950 | before = Integer.MIN_VALUE; 1951 | after = Integer.MIN_VALUE; 1952 | flexibility = CAN_STRETCH; // from the above, we're flexible when empty 1953 | } 1954 | 1955 | protected void include(int before, int after) { 1956 | this.before = max(this.before, before); 1957 | this.after = max(this.after, after); 1958 | } 1959 | 1960 | protected int size(boolean min) { 1961 | if (!min) { 1962 | if (canStretch(flexibility)) { 1963 | return MAX_SIZE; 1964 | } 1965 | } 1966 | return before + after; 1967 | } 1968 | 1969 | protected int getOffset(View c, Alignment alignment, int size) { 1970 | return before - alignment.getAlignmentValue(c, size); 1971 | } 1972 | 1973 | protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) { 1974 | this.flexibility &= spec.getFlexibility(); 1975 | int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal); 1976 | Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal); 1977 | // todo test this works correctly when the returned value is UNDEFINED 1978 | int before = alignment.getAlignmentValue(c, size); 1979 | include(before, size - before); 1980 | } 1981 | 1982 | @Override 1983 | public String toString() { 1984 | return "Bounds{" + 1985 | "before=" + before + 1986 | ", after=" + after + 1987 | '}'; 1988 | } 1989 | } 1990 | 1991 | /** 1992 | * An Interval represents a contiguous range of values that lie between 1993 | * the interval's {@link #min} and {@link #max} values. 1994 | *

1995 | * Intervals are immutable so may be passed as values and used as keys in hash tables. 1996 | * It is not necessary to have multiple instances of Intervals which have the same 1997 | * {@link #min} and {@link #max} values. 1998 | *

1999 | * Intervals are often written as {@code [min, max]} and represent the set of values 2000 | * {@code x} such that {@code min <= x < max}. 2001 | */ 2002 | final static class Interval { 2003 | /** 2004 | * The minimum value. 2005 | */ 2006 | public final int min; 2007 | 2008 | /** 2009 | * The maximum value. 2010 | */ 2011 | public final int max; 2012 | 2013 | /** 2014 | * Construct a new Interval, {@code interval}, where: 2015 | *

    2016 | *
  • {@code interval.min = min}
  • 2017 | *
  • {@code interval.max = max}
  • 2018 | *
2019 | * 2020 | * @param min the minimum value. 2021 | * @param max the maximum value. 2022 | */ 2023 | public Interval(int min, int max) { 2024 | this.min = min; 2025 | this.max = max; 2026 | } 2027 | 2028 | int size() { 2029 | return max - min; 2030 | } 2031 | 2032 | Interval inverse() { 2033 | return new Interval(max, min); 2034 | } 2035 | 2036 | /** 2037 | * Returns {@code true} if the {@link #getClass class}, 2038 | * {@link #min} and {@link #max} properties of this Interval and the 2039 | * supplied parameter are pairwise equal; {@code false} otherwise. 2040 | * 2041 | * @param that the object to compare this interval with 2042 | * 2043 | * @return {@code true} if the specified object is equal to this 2044 | * {@code Interval}, {@code false} otherwise. 2045 | */ 2046 | @Override 2047 | public boolean equals(Object that) { 2048 | if (this == that) { 2049 | return true; 2050 | } 2051 | if (that == null || getClass() != that.getClass()) { 2052 | return false; 2053 | } 2054 | 2055 | Interval interval = (Interval) that; 2056 | 2057 | if (max != interval.max) { 2058 | return false; 2059 | } 2060 | //noinspection RedundantIfStatement 2061 | if (min != interval.min) { 2062 | return false; 2063 | } 2064 | 2065 | return true; 2066 | } 2067 | 2068 | @Override 2069 | public int hashCode() { 2070 | int result = min; 2071 | result = 31 * result + max; 2072 | return result; 2073 | } 2074 | 2075 | @Override 2076 | public String toString() { 2077 | return "[" + min + ", " + max + "]"; 2078 | } 2079 | } 2080 | 2081 | /** 2082 | * A Spec defines the horizontal or vertical characteristics of a group of 2083 | * cells. Each spec. defines the grid indices and alignment 2084 | * along the appropriate axis. 2085 | *

2086 | * The grid indices are the leading and trailing edges of this cell group. 2087 | * See {@link GridLayout} for a description of the conventions used by GridLayout 2088 | * for grid indices. 2089 | *

2090 | * The alignment property specifies how cells should be aligned in this group. 2091 | * For row groups, this specifies the vertical alignment. 2092 | * For column groups, this specifies the horizontal alignment. 2093 | *

2094 | * Use the following static methods to create specs: 2095 | *

    2096 | *
  • {@link #spec(int)}
  • 2097 | *
  • {@link #spec(int, int)}
  • 2098 | *
  • {@link #spec(int, Alignment)}
  • 2099 | *
  • {@link #spec(int, int, Alignment)}
  • 2100 | *
2101 | * 2102 | */ 2103 | public static class Spec { 2104 | static final Spec UNDEFINED = spec(GridLayout.UNDEFINED); 2105 | 2106 | final boolean startDefined; 2107 | final Interval span; 2108 | final Alignment alignment; 2109 | 2110 | private Spec(boolean startDefined, Interval span, Alignment alignment) { 2111 | this.startDefined = startDefined; 2112 | this.span = span; 2113 | this.alignment = alignment; 2114 | } 2115 | 2116 | private Spec(boolean startDefined, int start, int size, Alignment alignment) { 2117 | this(startDefined, new Interval(start, start + size), alignment); 2118 | } 2119 | 2120 | final Spec copyWriteSpan(Interval span) { 2121 | return new Spec(startDefined, span, alignment); 2122 | } 2123 | 2124 | final Spec copyWriteAlignment(Alignment alignment) { 2125 | return new Spec(startDefined, span, alignment); 2126 | } 2127 | 2128 | final int getFlexibility() { 2129 | return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH; 2130 | } 2131 | 2132 | /** 2133 | * Returns {@code true} if the {@code class}, {@code alignment} and {@code span} 2134 | * properties of this Spec and the supplied parameter are pairwise equal, 2135 | * {@code false} otherwise. 2136 | * 2137 | * @param that the object to compare this spec with 2138 | * 2139 | * @return {@code true} if the specified object is equal to this 2140 | * {@code Spec}; {@code false} otherwise 2141 | */ 2142 | @Override 2143 | public boolean equals(Object that) { 2144 | if (this == that) { 2145 | return true; 2146 | } 2147 | if (that == null || getClass() != that.getClass()) { 2148 | return false; 2149 | } 2150 | 2151 | Spec spec = (Spec) that; 2152 | 2153 | if (!alignment.equals(spec.alignment)) { 2154 | return false; 2155 | } 2156 | //noinspection RedundantIfStatement 2157 | if (!span.equals(spec.span)) { 2158 | return false; 2159 | } 2160 | 2161 | return true; 2162 | } 2163 | 2164 | @Override 2165 | public int hashCode() { 2166 | int result = span.hashCode(); 2167 | result = 31 * result + alignment.hashCode(); 2168 | return result; 2169 | } 2170 | } 2171 | 2172 | /** 2173 | * Return a Spec, {@code spec}, where: 2174 | *
    2175 | *
  • {@code spec.span = [start, start + size]}
  • 2176 | *
  • {@code spec.alignment = alignment}
  • 2177 | *
2178 | * 2179 | * @param start the start 2180 | * @param size the size 2181 | * @param alignment the alignment 2182 | */ 2183 | public static Spec spec(int start, int size, Alignment alignment) { 2184 | return new Spec(start != UNDEFINED, start, size, alignment); 2185 | } 2186 | 2187 | /** 2188 | * Return a Spec, {@code spec}, where: 2189 | *
    2190 | *
  • {@code spec.span = [start, start + 1]}
  • 2191 | *
  • {@code spec.alignment = alignment}
  • 2192 | *
2193 | * 2194 | * @param start the start index 2195 | * @param alignment the alignment 2196 | */ 2197 | public static Spec spec(int start, Alignment alignment) { 2198 | return spec(start, 1, alignment); 2199 | } 2200 | 2201 | /** 2202 | * Return a Spec, {@code spec}, where: 2203 | *
    2204 | *
  • {@code spec.span = [start, start + size]}
  • 2205 | *
2206 | * 2207 | * @param start the start 2208 | * @param size the size 2209 | */ 2210 | public static Spec spec(int start, int size) { 2211 | return spec(start, size, UNDEFINED_ALIGNMENT); 2212 | } 2213 | 2214 | /** 2215 | * Return a Spec, {@code spec}, where: 2216 | *
    2217 | *
  • {@code spec.span = [start, start + 1]}
  • 2218 | *
2219 | * 2220 | * @param start the start index 2221 | */ 2222 | public static Spec spec(int start) { 2223 | return spec(start, 1); 2224 | } 2225 | 2226 | /** 2227 | * Alignments specify where a view should be placed within a cell group and 2228 | * what size it should be. 2229 | *

2230 | * The {@link LayoutParams} class contains a {@link LayoutParams#rowSpec rowSpec} 2231 | * and a {@link LayoutParams#columnSpec columnSpec} each of which contains an 2232 | * {@code alignment}. Overall placement of the view in the cell 2233 | * group is specified by the two alignments which act along each axis independently. 2234 | *

2235 | * The GridLayout class defines the most common alignments used in general layout: 2236 | * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link 2237 | * #BASELINE} and {@link #FILL}. 2238 | */ 2239 | /* 2240 | * An Alignment implementation must define {@link #getAlignmentValue(View, int, int)}, 2241 | * to return the appropriate value for the type of alignment being defined. 2242 | * The enclosing algorithms position the children 2243 | * so that the locations defined by the alignment values 2244 | * are the same for all of the views in a group. 2245 | *

2246 | */ 2247 | public static abstract class Alignment { 2248 | Alignment() { 2249 | } 2250 | 2251 | /** 2252 | * Returns an alignment value. In the case of vertical alignments the value 2253 | * returned should indicate the distance from the top of the view to the 2254 | * alignment location. 2255 | * For horizontal alignments measurement is made from the left edge of the component. 2256 | * 2257 | * @param view the view to which this alignment should be applied 2258 | * @param viewSize the measured size of the view 2259 | * @return the alignment value 2260 | */ 2261 | abstract int getAlignmentValue(View view, int viewSize); 2262 | 2263 | /** 2264 | * Returns the size of the view specified by this alignment. 2265 | * In the case of vertical alignments this method should return a height; for 2266 | * horizontal alignments this method should return the width. 2267 | *

2268 | * The default implementation returns {@code viewSize}. 2269 | * 2270 | * @param view the view to which this alignment should be applied 2271 | * @param viewSize the measured size of the view 2272 | * @param cellSize the size of the cell into which this view will be placed 2273 | * @param measurementType This parameter is currently unused as GridLayout only supports 2274 | * one type of measurement: {@link View#measure(int, int)}. 2275 | * 2276 | * @return the aligned size 2277 | */ 2278 | int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { 2279 | return viewSize; 2280 | } 2281 | 2282 | Bounds getBounds() { 2283 | return new Bounds(); 2284 | } 2285 | } 2286 | 2287 | static final Alignment UNDEFINED_ALIGNMENT = new Alignment() { 2288 | public int getAlignmentValue(View view, int viewSize) { 2289 | return UNDEFINED; 2290 | } 2291 | }; 2292 | 2293 | private static final Alignment LEADING = new Alignment() { 2294 | public int getAlignmentValue(View view, int viewSize) { 2295 | return 0; 2296 | } 2297 | }; 2298 | 2299 | private static final Alignment TRAILING = new Alignment() { 2300 | public int getAlignmentValue(View view, int viewSize) { 2301 | return viewSize; 2302 | } 2303 | }; 2304 | 2305 | /** 2306 | * Indicates that a view should be aligned with the top 2307 | * edges of the other views in its cell group. 2308 | */ 2309 | public static final Alignment TOP = LEADING; 2310 | 2311 | /** 2312 | * Indicates that a view should be aligned with the bottom 2313 | * edges of the other views in its cell group. 2314 | */ 2315 | public static final Alignment BOTTOM = TRAILING; 2316 | 2317 | /** 2318 | * Indicates that a view should be aligned with the right 2319 | * edges of the other views in its cell group. 2320 | */ 2321 | public static final Alignment RIGHT = TRAILING; 2322 | 2323 | /** 2324 | * Indicates that a view should be aligned with the left 2325 | * edges of the other views in its cell group. 2326 | */ 2327 | public static final Alignment LEFT = LEADING; 2328 | 2329 | /** 2330 | * Indicates that a view should be centered with the other views in its cell group. 2331 | * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and {@link 2332 | * LayoutParams#columnSpec columnSpecs}. 2333 | */ 2334 | public static final Alignment CENTER = new Alignment() { 2335 | public int getAlignmentValue(View view, int viewSize) { 2336 | return viewSize >> 1; 2337 | } 2338 | }; 2339 | 2340 | /** 2341 | * Indicates that a view should be aligned with the baselines 2342 | * of the other views in its cell group. 2343 | * This constant may only be used as an alignment in {@link LayoutParams#rowSpec rowSpecs}. 2344 | * 2345 | * @see View#getBaseline() 2346 | */ 2347 | public static final Alignment BASELINE = new Alignment() { 2348 | public int getAlignmentValue(View view, int viewSize) { 2349 | if (view == null) { 2350 | return UNDEFINED; 2351 | } 2352 | int baseline = view.getBaseline(); 2353 | return (baseline == -1) ? UNDEFINED : baseline; 2354 | } 2355 | 2356 | @Override 2357 | public Bounds getBounds() { 2358 | return new Bounds() { 2359 | /* 2360 | In a baseline aligned row in which some components define a baseline 2361 | and some don't, we need a third variable to properly account for all 2362 | the sizes. This tracks the maximum size of all the components - 2363 | including those that don't define a baseline. 2364 | */ 2365 | private int size; 2366 | 2367 | @Override 2368 | protected void reset() { 2369 | super.reset(); 2370 | size = Integer.MIN_VALUE; 2371 | } 2372 | 2373 | @Override 2374 | protected void include(int before, int after) { 2375 | super.include(before, after); 2376 | size = max(size, before + after); 2377 | } 2378 | 2379 | @Override 2380 | protected int size(boolean min) { 2381 | return max(super.size(min), size); 2382 | } 2383 | 2384 | @Override 2385 | protected int getOffset(View c, Alignment alignment, int size) { 2386 | return max(0, super.getOffset(c, alignment, size)); 2387 | } 2388 | }; 2389 | } 2390 | }; 2391 | 2392 | /** 2393 | * Indicates that a view should expanded to fit the boundaries of its cell group. 2394 | * This constant may be used in both {@link LayoutParams#rowSpec rowSpecs} and 2395 | * {@link LayoutParams#columnSpec columnSpecs}. 2396 | */ 2397 | public static final Alignment FILL = new Alignment() { 2398 | public int getAlignmentValue(View view, int viewSize) { 2399 | return UNDEFINED; 2400 | } 2401 | 2402 | @Override 2403 | public int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) { 2404 | return cellSize; 2405 | } 2406 | }; 2407 | 2408 | static boolean canStretch(int flexibility) { 2409 | return (flexibility & CAN_STRETCH) != 0; 2410 | } 2411 | 2412 | private static final int INFLEXIBLE = 0; 2413 | 2414 | private static final int CAN_STRETCH = 2; 2415 | 2416 | ////////////////////////////////////////////////////////////////////////// 2417 | // Pair 2418 | // 2419 | // Older versions of Android do not have this class, so here's a local 2420 | // version for backwards compatibility 2421 | 2422 | private static class Pair { 2423 | public final F first; 2424 | public final S second; 2425 | 2426 | public Pair(F first, S second) { 2427 | this.first = first; 2428 | this.second = second; 2429 | } 2430 | 2431 | public boolean equals(Object o) { 2432 | if (o == this) 2433 | return true; 2434 | if (!(o instanceof Pair)) 2435 | return false; 2436 | final Pair other; 2437 | try { 2438 | other = (Pair) o; 2439 | } 2440 | catch (ClassCastException e) { 2441 | return false; 2442 | } 2443 | return first.equals(other.first) && second.equals(other.second); 2444 | } 2445 | 2446 | public int hashCode() { 2447 | int result = 17; 2448 | result = 31 * result + first.hashCode(); 2449 | result = 31 * result + second.hashCode(); 2450 | return result; 2451 | } 2452 | 2453 | public static Pair create(A a, B b) { 2454 | return new Pair(a, b); 2455 | } 2456 | } 2457 | 2458 | ////////////////////////////////////////////////////////////////////////// 2459 | // Wrapped OnHierarchyChangeListener 2460 | // 2461 | // We need to listen to hierarchy changes ourselves, so we set our own 2462 | // listener in the constructor then handle setting of a custom listener 2463 | // for anyone else who wants to consume these events. 2464 | 2465 | private OnHierarchyChangeListener mListener; 2466 | 2467 | private final OnHierarchyChangeListener GRIDLAYOUT_LISTENER = new OnHierarchyChangeListener() { 2468 | public void onChildViewRemoved(View parent, View child) { 2469 | if (mListener != null) { 2470 | mListener.onChildViewRemoved(parent, child); 2471 | } 2472 | 2473 | invalidateStructure(); 2474 | } 2475 | 2476 | public void onChildViewAdded(View parent, View child) { 2477 | if (mListener != null) { 2478 | mListener.onChildViewAdded(parent, child); 2479 | } 2480 | 2481 | invalidateStructure(); 2482 | } 2483 | }; 2484 | 2485 | @Override 2486 | public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { 2487 | mListener = listener; 2488 | } 2489 | 2490 | ////////////////////////////////////////////////////////////////////////// 2491 | // resolveSizeAndState() wrapper 2492 | // 2493 | // This newer method of measurement was introduced in API 11, so we 2494 | // conditionally use it if available. 2495 | 2496 | private static boolean mResolveSizeAndStateAvailable; 2497 | 2498 | static { 2499 | try { 2500 | ResolveSizeAndStateWrapper.checkAvailable(); 2501 | mResolveSizeAndStateAvailable = true; 2502 | } 2503 | catch (Throwable t) { 2504 | mResolveSizeAndStateAvailable = false; 2505 | } 2506 | } 2507 | 2508 | private static class ResolveSizeAndStateWrapper { 2509 | static { 2510 | try { 2511 | View.class.getMethod("resolveSizeAndState", int.class, int.class, int.class); 2512 | } 2513 | catch (Exception ex) { 2514 | throw new RuntimeException(ex); 2515 | } 2516 | } 2517 | 2518 | public static void checkAvailable() { 2519 | } 2520 | 2521 | public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 2522 | return View.resolveSizeAndState(size, measureSpec, childMeasuredState); 2523 | } 2524 | } 2525 | 2526 | ////////////////////////////////////////////////////////////////////////// 2527 | // Notifications of child visibility changes 2528 | // 2529 | // We need to call invalidateStructure() when a child's GONE flag changes 2530 | // state. However, the API 14's implementation depends on 2531 | // ViewGroup.onChildVisibilityChanged(), which is nonexistant in older 2532 | // versions of Android. As a compromise, the method below should be 2533 | // called whenever the visibility of children change. 2534 | 2535 | public void notifyChildVisibilityChanged() { 2536 | invalidateStructure(); 2537 | } 2538 | } 2539 | -------------------------------------------------------------------------------- /library/src/com/gridlayout/Space.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.gridlayout; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.util.AttributeSet; 22 | import android.view.View; 23 | 24 | /** 25 | * Space is a lightweight View subclass that may be used to create gaps between components 26 | * in general purpose layouts. 27 | */ 28 | public final class Space extends View { 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public Space(Context context, AttributeSet attrs, int defStyle) { 33 | super(context, attrs, defStyle); 34 | if (getVisibility() == VISIBLE) { 35 | setVisibility(INVISIBLE); 36 | } 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public Space(Context context, AttributeSet attrs) { 43 | this(context, attrs, 0); 44 | } 45 | 46 | /** 47 | * {@inheritDoc} 48 | */ 49 | public Space(Context context) { 50 | //noinspection NullableProblems 51 | this(context, null); 52 | } 53 | 54 | /** 55 | * Draw nothing. 56 | * 57 | * @param canvas an unused parameter. 58 | */ 59 | @Override 60 | public void draw(Canvas canvas) { 61 | } 62 | 63 | /** 64 | * Compare to: {@link View#getDefaultSize(int, int)} 65 | * If mode is AT_MOST, return the child size instead of the parent size 66 | * (unless it is too big). 67 | */ 68 | private static int getDefaultSize2(int size, int measureSpec) { 69 | int result = size; 70 | int specMode = MeasureSpec.getMode(measureSpec); 71 | int specSize = MeasureSpec.getSize(measureSpec); 72 | 73 | switch (specMode) { 74 | case MeasureSpec.UNSPECIFIED: 75 | result = size; 76 | break; 77 | case MeasureSpec.AT_MOST: 78 | result = Math.min(size, specSize); 79 | break; 80 | case MeasureSpec.EXACTLY: 81 | result = specSize; 82 | break; 83 | } 84 | return result; 85 | } 86 | 87 | @Override 88 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 89 | setMeasuredDimension( 90 | getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec), 91 | getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /samples/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /samples/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-15 12 | android.library.reference.1=../library 13 | -------------------------------------------------------------------------------- /samples/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlew/android-gridlayout/29618e0122f28e11351f8ea9f6ea705bf9afd755/samples/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlew/android-gridlayout/29618e0122f28e11351f8ea9f6ea705bf9afd755/samples/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlew/android-gridlayout/29618e0122f28e11351f8ea9f6ea705bf9afd755/samples/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /samples/res/layout/activity_gridlayout_visibility.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 47 | 48 | -------------------------------------------------------------------------------- /samples/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World, GridLayoutSamplesActivity! 5 | GridLayoutSamples 6 | 7 | -------------------------------------------------------------------------------- /samples/src/com/gridlayout/samples/GridLayoutSamplesActivity.java: -------------------------------------------------------------------------------- 1 | package com.gridlayout.samples; 2 | 3 | import android.app.ListActivity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.ListView; 9 | 10 | public class GridLayoutSamplesActivity extends ListActivity { 11 | 12 | @Override 13 | public void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, SAMPLES)); 17 | } 18 | 19 | @Override 20 | protected void onListItemClick(ListView l, View v, int position, long id) { 21 | String item = SAMPLES[position]; 22 | if (item.equals(SAMPLE_SIMPLE)) { 23 | startActivity(new Intent(this, SimpleGridLayoutActivity.class)); 24 | } 25 | else if (item.equals(SAMPLE_VISIBILITY)) { 26 | startActivity(new Intent(this, GridLayoutVisibilityActivity.class)); 27 | } 28 | } 29 | 30 | private static final String SAMPLE_SIMPLE = "Simple GridLayout Activity"; 31 | 32 | private static final String SAMPLE_VISIBILITY = "GridLayout Visibility Activity"; 33 | 34 | private static final String[] SAMPLES = new String[] { SAMPLE_SIMPLE, SAMPLE_VISIBILITY }; 35 | } -------------------------------------------------------------------------------- /samples/src/com/gridlayout/samples/GridLayoutVisibilityActivity.java: -------------------------------------------------------------------------------- 1 | package com.gridlayout.samples; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.view.View.OnClickListener; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.gridlayout.GridLayout; 11 | 12 | public class GridLayoutVisibilityActivity extends Activity { 13 | 14 | private GridLayout mGridLayout; 15 | private TextView mTextView1; 16 | private TextView mTextView2; 17 | 18 | int cycle = 0; 19 | 20 | @Override 21 | public void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | 24 | setContentView(R.layout.activity_gridlayout_visibility); 25 | 26 | mGridLayout = (GridLayout) findViewById(R.id.grid_layout); 27 | mTextView1 = (TextView) findViewById(R.id.textview1); 28 | mTextView2 = (TextView) findViewById(R.id.textview2); 29 | 30 | Button button = (Button) findViewById(R.id.button); 31 | button.setOnClickListener(new OnClickListener() { 32 | public void onClick(View v) { 33 | if (cycle == 0) { 34 | mTextView1.setVisibility(View.GONE); 35 | mGridLayout.notifyChildVisibilityChanged(); 36 | cycle++; 37 | } 38 | else if (cycle == 1) { 39 | mTextView2.setVisibility(View.GONE); 40 | mGridLayout.notifyChildVisibilityChanged(); 41 | cycle++; 42 | } 43 | else { 44 | mTextView1.setVisibility(View.VISIBLE); 45 | mTextView2.setVisibility(View.VISIBLE); 46 | mGridLayout.notifyChildVisibilityChanged(); 47 | cycle = 0; 48 | } 49 | } 50 | }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /samples/src/com/gridlayout/samples/SimpleGridLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package com.gridlayout.samples; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | /** 7 | * Demonstrates a simple GridLayout created from XML. 8 | * 9 | * Uses the same sample code as from http://android-developers.blogspot.com/2011/11/new-layout-widgets-space-and-gridlayout.html 10 | */ 11 | public class SimpleGridLayoutActivity extends Activity { 12 | 13 | @Override 14 | public void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_simple_gridlayout); 17 | } 18 | 19 | } 20 | --------------------------------------------------------------------------------