Home | History | Annotate | Download | only in qs
      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 
     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
     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
    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 
    146         public static void link(ViewGroup viewGroup, BaseAdapter adapter) {
    147             new ViewGroupAdapterBridge(viewGroup, adapter);
    148         }
    149 
    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 
    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
    193         public void onChanged() {
    194             refresh();
    195         }
    196 
    197         @Override
    198         public void onInvalidated() {
    199             release();
    200         }
    201 
    202         private void release() {
    203             if (!mReleased) {
    204                 mReleased = true;
    205                 mAdapter.unregisterDataSetObserver(this);
    206             }
    207         }
    208     }
    209 }
    210