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             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