Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 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 | |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 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 | |
Winson | c0d7058 | 2016-01-29 10:24:39 -0800 | [diff] [blame] | 27 | import com.android.systemui.R; |
| 28 | |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 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 | |
| 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 Monk | 05dd567 | 2018-08-09 09:38:21 -0400 | [diff] [blame] | 50 | 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 Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 56 | } |
| 57 | } |
| 58 | |
| 59 | a.recycle(); |
| 60 | } |
| 61 | |
| 62 | @Override |
| 63 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
Adrian Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 64 | if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) { |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 65 | 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 Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 71 | int childHeightSpec = MeasureSpec.UNSPECIFIED; |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 72 | 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 Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 87 | if (child.getMeasuredHeight() != maxHeight) { |
| 88 | child.measure(childWidthSpec, maxHeightSpec); |
| 89 | } |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 90 | } |
| 91 | totalHeight += maxHeight; |
| 92 | if (row > 0) { |
| 93 | totalHeight += mVerticalSpacing; |
| 94 | } |
| 95 | } |
| 96 | |
Jason Monk | deba7a4 | 2015-12-08 16:14:10 -0500 | [diff] [blame] | 97 | setMeasuredDimension(width, resolveSizeAndState(totalHeight, heightMeasureSpec, 0)); |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | @Override |
| 101 | protected void onLayout(boolean changed, int l, int t, int r, int b) { |
Adrian Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 102 | boolean isRtl = isLayoutRtl(); |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 103 | int children = getChildCount(); |
| 104 | int rows = (children + mNumColumns - 1) / mNumColumns; |
| 105 | int y = 0; |
| 106 | for (int row = 0; row < rows; row++) { |
Adrian Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 107 | int x = isRtl ? getWidth() : 0; |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 108 | 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 Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 115 | if (isRtl) { |
| 116 | x -= width; |
| 117 | } |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 118 | child.layout(x, y, x + width, y + height); |
| 119 | maxHeight = Math.max(maxHeight, height); |
Adrian Roos | 0832b48 | 2014-08-08 15:59:03 +0200 | [diff] [blame] | 120 | if (isRtl) { |
| 121 | x -= mHorizontalSpacing; |
| 122 | } else { |
| 123 | x += width + mHorizontalSpacing; |
| 124 | } |
Adrian Roos | 1940892 | 2014-08-07 20:54:12 +0200 | [diff] [blame] | 125 | } |
| 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 | } |