• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.android.systemui.globalactions;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.LinearLayout;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 /**
28  * Layout which uses nested LinearLayouts to create a grid with the following behavior:
29  *
30  * * Try to maintain a 'square' grid (equal number of columns and rows) based on the expected item
31  *   count.
32  * * Determine the position and parent of any item by its index and the total item count.
33  * * Display and hide sub-lists as needed, depending on the expected item count.
34  *
35  * While we could implement this behavior with a GridLayout, it would take significantly more
36  * time and effort, and would require more substantial refactoring of the existing code in
37  * GlobalActionsDialog, since it would require manipulation of layout properties on the child items
38  * themselves.
39  *
40  */
41 
42 public class ListGridLayout extends LinearLayout {
43     private static final String TAG = "ListGridLayout";
44     private int mExpectedCount;
45     private int mCurrentCount = 0;
46     private boolean mSwapRowsAndColumns;
47     private boolean mReverseSublists;
48     private boolean mReverseItems;
49 
50     // number of rows and columns to use for different numbers of items
51     private final int[][] mConfigs = {
52             // {rows, columns}
53             {0, 0}, // 0 items
54             {1, 1}, // 1 item
55             {1, 2}, // 2 items
56             {1, 3}, // 3 items
57             {2, 2}, // 4 items
58             {2, 3}, // 5 items
59             {2, 3}, // 6 items
60             {3, 3}, // 7 items
61             {3, 3}, // 8 items
62             {3, 3}  // 9 items
63     };
64 
ListGridLayout(Context context, AttributeSet attrs)65     public ListGridLayout(Context context, AttributeSet attrs) {
66         super(context, attrs);
67     }
68 
69     /**
70      * Sets whether this grid should prioritize filling rows or columns first.
71      */
setSwapRowsAndColumns(boolean swap)72     public void setSwapRowsAndColumns(boolean swap) {
73         mSwapRowsAndColumns = swap;
74     }
75 
76     /**
77      * Sets whether this grid should fill sublists in reverse order.
78      */
setReverseSublists(boolean reverse)79     public void setReverseSublists(boolean reverse) {
80         mReverseSublists = reverse;
81     }
82 
83     /**
84      * Sets whether this grid should add items to sublists in reverse order.
85      * @param reverse
86      */
setReverseItems(boolean reverse)87     public void setReverseItems(boolean reverse) {
88         mReverseItems = reverse;
89     }
90 
91     /**
92      * Remove all items from this grid.
93      */
removeAllItems()94     public void removeAllItems() {
95         for (int i = 0; i < getChildCount(); i++) {
96             ViewGroup subList = getSublist(i);
97             if (subList != null) {
98                 subList.removeAllViews();
99                 subList.setVisibility(View.GONE);
100             }
101         }
102         mCurrentCount = 0;
103     }
104 
105     /**
106      * Adds a view item to this grid, placing it in the correct sublist and ensuring that the
107      * sublist is visible.
108      *
109      * This function is stateful, since it tracks how many items have been added thus far, to
110      * determine which sublist they should be added to. To ensure that this works correctly, call
111      * removeAllItems() instead of removing views individually with removeView() to ensure that the
112      * counter gets reset correctly.
113      * @param item
114      */
addItem(View item)115     public void addItem(View item) {
116         ViewGroup parent = getParentView(mCurrentCount, mReverseSublists, mSwapRowsAndColumns);
117         if (mReverseItems) {
118             parent.addView(item, 0);
119         } else {
120             parent.addView(item);
121         }
122         parent.setVisibility(View.VISIBLE);
123         mCurrentCount++;
124     }
125 
126     /**
127      * Get the parent view associated with the item which should be placed at the given position.
128      * @param index The index of the item.
129      * @param reverseSublists Reverse the order of sublists. Ordinarily, sublists fill from first to
130      *                        last, whereas setting this to true will fill them last to first.
131      * @param swapRowsAndColumns Swap the order in which rows and columns are filled. By default,
132      *                           columns fill first, adding one item to each row. Setting this to
133      *                           true will cause rows to fill first, adding one item to each column.
134      * @return
135      */
136     @VisibleForTesting
getParentView(int index, boolean reverseSublists, boolean swapRowsAndColumns)137     protected ViewGroup getParentView(int index, boolean reverseSublists,
138             boolean swapRowsAndColumns) {
139         if (getRowCount() == 0 || index < 0) {
140             return null;
141         }
142         int targetIndex = Math.min(index, getMaxElementCount() - 1);
143         int row = getParentViewIndex(targetIndex, reverseSublists, swapRowsAndColumns);
144         return getSublist(row);
145     }
146 
147     @VisibleForTesting
getSublist(int index)148     protected ViewGroup getSublist(int index) {
149         return (ViewGroup) getChildAt(index);
150     }
151 
reverseSublistIndex(int index)152     private int reverseSublistIndex(int index) {
153         return getChildCount() - (index + 1);
154     }
155 
getParentViewIndex(int index, boolean reverseSublists, boolean swapRowsAndColumns)156     private int getParentViewIndex(int index, boolean reverseSublists, boolean swapRowsAndColumns) {
157         int sublistIndex;
158         int rows = getRowCount();
159         if (swapRowsAndColumns) {
160             sublistIndex = (int) Math.floor(index / rows);
161         } else {
162             sublistIndex = index % rows;
163         }
164         if (reverseSublists) {
165             sublistIndex = reverseSublistIndex(sublistIndex);
166         }
167         return sublistIndex;
168     }
169 
170     /**
171      * Sets the expected number of items that this grid will be responsible for rendering.
172      */
setExpectedCount(int count)173     public void setExpectedCount(int count) {
174         mExpectedCount = count;
175     }
176 
getMaxElementCount()177     private int getMaxElementCount() {
178         return mConfigs.length - 1;
179     }
180 
getConfig()181     private int[] getConfig() {
182         if (mExpectedCount < 0) {
183             return mConfigs[0];
184         }
185         int targetElements = Math.min(getMaxElementCount(), mExpectedCount);
186         return mConfigs[targetElements];
187     }
188 
189     /**
190      * Get the number of rows which will be used to render children.
191      */
getRowCount()192     public int getRowCount() {
193         return getConfig()[0];
194     }
195 
196     /**
197      * Get the number of columns which will be used to render children.
198      */
getColumnCount()199     public int getColumnCount() {
200         return getConfig()[1];
201     }
202 }
203