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