blob: 87c64c78edc8419f6cffdd73e6847905e7ebf3b5 [file] [log] [blame]
Adrian Roos19408922014-08-07 20:54:12 +02001/*
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
17package com.android.systemui.qs;
18
Adrian Roos19408922014-08-07 20:54:12 +020019import android.content.Context;
20import android.content.res.TypedArray;
21import android.database.DataSetObserver;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.BaseAdapter;
26
Winsonc0d70582016-01-29 10:24:39 -080027import com.android.systemui.R;
28
Adrian Roos19408922014-08-07 20:54:12 +020029import 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 */
36public class PseudoGridView extends ViewGroup {
37
38 private int mNumColumns = 3;
39 private int mVerticalSpacing;
40 private int mHorizontalSpacing;
41
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);
Jason Monk05dd5672018-08-09 09:38:21 -040050 if (attr == R.styleable.PseudoGridView_numColumns) {
51 mNumColumns = a.getInt(attr, 3);
52 } else if (attr == R.styleable.PseudoGridView_verticalSpacing) {
53 mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
54 } else if (attr == R.styleable.PseudoGridView_horizontalSpacing) {
55 mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
Adrian Roos19408922014-08-07 20:54:12 +020056 }
57 }
58
59 a.recycle();
60 }
61
62 @Override
63 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Adrian Roos0832b482014-08-08 15:59:03 +020064 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
Adrian Roos19408922014-08-07 20:54:12 +020065 throw new UnsupportedOperationException("Needs a maximum width");
66 }
67 int width = MeasureSpec.getSize(widthMeasureSpec);
68
69 int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
70 int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
Adrian Roos0832b482014-08-08 15:59:03 +020071 int childHeightSpec = MeasureSpec.UNSPECIFIED;
Adrian Roos19408922014-08-07 20:54:12 +020072 int totalHeight = 0;
73 int children = getChildCount();
74 int rows = (children + mNumColumns - 1) / mNumColumns;
75 for (int row = 0; row < rows; row++) {
76 int startOfRow = row * mNumColumns;
77 int endOfRow = Math.min(startOfRow + mNumColumns, children);
78 int maxHeight = 0;
79 for (int i = startOfRow; i < endOfRow; i++) {
80 View child = getChildAt(i);
81 child.measure(childWidthSpec, childHeightSpec);
82 maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
83 }
84 int maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
85 for (int i = startOfRow; i < endOfRow; i++) {
86 View child = getChildAt(i);
Adrian Roos0832b482014-08-08 15:59:03 +020087 if (child.getMeasuredHeight() != maxHeight) {
88 child.measure(childWidthSpec, maxHeightSpec);
89 }
Adrian Roos19408922014-08-07 20:54:12 +020090 }
91 totalHeight += maxHeight;
92 if (row > 0) {
93 totalHeight += mVerticalSpacing;
94 }
95 }
96
Jason Monkdeba7a42015-12-08 16:14:10 -050097 setMeasuredDimension(width, resolveSizeAndState(totalHeight, heightMeasureSpec, 0));
Adrian Roos19408922014-08-07 20:54:12 +020098 }
99
100 @Override
101 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Adrian Roos0832b482014-08-08 15:59:03 +0200102 boolean isRtl = isLayoutRtl();
Adrian Roos19408922014-08-07 20:54:12 +0200103 int children = getChildCount();
104 int rows = (children + mNumColumns - 1) / mNumColumns;
105 int y = 0;
106 for (int row = 0; row < rows; row++) {
Adrian Roos0832b482014-08-08 15:59:03 +0200107 int x = isRtl ? getWidth() : 0;
Adrian Roos19408922014-08-07 20:54:12 +0200108 int maxHeight = 0;
109 int startOfRow = row * mNumColumns;
110 int endOfRow = Math.min(startOfRow + mNumColumns, children);
111 for (int i = startOfRow; i < endOfRow; i++) {
112 View child = getChildAt(i);
113 int width = child.getMeasuredWidth();
114 int height = child.getMeasuredHeight();
Adrian Roos0832b482014-08-08 15:59:03 +0200115 if (isRtl) {
116 x -= width;
117 }
Adrian Roos19408922014-08-07 20:54:12 +0200118 child.layout(x, y, x + width, y + height);
119 maxHeight = Math.max(maxHeight, height);
Adrian Roos0832b482014-08-08 15:59:03 +0200120 if (isRtl) {
121 x -= mHorizontalSpacing;
122 } else {
123 x += width + mHorizontalSpacing;
124 }
Adrian Roos19408922014-08-07 20:54:12 +0200125 }
126 y += maxHeight;
127 if (row > 0) {
128 y += mVerticalSpacing;
129 }
130 }
131 }
132
133 /**
134 * Bridges between a ViewGroup and a BaseAdapter.
135 * <p>
136 * Usage: {@code ViewGroupAdapterBridge.link(viewGroup, adapter)}
137 * <br />
138 * After this call, the ViewGroup's children will be provided by the adapter.
139 */
140 public static class ViewGroupAdapterBridge extends DataSetObserver {
141
142 private final WeakReference<ViewGroup> mViewGroup;
143 private final BaseAdapter mAdapter;
144 private boolean mReleased;
145
146 public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
147 new ViewGroupAdapterBridge(viewGroup, adapter);
148 }
149
150 private ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter) {
151 mViewGroup = new WeakReference<>(viewGroup);
152 mAdapter = adapter;
153 mReleased = false;
154 mAdapter.registerDataSetObserver(this);
155 refresh();
156 }
157
158 private void refresh() {
159 if (mReleased) {
160 return;
161 }
162 ViewGroup viewGroup = mViewGroup.get();
163 if (viewGroup == null) {
164 release();
165 return;
166 }
167 final int childCount = viewGroup.getChildCount();
168 final int adapterCount = mAdapter.getCount();
169 final int N = Math.max(childCount, adapterCount);
170 for (int i = 0; i < N; i++) {
171 if (i < adapterCount) {
172 View oldView = null;
173 if (i < childCount) {
174 oldView = viewGroup.getChildAt(i);
175 }
176 View newView = mAdapter.getView(i, oldView, viewGroup);
177 if (oldView == null) {
178 // We ran out of existing views. Add it at the end.
179 viewGroup.addView(newView);
180 } else if (oldView != newView) {
181 // We couldn't rebind the view. Replace it.
182 viewGroup.removeViewAt(i);
183 viewGroup.addView(newView, i);
184 }
185 } else {
186 int lastIndex = viewGroup.getChildCount() - 1;
187 viewGroup.removeViewAt(lastIndex);
188 }
189 }
190 }
191
192 @Override
193 public void onChanged() {
194 refresh();
195 }
196
197 @Override
198 public void onInvalidated() {
199 release();
200 }
201
202 private void release() {
203 if (!mReleased) {
204 mReleased = true;
205 mAdapter.unregisterDataSetObserver(this);
206 }
207 }
208 }
209}