-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGridLayout.java
More file actions
2974 lines (2610 loc) · 109 KB
/
Copy pathGridLayout.java
File metadata and controls
2974 lines (2610 loc) · 109 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.widget;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.LogPrinter;
import android.util.Pair;
import android.util.Printer;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews.RemoteView;
import com.android.internal.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static android.view.Gravity.*;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* A layout that places its children in a rectangular <em>grid</em>.
* <p>
* The grid is composed of a set of infinitely thin lines that separate the
* viewing area into <em>cells</em>. Throughout the API, grid lines are referenced
* by grid <em>indices</em>. A grid with {@code N} columns
* has {@code N + 1} grid indices that run from {@code 0}
* through {@code N} inclusive. Regardless of how GridLayout is
* configured, grid index {@code 0} is fixed to the leading edge of the
* container and grid index {@code N} is fixed to its trailing edge
* (after padding is taken into account).
*
* <h4>Row and Column Specs</h4>
*
* Children occupy one or more contiguous cells, as defined
* by their {@link GridLayout.LayoutParams#rowSpec rowSpec} and
* {@link GridLayout.LayoutParams#columnSpec columnSpec} layout parameters.
* Each spec defines the set of rows or columns that are to be
* occupied; and how children should be aligned within the resulting group of cells.
* Although cells do not normally overlap in a GridLayout, GridLayout does
* not prevent children being defined to occupy the same cell or group of cells.
* In this case however, there is no guarantee that children will not themselves
* overlap after the layout operation completes.
*
* <h4>Default Cell Assignment</h4>
*
* If a child does not specify the row and column indices of the cell it
* wishes to occupy, GridLayout assigns cell locations automatically using its:
* {@link GridLayout#setOrientation(int) orientation},
* {@link GridLayout#setRowCount(int) rowCount} and
* {@link GridLayout#setColumnCount(int) columnCount} properties.
*
* <h4>Space</h4>
*
* Space between children may be specified either by using instances of the
* dedicated {@link Space} view or by setting the
*
* {@link ViewGroup.MarginLayoutParams#leftMargin leftMargin},
* {@link ViewGroup.MarginLayoutParams#topMargin topMargin},
* {@link ViewGroup.MarginLayoutParams#rightMargin rightMargin} and
* {@link ViewGroup.MarginLayoutParams#bottomMargin bottomMargin}
*
* layout parameters. When the
* {@link GridLayout#setUseDefaultMargins(boolean) useDefaultMargins}
* property is set, default margins around children are automatically
* allocated based on the prevailing UI style guide for the platform.
* Each of the margins so defined may be independently overridden by an assignment
* to the appropriate layout parameter.
* Default values will generally produce a reasonable spacing between components
* but values may change between different releases of the platform.
*
* <h4>Excess Space Distribution</h4>
*
* As of API 21, GridLayout's distribution of excess space accomodates the principle of weight.
* In the event that no weights are specified, the previous conventions are respected and
* columns and rows are taken as flexible if their views specify some form of alignment
* within their groups.
* <p>
* The flexibility of a view is therefore influenced by its alignment which is,
* in turn, typically defined by setting the
* {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
* If either a weight or alignment were defined along a given axis then the component
* is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
* the component is instead assumed to be <em>inflexible</em>.
* <p>
* Multiple components in the same row or column group are
* considered to act in <em>parallel</em>. Such a
* group is flexible only if <em>all</em> of the components
* within it are flexible. Row and column groups that sit either side of a common boundary
* are instead considered to act in <em>series</em>. The composite group made of these two
* elements is flexible if <em>one</em> of its elements is flexible.
* <p>
* To make a column stretch, make sure all of the components inside it define a
* weight or a gravity. To prevent a column from stretching, ensure that one of the components
* in the column does not define a weight or a gravity.
* <p>
* When the principle of flexibility does not provide complete disambiguation,
* GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
* and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
* parameters as a constraint in the a set of variables that define the grid-lines along a
* given axis. During layout, GridLayout solves the constraints so as to return the unique
* solution to those constraints for which all variables are less-than-or-equal-to
* the corresponding value in any other valid solution.
*
* <h4>Interpretation of GONE</h4>
*
* For layout purposes, GridLayout treats views whose visibility status is
* {@link View#GONE GONE}, as having zero width and height. This is subtly different from
* the policy of ignoring views that are marked as GONE outright. If, for example, a gone-marked
* view was alone in a column, that column would itself collapse to zero width if and only if
* no gravity was defined on the view. If gravity was defined, then the gone-marked
* view has no effect on the layout and the container should be laid out as if the view
* had never been added to it. GONE views are taken to have zero weight during excess space
* distribution.
* <p>
* These statements apply equally to rows as well as columns, and to groups of rows or columns.
*
* <p>
* See {@link GridLayout.LayoutParams} for a full description of the
* layout parameters used by GridLayout.
*
* @attr ref android.R.styleable#GridLayout_orientation
* @attr ref android.R.styleable#GridLayout_rowCount
* @attr ref android.R.styleable#GridLayout_columnCount
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
@RemoteView
public class GridLayout extends ViewGroup {
// Public constants
/** @hide */
@IntDef({HORIZONTAL, VERTICAL})
@Retention(RetentionPolicy.SOURCE)
public @interface Orientation {}
/**
* The horizontal orientation.
*/
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
/**
* The vertical orientation.
*/
public static final int VERTICAL = LinearLayout.VERTICAL;
/**
* The constant used to indicate that a value is undefined.
* Fields can use this value to indicate that their values
* have not yet been set. Similarly, methods can return this value
* to indicate that there is no suitable value that the implementation
* can return.
* The value used for the constant (currently {@link Integer#MIN_VALUE}) is
* intended to avoid confusion between valid values whose sign may not be known.
*/
public static final int UNDEFINED = Integer.MIN_VALUE;
/** @hide */
@IntDef({ALIGN_BOUNDS, ALIGN_MARGINS})
@Retention(RetentionPolicy.SOURCE)
public @interface AlignmentMode {}
/**
* This constant is an {@link #setAlignmentMode(int) alignmentMode}.
* When the {@code alignmentMode} is set to {@link #ALIGN_BOUNDS}, alignment
* is made between the edges of each component's raw
* view boundary: i.e. the area delimited by the component's:
* {@link android.view.View#getTop() top},
* {@link android.view.View#getLeft() left},
* {@link android.view.View#getBottom() bottom} and
* {@link android.view.View#getRight() right} properties.
* <p>
* For example, when {@code GridLayout} is in {@link #ALIGN_BOUNDS} mode,
* children that belong to a row group that uses {@link #TOP} alignment will
* all return the same value when their {@link android.view.View#getTop()}
* method is called.
*
* @see #setAlignmentMode(int)
*/
public static final int ALIGN_BOUNDS = 0;
/**
* This constant is an {@link #setAlignmentMode(int) alignmentMode}.
* When the {@code alignmentMode} is set to {@link #ALIGN_MARGINS},
* the bounds of each view are extended outwards, according
* to their margins, before the edges of the resulting rectangle are aligned.
* <p>
* For example, when {@code GridLayout} is in {@link #ALIGN_MARGINS} mode,
* the quantity {@code top - layoutParams.topMargin} is the same for all children that
* belong to a row group that uses {@link #TOP} alignment.
*
* @see #setAlignmentMode(int)
*/
public static final int ALIGN_MARGINS = 1;
// Misc constants
static final int MAX_SIZE = 100000;
static final int DEFAULT_CONTAINER_MARGIN = 0;
static final int UNINITIALIZED_HASH = 0;
static final Printer LOG_PRINTER = new LogPrinter(Log.DEBUG, GridLayout.class.getName());
static final Printer NO_PRINTER = new Printer() {
@Override
public void println(String x) {
}
};
// Defaults
private static final int DEFAULT_ORIENTATION = HORIZONTAL;
private static final int DEFAULT_COUNT = UNDEFINED;
private static final boolean DEFAULT_USE_DEFAULT_MARGINS = false;
private static final boolean DEFAULT_ORDER_PRESERVED = true;
private static final int DEFAULT_ALIGNMENT_MODE = ALIGN_MARGINS;
// TypedArray indices
private static final int ORIENTATION = R.styleable.GridLayout_orientation;
private static final int ROW_COUNT = R.styleable.GridLayout_rowCount;
private static final int COLUMN_COUNT = R.styleable.GridLayout_columnCount;
private static final int USE_DEFAULT_MARGINS = R.styleable.GridLayout_useDefaultMargins;
private static final int ALIGNMENT_MODE = R.styleable.GridLayout_alignmentMode;
private static final int ROW_ORDER_PRESERVED = R.styleable.GridLayout_rowOrderPreserved;
private static final int COLUMN_ORDER_PRESERVED = R.styleable.GridLayout_columnOrderPreserved;
// Instance variables
final Axis mHorizontalAxis = new Axis(true);
final Axis mVerticalAxis = new Axis(false);
int mOrientation = DEFAULT_ORIENTATION;
boolean mUseDefaultMargins = DEFAULT_USE_DEFAULT_MARGINS;
int mAlignmentMode = DEFAULT_ALIGNMENT_MODE;
int mDefaultGap;
int mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
Printer mPrinter = LOG_PRINTER;
// Constructors
public GridLayout(Context context) {
this(context, null);
}
public GridLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public GridLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mDefaultGap = context.getResources().getDimensionPixelOffset(R.dimen.default_gap);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.GridLayout, defStyleAttr, defStyleRes);
try {
setRowCount(a.getInt(ROW_COUNT, DEFAULT_COUNT));
setColumnCount(a.getInt(COLUMN_COUNT, DEFAULT_COUNT));
setOrientation(a.getInt(ORIENTATION, DEFAULT_ORIENTATION));
setUseDefaultMargins(a.getBoolean(USE_DEFAULT_MARGINS, DEFAULT_USE_DEFAULT_MARGINS));
setAlignmentMode(a.getInt(ALIGNMENT_MODE, DEFAULT_ALIGNMENT_MODE));
setRowOrderPreserved(a.getBoolean(ROW_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
setColumnOrderPreserved(a.getBoolean(COLUMN_ORDER_PRESERVED, DEFAULT_ORDER_PRESERVED));
} finally {
a.recycle();
}
}
// Implementation
/**
* Returns the current orientation.
*
* @return either {@link #HORIZONTAL} or {@link #VERTICAL}
*
* @see #setOrientation(int)
*
* @attr ref android.R.styleable#GridLayout_orientation
*/
@Orientation
public int getOrientation() {
return mOrientation;
}
/**
*
* GridLayout uses the orientation property for two purposes:
* <ul>
* <li>
* To control the 'direction' in which default row/column indices are generated
* when they are not specified in a component's layout parameters.
* </li>
* <li>
* To control which axis should be processed first during the layout operation:
* when orientation is {@link #HORIZONTAL} the horizontal axis is laid out first.
* </li>
* </ul>
*
* The order in which axes are laid out is important if, for example, the height of
* one of GridLayout's children is dependent on its width - and its width is, in turn,
* dependent on the widths of other components.
* <p>
* If your layout contains a {@link TextView} (or derivative:
* {@code Button}, {@code EditText}, {@code CheckBox}, etc.) which is
* in multi-line mode (the default) it is normally best to leave GridLayout's
* orientation as {@code HORIZONTAL} - because {@code TextView} is capable of
* deriving its height for a given width, but not the other way around.
* <p>
* Other than the effects above, orientation does not affect the actual layout operation of
* GridLayout, so it's fine to leave GridLayout in {@code HORIZONTAL} mode even if
* the height of the intended layout greatly exceeds its width.
* <p>
* The default value of this property is {@link #HORIZONTAL}.
*
* @param orientation either {@link #HORIZONTAL} or {@link #VERTICAL}
*
* @see #getOrientation()
*
* @attr ref android.R.styleable#GridLayout_orientation
*/
public void setOrientation(@Orientation int orientation) {
if (this.mOrientation != orientation) {
this.mOrientation = orientation;
invalidateStructure();
requestLayout();
}
}
/**
* Returns the current number of rows. This is either the last value that was set
* with {@link #setRowCount(int)} or, if no such value was set, the maximum
* value of each the upper bounds defined in {@link LayoutParams#rowSpec}.
*
* @return the current number of rows
*
* @see #setRowCount(int)
* @see LayoutParams#rowSpec
*
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public int getRowCount() {
return mVerticalAxis.getCount();
}
/**
* RowCount is used only to generate default row/column indices when
* they are not specified by a component's layout parameters.
*
* @param rowCount the number of rows
*
* @see #getRowCount()
* @see LayoutParams#rowSpec
*
* @attr ref android.R.styleable#GridLayout_rowCount
*/
public void setRowCount(int rowCount) {
mVerticalAxis.setCount(rowCount);
invalidateStructure();
requestLayout();
}
/**
* Returns the current number of columns. This is either the last value that was set
* with {@link #setColumnCount(int)} or, if no such value was set, the maximum
* value of each the upper bounds defined in {@link LayoutParams#columnSpec}.
*
* @return the current number of columns
*
* @see #setColumnCount(int)
* @see LayoutParams#columnSpec
*
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public int getColumnCount() {
return mHorizontalAxis.getCount();
}
/**
* ColumnCount is used only to generate default column/column indices when
* they are not specified by a component's layout parameters.
*
* @param columnCount the number of columns.
*
* @see #getColumnCount()
* @see LayoutParams#columnSpec
*
* @attr ref android.R.styleable#GridLayout_columnCount
*/
public void setColumnCount(int columnCount) {
mHorizontalAxis.setCount(columnCount);
invalidateStructure();
requestLayout();
}
/**
* Returns whether or not this GridLayout will allocate default margins when no
* corresponding layout parameters are defined.
*
* @return {@code true} if default margins should be allocated
*
* @see #setUseDefaultMargins(boolean)
*
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public boolean getUseDefaultMargins() {
return mUseDefaultMargins;
}
/**
* When {@code true}, GridLayout allocates default margins around children
* based on the child's visual characteristics. Each of the
* margins so defined may be independently overridden by an assignment
* to the appropriate layout parameter.
* <p>
* When {@code false}, the default value of all margins is zero.
* <p>
* When setting to {@code true}, consider setting the value of the
* {@link #setAlignmentMode(int) alignmentMode}
* property to {@link #ALIGN_BOUNDS}.
* <p>
* The default value of this property is {@code false}.
*
* @param useDefaultMargins use {@code true} to make GridLayout allocate default margins
*
* @see #getUseDefaultMargins()
* @see #setAlignmentMode(int)
*
* @see MarginLayoutParams#leftMargin
* @see MarginLayoutParams#topMargin
* @see MarginLayoutParams#rightMargin
* @see MarginLayoutParams#bottomMargin
*
* @attr ref android.R.styleable#GridLayout_useDefaultMargins
*/
public void setUseDefaultMargins(boolean useDefaultMargins) {
this.mUseDefaultMargins = useDefaultMargins;
requestLayout();
}
/**
* Returns the alignment mode.
*
* @return the alignment mode; either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
*
* @see #ALIGN_BOUNDS
* @see #ALIGN_MARGINS
*
* @see #setAlignmentMode(int)
*
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
@AlignmentMode
public int getAlignmentMode() {
return mAlignmentMode;
}
/**
* Sets the alignment mode to be used for all of the alignments between the
* children of this container.
* <p>
* The default value of this property is {@link #ALIGN_MARGINS}.
*
* @param alignmentMode either {@link #ALIGN_BOUNDS} or {@link #ALIGN_MARGINS}
*
* @see #ALIGN_BOUNDS
* @see #ALIGN_MARGINS
*
* @see #getAlignmentMode()
*
* @attr ref android.R.styleable#GridLayout_alignmentMode
*/
public void setAlignmentMode(@AlignmentMode int alignmentMode) {
this.mAlignmentMode = alignmentMode;
requestLayout();
}
/**
* Returns whether or not row boundaries are ordered by their grid indices.
*
* @return {@code true} if row boundaries must appear in the order of their indices,
* {@code false} otherwise
*
* @see #setRowOrderPreserved(boolean)
*
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public boolean isRowOrderPreserved() {
return mVerticalAxis.isOrderPreserved();
}
/**
* When this property is {@code true}, GridLayout is forced to place the row boundaries
* so that their associated grid indices are in ascending order in the view.
* <p>
* When this property is {@code false} GridLayout is at liberty to place the vertical row
* boundaries in whatever order best fits the given constraints.
* <p>
* The default value of this property is {@code true}.
* @param rowOrderPreserved {@code true} to force GridLayout to respect the order
* of row boundaries
*
* @see #isRowOrderPreserved()
*
* @attr ref android.R.styleable#GridLayout_rowOrderPreserved
*/
public void setRowOrderPreserved(boolean rowOrderPreserved) {
mVerticalAxis.setOrderPreserved(rowOrderPreserved);
invalidateStructure();
requestLayout();
}
/**
* Returns whether or not column boundaries are ordered by their grid indices.
*
* @return {@code true} if column boundaries must appear in the order of their indices,
* {@code false} otherwise
*
* @see #setColumnOrderPreserved(boolean)
*
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public boolean isColumnOrderPreserved() {
return mHorizontalAxis.isOrderPreserved();
}
/**
* When this property is {@code true}, GridLayout is forced to place the column boundaries
* so that their associated grid indices are in ascending order in the view.
* <p>
* When this property is {@code false} GridLayout is at liberty to place the horizontal column
* boundaries in whatever order best fits the given constraints.
* <p>
* The default value of this property is {@code true}.
*
* @param columnOrderPreserved use {@code true} to force GridLayout to respect the order
* of column boundaries.
*
* @see #isColumnOrderPreserved()
*
* @attr ref android.R.styleable#GridLayout_columnOrderPreserved
*/
public void setColumnOrderPreserved(boolean columnOrderPreserved) {
mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
invalidateStructure();
requestLayout();
}
/**
* Return the printer that will log diagnostics from this layout.
*
* @see #setPrinter(android.util.Printer)
*
* @return the printer associated with this view
*
* @hide
*/
public Printer getPrinter() {
return mPrinter;
}
/**
* Set the printer that will log diagnostics from this layout.
* The default value is created by {@link android.util.LogPrinter}.
*
* @param printer the printer associated with this layout
*
* @see #getPrinter()
*
* @hide
*/
public void setPrinter(Printer printer) {
this.mPrinter = (printer == null) ? NO_PRINTER : printer;
}
// Static utility methods
static int max2(int[] a, int valueIfEmpty) {
int result = valueIfEmpty;
for (int i = 0, N = a.length; i < N; i++) {
result = Math.max(result, a[i]);
}
return result;
}
@SuppressWarnings("unchecked")
static <T> T[] append(T[] a, T[] b) {
T[] result = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length + b.length);
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
static Alignment getAlignment(int gravity, boolean horizontal) {
int mask = horizontal ? HORIZONTAL_GRAVITY_MASK : VERTICAL_GRAVITY_MASK;
int shift = horizontal ? AXIS_X_SHIFT : AXIS_Y_SHIFT;
int flags = (gravity & mask) >> shift;
switch (flags) {
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
return horizontal ? LEFT : TOP;
case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
return horizontal ? RIGHT : BOTTOM;
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
return FILL;
case AXIS_SPECIFIED:
return CENTER;
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | RELATIVE_LAYOUT_DIRECTION):
return START;
case (AXIS_SPECIFIED | AXIS_PULL_AFTER | RELATIVE_LAYOUT_DIRECTION):
return END;
default:
return UNDEFINED_ALIGNMENT;
}
}
/** @noinspection UnusedParameters*/
private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
if (c.getClass() == Space.class) {
return 0;
}
return mDefaultGap / 2;
}
private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
return /*isAtEdge ? DEFAULT_CONTAINER_MARGIN :*/ getDefaultMargin(c, horizontal, leading);
}
private int getDefaultMargin(View c, LayoutParams p, boolean horizontal, boolean leading) {
if (!mUseDefaultMargins) {
return 0;
}
Spec spec = horizontal ? p.columnSpec : p.rowSpec;
Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
Interval span = spec.span;
boolean leading1 = (horizontal && isLayoutRtl()) ? !leading : leading;
boolean isAtEdge = leading1 ? (span.min == 0) : (span.max == axis.getCount());
return getDefaultMargin(c, isAtEdge, horizontal, leading);
}
int getMargin1(View view, boolean horizontal, boolean leading) {
LayoutParams lp = getLayoutParams(view);
int margin = horizontal ?
(leading ? lp.leftMargin : lp.rightMargin) :
(leading ? lp.topMargin : lp.bottomMargin);
return margin == UNDEFINED ? getDefaultMargin(view, lp, horizontal, leading) : margin;
}
private int getMargin(View view, boolean horizontal, boolean leading) {
if (mAlignmentMode == ALIGN_MARGINS) {
return getMargin1(view, horizontal, leading);
} else {
Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int[] margins = leading ? axis.getLeadingMargins() : axis.getTrailingMargins();
LayoutParams lp = getLayoutParams(view);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
int index = leading ? spec.span.min : spec.span.max;
return margins[index];
}
}
private int getTotalMargin(View child, boolean horizontal) {
return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
}
private static boolean fits(int[] a, int value, int start, int end) {
if (end > a.length) {
return false;
}
for (int i = start; i < end; i++) {
if (a[i] > value) {
return false;
}
}
return true;
}
private static void procrusteanFill(int[] a, int start, int end, int value) {
int length = a.length;
Arrays.fill(a, Math.min(start, length), Math.min(end, length), value);
}
private static void setCellGroup(LayoutParams lp, int row, int rowSpan, int col, int colSpan) {
lp.setRowSpecSpan(new Interval(row, row + rowSpan));
lp.setColumnSpecSpan(new Interval(col, col + colSpan));
}
// Logic to avert infinite loops by ensuring that the cells can be placed somewhere.
private static int clip(Interval minorRange, boolean minorWasDefined, int count) {
int size = minorRange.size();
if (count == 0) {
return size;
}
int min = minorWasDefined ? min(minorRange.min, count) : 0;
return min(size, count - min);
}
// install default indices for cells that don't define them
private void validateLayoutParams() {
final boolean horizontal = (mOrientation == HORIZONTAL);
final Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
final int count = (axis.definedCount != UNDEFINED) ? axis.definedCount : 0;
int major = 0;
int minor = 0;
int[] maxSizes = new int[count];
for (int i = 0, N = getChildCount(); i < N; i++) {
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
final Spec majorSpec = horizontal ? lp.rowSpec : lp.columnSpec;
final Interval majorRange = majorSpec.span;
final boolean majorWasDefined = majorSpec.startDefined;
final int majorSpan = majorRange.size();
if (majorWasDefined) {
major = majorRange.min;
}
final Spec minorSpec = horizontal ? lp.columnSpec : lp.rowSpec;
final Interval minorRange = minorSpec.span;
final boolean minorWasDefined = minorSpec.startDefined;
final int minorSpan = clip(minorRange, minorWasDefined, count);
if (minorWasDefined) {
minor = minorRange.min;
}
if (count != 0) {
// Find suitable row/col values when at least one is undefined.
if (!majorWasDefined || !minorWasDefined) {
while (!fits(maxSizes, major, minor, minor + minorSpan)) {
if (minorWasDefined) {
major++;
} else {
if (minor + minorSpan <= count) {
minor++;
} else {
minor = 0;
major++;
}
}
}
}
procrusteanFill(maxSizes, minor, minor + minorSpan, major + majorSpan);
}
if (horizontal) {
setCellGroup(lp, major, majorSpan, minor, minorSpan);
} else {
setCellGroup(lp, minor, minorSpan, major, majorSpan);
}
minor = minor + minorSpan;
}
}
private void invalidateStructure() {
mLastLayoutParamsHashCode = UNINITIALIZED_HASH;
mHorizontalAxis.invalidateStructure();
mVerticalAxis.invalidateStructure();
// This can end up being done twice. Better twice than not at all.
invalidateValues();
}
private void invalidateValues() {
// Need null check because requestLayout() is called in View's initializer,
// before we are set up.
if (mHorizontalAxis != null && mVerticalAxis != null) {
mHorizontalAxis.invalidateValues();
mVerticalAxis.invalidateValues();
}
}
/** @hide */
@Override
protected void onSetLayoutParams(View child, ViewGroup.LayoutParams layoutParams) {
super.onSetLayoutParams(child, layoutParams);
if (!checkLayoutParams(layoutParams)) {
handleInvalidParams("supplied LayoutParams are of the wrong type");
}
invalidateStructure();
}
final LayoutParams getLayoutParams(View c) {
return (LayoutParams) c.getLayoutParams();
}
private static void handleInvalidParams(String msg) {
throw new IllegalArgumentException(msg + ". ");
}
private void checkLayoutParams(LayoutParams lp, boolean horizontal) {
String groupName = horizontal ? "column" : "row";
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
Interval span = spec.span;
if (span.min != UNDEFINED && span.min < 0) {
handleInvalidParams(groupName + " indices must be positive");
}
Axis axis = horizontal ? mHorizontalAxis : mVerticalAxis;
int count = axis.definedCount;
if (count != UNDEFINED) {
if (span.max > count) {
handleInvalidParams(groupName +
" indices (start + span) mustn't exceed the " + groupName + " count");
}
if (span.size() > count) {
handleInvalidParams(groupName + " span mustn't exceed the " + groupName + " count");
}
}
}
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
if (!(p instanceof LayoutParams)) {
return false;
}
LayoutParams lp = (LayoutParams) p;
checkLayoutParams(lp, true);
checkLayoutParams(lp, false);
return true;
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams();
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
// Draw grid
private void drawLine(Canvas graphics, int x1, int y1, int x2, int y2, Paint paint) {
if (isLayoutRtl()) {
int width = getWidth();
graphics.drawLine(width - x1, y1, width - x2, y2, paint);
} else {
graphics.drawLine(x1, y1, x2, y2, paint);
}
}
/**
* @hide
*/
@Override
protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
// Apply defaults, so as to remove UNDEFINED values
LayoutParams lp = new LayoutParams();
for (int i = 0; i < getChildCount(); i++) {
View c = getChildAt(i);
lp.setMargins(
getMargin1(c, true, true),
getMargin1(c, false, true),
getMargin1(c, true, false),
getMargin1(c, false, false));
lp.onDebugDraw(c, canvas, paint);
}
}
/**
* @hide
*/
@Override
protected void onDebugDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.argb(50, 255, 255, 255));
Insets insets = getOpticalInsets();
int top = getPaddingTop() + insets.top;
int left = getPaddingLeft() + insets.left;
int right = getWidth() - getPaddingRight() - insets.right;
int bottom = getHeight() - getPaddingBottom() - insets.bottom;
int[] xs = mHorizontalAxis.locations;
if (xs != null) {
for (int i = 0, length = xs.length; i < length; i++) {
int x = left + xs[i];
drawLine(canvas, x, top, x, bottom, paint);
}
}
int[] ys = mVerticalAxis.locations;
if (ys != null) {
for (int i = 0, length = ys.length; i < length; i++) {
int y = top + ys[i];
drawLine(canvas, left, y, right, y, paint);
}
}
super.onDebugDraw(canvas);
}
@Override
public void onViewAdded(View child) {
super.onViewAdded(child);
invalidateStructure();
}
@Override
public void onViewRemoved(View child) {
super.onViewRemoved(child);
invalidateStructure();
}
/**
* We need to call invalidateStructure() when a child's GONE flag changes state.
* This implementation is a catch-all, invalidating on any change in the visibility flags.
*
* @hide
*/
@Override
protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
if (oldVisibility == GONE || newVisibility == GONE) {
invalidateStructure();
}
}
private int computeLayoutParamsHashCode() {
int result = 1;
for (int i = 0, N = getChildCount(); i < N; i++) {
View c = getChildAt(i);
if (c.getVisibility() == View.GONE) continue;
LayoutParams lp = (LayoutParams) c.getLayoutParams();
result = 31 * result + lp.hashCode();
}
return result;
}
private void consistencyCheck() {
if (mLastLayoutParamsHashCode == UNINITIALIZED_HASH) {
validateLayoutParams();
mLastLayoutParamsHashCode = computeLayoutParamsHashCode();
} else if (mLastLayoutParamsHashCode != computeLayoutParamsHashCode()) {
mPrinter.println("The fields of some layout parameters were modified in between "
+ "layout operations. Check the javadoc for GridLayout.LayoutParams#rowSpec.");
invalidateStructure();
consistencyCheck();
}
}
// Measurement
// Note: padding has already been removed from the supplied specs
private void measureChildWithMargins2(View child, int parentWidthSpec, int parentHeightSpec,
int childWidth, int childHeight) {
int childWidthSpec = getChildMeasureSpec(parentWidthSpec,
getTotalMargin(child, true), childWidth);
int childHeightSpec = getChildMeasureSpec(parentHeightSpec,
getTotalMargin(child, false), childHeight);
child.measure(childWidthSpec, childHeightSpec);
}
// Note: padding has already been removed from the supplied specs
private void measureChildrenWithMargins(int widthSpec, int heightSpec, boolean firstPass) {