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 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); 56 } 57 } 58 59 a.recycle(); 60 } 61 62 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)63 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) { 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); 71 int childHeightSpec = MeasureSpec.UNSPECIFIED; 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); 87 if (child.getMeasuredHeight() != maxHeight) { 88 child.measure(childWidthSpec, maxHeightSpec); 89 } 90 } 91 totalHeight += maxHeight; 92 if (row > 0) { 93 totalHeight += mVerticalSpacing; 94 } 95 } 96 97 setMeasuredDimension(width, resolveSizeAndState(totalHeight, heightMeasureSpec, 0)); 98 } 99 100 @Override onLayout(boolean changed, int l, int t, int r, int b)101 protected void onLayout(boolean changed, int l, int t, int r, int b) { 102 boolean isRtl = isLayoutRtl(); 103 int children = getChildCount(); 104 int rows = (children + mNumColumns - 1) / mNumColumns; 105 int y = 0; 106 for (int row = 0; row < rows; row++) { 107 int x = isRtl ? getWidth() : 0; 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(); 115 if (isRtl) { 116 x -= width; 117 } 118 child.layout(x, y, x + width, y + height); 119 maxHeight = Math.max(maxHeight, height); 120 if (isRtl) { 121 x -= mHorizontalSpacing; 122 } else { 123 x += width + mHorizontalSpacing; 124 } 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 link(ViewGroup viewGroup, BaseAdapter adapter)146 public static void link(ViewGroup viewGroup, BaseAdapter adapter) { 147 new ViewGroupAdapterBridge(viewGroup, adapter); 148 } 149 ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter)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 refresh()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 onChanged()193 public void onChanged() { 194 refresh(); 195 } 196 197 @Override onInvalidated()198 public void onInvalidated() { 199 release(); 200 } 201 release()202 private void release() { 203 if (!mReleased) { 204 mReleased = true; 205 mAdapter.unregisterDataSetObserver(this); 206 } 207 } 208 } 209 } 210