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 com.android.systemui.R; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.database.DataSetObserver; 24 import android.util.AttributeSet; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.widget.BaseAdapter; 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 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 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, getDefaultSize(totalHeight, heightMeasureSpec)); 102 } 103 104 @Override 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 150 public static void link(ViewGroup viewGroup, BaseAdapter adapter) { 151 new ViewGroupAdapterBridge(viewGroup, adapter); 152 } 153 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 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 197 public void onChanged() { 198 refresh(); 199 } 200 201 @Override 202 public void onInvalidated() { 203 release(); 204 } 205 206 private void release() { 207 if (!mReleased) { 208 mReleased = true; 209 mAdapter.unregisterDataSetObserver(this); 210 } 211 } 212 } 213 } 214