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
17package com.android.systemui.qs;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.database.DataSetObserver;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.BaseAdapter;
26
27import com.android.systemui.R;
28
29import 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 */
36public 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, resolveSizeAndState(totalHeight, heightMeasureSpec, 0));
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