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