blob: 87c64c78edc8419f6cffdd73e6847905e7ebf3b5 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.qs;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.android.systemui.R;
import java.lang.ref.WeakReference;
/**
* A view that arranges it's children in a grid with a fixed number of evenly spaced columns.
*
* {@see android.widget.GridView}
*/
public class PseudoGridView extends ViewGroup {
private int mNumColumns = 3;
private int mVerticalSpacing;
private int mHorizontalSpacing;
public PseudoGridView(Context context, AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PseudoGridView);
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
if (attr == R.styleable.PseudoGridView_numColumns) {
mNumColumns = a.getInt(attr, 3);
} else if (attr == R.styleable.PseudoGridView_verticalSpacing) {
mVerticalSpacing = a.getDimensionPixelSize(attr, 0);
} else if (attr == R.styleable.PseudoGridView_horizontalSpacing) {
mHorizontalSpacing = a.getDimensionPixelSize(attr, 0);
}
}
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) {
throw new UnsupportedOperationException("Needs a maximum width");
}
int width = MeasureSpec.getSize(widthMeasureSpec);
int childWidth = (width - (mNumColumns - 1) * mHorizontalSpacing) / mNumColumns;
int childWidthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
int childHeightSpec = MeasureSpec.UNSPECIFIED;
int totalHeight = 0;
int children = getChildCount();
int rows = (children + mNumColumns - 1) / mNumColumns;
for (int row = 0; row < rows; row++) {
int startOfRow = row * mNumColumns;
int endOfRow = Math.min(startOfRow + mNumColumns, children);
int maxHeight = 0;
for (int i = startOfRow; i < endOfRow; i++) {
View child = getChildAt(i);
child.measure(childWidthSpec, childHeightSpec);
maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
}
int maxHeightSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
for (int i = startOfRow; i < endOfRow; i++) {
View child = getChildAt(i);
if (child.getMeasuredHeight() != maxHeight) {
child.measure(childWidthSpec, maxHeightSpec);
}
}
totalHeight += maxHeight;
if (row > 0) {
totalHeight += mVerticalSpacing;
}
}
setMeasuredDimension(width, resolveSizeAndState(totalHeight, heightMeasureSpec, 0));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
boolean isRtl = isLayoutRtl();
int children = getChildCount();
int rows = (children + mNumColumns - 1) / mNumColumns;
int y = 0;
for (int row = 0; row < rows; row++) {
int x = isRtl ? getWidth() : 0;
int maxHeight = 0;
int startOfRow = row * mNumColumns;
int endOfRow = Math.min(startOfRow + mNumColumns, children);
for (int i = startOfRow; i < endOfRow; i++) {
View child = getChildAt(i);
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
if (isRtl) {
x -= width;
}
child.layout(x, y, x + width, y + height);
maxHeight = Math.max(maxHeight, height);
if (isRtl) {
x -= mHorizontalSpacing;
} else {
x += width + mHorizontalSpacing;
}
}
y += maxHeight;
if (row > 0) {
y += mVerticalSpacing;
}
}
}
/**
* Bridges between a ViewGroup and a BaseAdapter.
* <p>
* Usage: {@code ViewGroupAdapterBridge.link(viewGroup, adapter)}
* <br />
* After this call, the ViewGroup's children will be provided by the adapter.
*/
public static class ViewGroupAdapterBridge extends DataSetObserver {
private final WeakReference<ViewGroup> mViewGroup;
private final BaseAdapter mAdapter;
private boolean mReleased;
public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
new ViewGroupAdapterBridge(viewGroup, adapter);
}
private ViewGroupAdapterBridge(ViewGroup viewGroup, BaseAdapter adapter) {
mViewGroup = new WeakReference<>(viewGroup);
mAdapter = adapter;
mReleased = false;
mAdapter.registerDataSetObserver(this);
refresh();
}
private void refresh() {
if (mReleased) {
return;
}
ViewGroup viewGroup = mViewGroup.get();
if (viewGroup == null) {
release();
return;
}
final int childCount = viewGroup.getChildCount();
final int adapterCount = mAdapter.getCount();
final int N = Math.max(childCount, adapterCount);
for (int i = 0; i < N; i++) {
if (i < adapterCount) {
View oldView = null;
if (i < childCount) {
oldView = viewGroup.getChildAt(i);
}
View newView = mAdapter.getView(i, oldView, viewGroup);
if (oldView == null) {
// We ran out of existing views. Add it at the end.
viewGroup.addView(newView);
} else if (oldView != newView) {
// We couldn't rebind the view. Replace it.
viewGroup.removeViewAt(i);
viewGroup.addView(newView, i);
}
} else {
int lastIndex = viewGroup.getChildCount() - 1;
viewGroup.removeViewAt(lastIndex);
}
}
}
@Override
public void onChanged() {
refresh();
}
@Override
public void onInvalidated() {
release();
}
private void release() {
if (!mReleased) {
mReleased = true;
mAdapter.unregisterDataSetObserver(this);
}
}
}
}