1 /*
2  * Copyright (C) 2014 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.qs;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.database.DataSetObserver;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.BaseAdapter;
26 
27 import com.android.systemui.R;
28 
29 import java.lang.ref.WeakReference;
30 
31 /**
32  * A view that arranges it's children in a grid with a fixed number of evenly spaced columns.
33  *
34  * {@see android.widget.GridView}
35  */
36 public class PseudoGridView extends ViewGroup {
37 
38     private int mNumColumns = 3;
39     private int mVerticalSpacing;
40     private int mHorizontalSpacing;
41     private int mFixedChildWidth = -1;
42 
PseudoGridView(Context context, AttributeSet attrs)43     public PseudoGridView(Context context, AttributeSet attrs) {
44         super(context, attrs);
45 
46         final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PseudoGridView);
47 
48         final int N = a.getIndexCount();
49         for (int i = 0; i < N; i++) {
50             int attr = a.getIndex(i);
51             if (attr == R.styleable.PseudoGridView_numColumns) {
52                 mNumColumns = a.getInt(attr, 3);
53             } else if (attr == R.styleable.PseudoGridView_verticalSpacing) {
54                 mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
55             } else if (attr == R.styleable.PseudoGridView_horizontalSpacing) {
56                 mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
57             } else if (attr == R.styleable.PseudoGridView_fixedChildWidth) {
58                 mFixedChildWidth = a.getDimensionPixelSize(attr, -1);
59             }
60         }
61 
62         a.recycle();
63     }
64 
65     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)66     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
67         if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
68             throw new UnsupportedOperationException("Needs a maximum width");
69         }
70         int width = MeasureSpec.getSize(widthMeasureSpec);
71         int childWidth;
72         int necessarySpaceForChildWidth =
73                 mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
74         if (mFixedChildWidth != -1 && necessarySpaceForChildWidth <= width) {
75             childWidth = mFixedChildWidth;
76             width = mFixedChildWidth * mNumColumns + mHorizontalSpacing * (mNumColumns - 1);
77         } else {
78             childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
79         }
80         int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
81         int childHeightSpec = MeasureSpec.UNSPECIFIED;
82         int totalHeight = 0;
83         int children = getChildCount();
84         int rows = (children + mNumColumns - 1) / mNumColumns;
85         for (int row = 0; row < rows; row++) {
86             int startOfRow = row * mNumColumns;
87             int endOfRow = Math.min(startOfRow + mNumColumns, children);
88             int maxHeight = 0;
89             for (int i = startOfRow; i < endOfRow; i++) {
90                 View child = getChildAt(i);
91                 child.measure(childWidthSpec, childHeightSpec);
92                 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
93             }
94             int maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
95             for (int i = startOfRow; i < endOfRow; i++) {
96                 View child = getChildAt(i);
97                 if (child.getMeasuredHeight() != maxHeight) {
98                     child.measure(childWidthSpec, maxHeightSpec);
99                 }
100             }
101             totalHeight += maxHeight;
102             if (row > 0) {
103                 totalHeight += mVerticalSpacing;
104             }
105         }
106 
107         setMeasuredDimension(width, resolveSizeAndState(totalHeight, heightMeasureSpec, 0));
108     }
109 
110     @Override
onLayout(boolean changed, int l, int t, int r, int b)111     protected void onLayout(boolean changed, int l, int t, int r, int b) {
112         boolean isRtl = isLayoutRtl();
113         int children = getChildCount();
114         int rows = (children + mNumColumns - 1) / mNumColumns;
115         int y = 0;
116         for (int row = 0; row < rows; row++) {
117             int x = isRtl ? getWidth() : 0;
118             int maxHeight = 0;
119             int startOfRow = row * mNumColumns;
120             int endOfRow = Math.min(startOfRow + mNumColumns, children);
121             for (int i = startOfRow; i < endOfRow; i++) {
122                 View child = getChildAt(i);
123                 int width = child.getMeasuredWidth();
124                 int height = child.getMeasuredHeight();
125                 if (isRtl) {
126                     x -= width;
127                 }
128                 child.layout(x, y, x + width, y + height);
129                 maxHeight = Math.max(maxHeight, height);
130                 if (isRtl) {
131                     x -= mHorizontalSpacing;
132                 } else {
133                     x += width + mHorizontalSpacing;
134                 }
135             }
136             y += maxHeight + mVerticalSpacing;
137         }
138     }
139 
140     /**
141      * Bridges between a ViewGroup and a BaseAdapter.
142      * <p>
143      * Usage: {@code ViewGroupAdapterBridge.link(viewGroup, adapter)}
144      * <br />
145      * After this call, the ViewGroup's children will be provided by the adapter.
146      */
147     public static class ViewGroupAdapterBridge extends DataSetObserver {
148 
149         private final WeakReference<ViewGroup> mViewGroup;
150         private final BaseAdapter mAdapter;
151         private boolean mReleased;
152 
link(ViewGroup viewGroup, BaseAdapter adapter)153         public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
154             new ViewGroupAdapterBridge(viewGroup, adapter);
155         }
156 
ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter)157         private ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter) {
158             mViewGroup = new WeakReference<>(viewGroup);
159             mAdapter = adapter;
160             mReleased = false;
161             mAdapter.registerDataSetObserver(this);
162             refresh();
163         }
164 
refresh()165         private void refresh() {
166             if (mReleased) {
167                 return;
168             }
169             ViewGroup viewGroup = mViewGroup.get();
170             if (viewGroup == null) {
171                 release();
172                 return;
173             }
174             final int childCount = viewGroup.getChildCount();
175             final int adapterCount = mAdapter.getCount();
176             final int N = Math.max(childCount, adapterCount);
177             for (int i = 0; i < N; i++) {
178                 if (i < adapterCount) {
179                     View oldView = null;
180                     if (i < childCount) {
181                         oldView = viewGroup.getChildAt(i);
182                     }
183                     View newView = mAdapter.getView(i, oldView, viewGroup);
184                     if (oldView == null) {
185                         // We ran out of existing views. Add it at the end.
186                         viewGroup.addView(newView);
187                     } else if (oldView != newView) {
188                         // We couldn't rebind the view. Replace it.
189                         viewGroup.removeViewAt(i);
190                         viewGroup.addView(newView, i);
191                     }
192                 } else {
193                     int lastIndex = viewGroup.getChildCount() - 1;
194                     viewGroup.removeViewAt(lastIndex);
195                 }
196             }
197         }
198 
199         @Override
onChanged()200         public void onChanged() {
201             refresh();
202         }
203 
204         @Override
onInvalidated()205         public void onInvalidated() {
206             release();
207         }
208 
release()209         private void release() {
210             if (!mReleased) {
211                 mReleased = true;
212                 mAdapter.unregisterDataSetObserver(this);
213             }
214         }
215     }
216 }
217