Home | History | Annotate | Download | only in launcher2
      1 /*
      2  * Copyright (C) 2010 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.launcher2;
     18 
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.util.AttributeSet;
     22 import android.view.MotionEvent;
     23 import android.view.View;
     24 import android.view.ViewDebug;
     25 import android.view.ViewGroup;
     26 
     27 import com.android.launcher.R;
     28 
     29 /**
     30  * An abstraction of the original CellLayout which supports laying out items
     31  * which span multiple cells into a grid-like layout.  Also supports dimming
     32  * to give a preview of its contents.
     33  */
     34 public class PagedViewCellLayout extends ViewGroup implements Page {
     35     static final String TAG = "PagedViewCellLayout";
     36 
     37     private int mCellCountX;
     38     private int mCellCountY;
     39     private int mOriginalCellWidth;
     40     private int mOriginalCellHeight;
     41     private int mCellWidth;
     42     private int mCellHeight;
     43     private int mOriginalWidthGap;
     44     private int mOriginalHeightGap;
     45     private int mWidthGap;
     46     private int mHeightGap;
     47     private int mMaxGap;
     48     protected PagedViewCellLayoutChildren mChildren;
     49 
     50     public PagedViewCellLayout(Context context) {
     51         this(context, null);
     52     }
     53 
     54     public PagedViewCellLayout(Context context, AttributeSet attrs) {
     55         this(context, attrs, 0);
     56     }
     57 
     58     public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) {
     59         super(context, attrs, defStyle);
     60 
     61         setAlwaysDrawnWithCacheEnabled(false);
     62 
     63         // setup default cell parameters
     64         Resources resources = context.getResources();
     65         mOriginalCellWidth = mCellWidth =
     66             resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width);
     67         mOriginalCellHeight = mCellHeight =
     68             resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height);
     69         mCellCountX = LauncherModel.getCellCountX();
     70         mCellCountY = LauncherModel.getCellCountY();
     71         mOriginalWidthGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1;
     72         mMaxGap = resources.getDimensionPixelSize(R.dimen.apps_customize_max_gap);
     73 
     74         mChildren = new PagedViewCellLayoutChildren(context);
     75         mChildren.setCellDimensions(mCellWidth, mCellHeight);
     76         mChildren.setGap(mWidthGap, mHeightGap);
     77 
     78         addView(mChildren);
     79     }
     80 
     81     public int getCellWidth() {
     82         return mCellWidth;
     83     }
     84 
     85     public int getCellHeight() {
     86         return mCellHeight;
     87     }
     88 
     89     @Override
     90     public void cancelLongPress() {
     91         super.cancelLongPress();
     92 
     93         // Cancel long press for all children
     94         final int count = getChildCount();
     95         for (int i = 0; i < count; i++) {
     96             final View child = getChildAt(i);
     97             child.cancelLongPress();
     98         }
     99     }
    100 
    101     public boolean addViewToCellLayout(View child, int index, int childId,
    102             PagedViewCellLayout.LayoutParams params) {
    103         final PagedViewCellLayout.LayoutParams lp = params;
    104 
    105         // Generate an id for each view, this assumes we have at most 256x256 cells
    106         // per workspace screen
    107         if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) &&
    108                 lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) {
    109             // If the horizontal or vertical span is set to -1, it is taken to
    110             // mean that it spans the extent of the CellLayout
    111             if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX;
    112             if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY;
    113 
    114             child.setId(childId);
    115             mChildren.addView(child, index, lp);
    116 
    117             return true;
    118         }
    119         return false;
    120     }
    121 
    122     @Override
    123     public void removeAllViewsOnPage() {
    124         mChildren.removeAllViews();
    125         setLayerType(LAYER_TYPE_NONE, null);
    126     }
    127 
    128     @Override
    129     public void removeViewOnPageAt(int index) {
    130         mChildren.removeViewAt(index);
    131     }
    132 
    133     /**
    134      * Clears all the key listeners for the individual icons.
    135      */
    136     public void resetChildrenOnKeyListeners() {
    137         int childCount = mChildren.getChildCount();
    138         for (int j = 0; j < childCount; ++j) {
    139             mChildren.getChildAt(j).setOnKeyListener(null);
    140         }
    141     }
    142 
    143     @Override
    144     public int getPageChildCount() {
    145         return mChildren.getChildCount();
    146     }
    147 
    148     public PagedViewCellLayoutChildren getChildrenLayout() {
    149         return mChildren;
    150     }
    151 
    152     @Override
    153     public View getChildOnPageAt(int i) {
    154         return mChildren.getChildAt(i);
    155     }
    156 
    157     @Override
    158     public int indexOfChildOnPage(View v) {
    159         return mChildren.indexOfChild(v);
    160     }
    161 
    162     public int getCellCountX() {
    163         return mCellCountX;
    164     }
    165 
    166     public int getCellCountY() {
    167         return mCellCountY;
    168     }
    169 
    170     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    171         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    172         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    173 
    174         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    175         int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
    176 
    177         if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
    178             throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
    179         }
    180 
    181         int numWidthGaps = mCellCountX - 1;
    182         int numHeightGaps = mCellCountY - 1;
    183 
    184         if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) {
    185             int hSpace = widthSpecSize - getPaddingLeft() - getPaddingRight();
    186             int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom();
    187             int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth);
    188             int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight);
    189             mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0);
    190             mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0);
    191 
    192             mChildren.setGap(mWidthGap, mHeightGap);
    193         } else {
    194             mWidthGap = mOriginalWidthGap;
    195             mHeightGap = mOriginalHeightGap;
    196         }
    197 
    198         // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY
    199         int newWidth = widthSpecSize;
    200         int newHeight = heightSpecSize;
    201         if (widthSpecMode == MeasureSpec.AT_MOST) {
    202             newWidth = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) +
    203                 ((mCellCountX - 1) * mWidthGap);
    204             newHeight = getPaddingTop() + getPaddingBottom() + (mCellCountY * mCellHeight) +
    205                 ((mCellCountY - 1) * mHeightGap);
    206             setMeasuredDimension(newWidth, newHeight);
    207         }
    208 
    209         final int count = getChildCount();
    210         for (int i = 0; i < count; i++) {
    211             View child = getChildAt(i);
    212             int childWidthMeasureSpec =
    213                 MeasureSpec.makeMeasureSpec(newWidth - getPaddingLeft() -
    214                         getPaddingRight(), MeasureSpec.EXACTLY);
    215             int childheightMeasureSpec =
    216                 MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() -
    217                         getPaddingBottom(), MeasureSpec.EXACTLY);
    218             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
    219         }
    220 
    221         setMeasuredDimension(newWidth, newHeight);
    222     }
    223 
    224     int getContentWidth() {
    225         return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
    226     }
    227 
    228     int getContentHeight() {
    229         if (mCellCountY > 0) {
    230             return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
    231         }
    232         return 0;
    233     }
    234 
    235     int getWidthBeforeFirstLayout() {
    236         if (mCellCountX > 0) {
    237             return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap);
    238         }
    239         return 0;
    240     }
    241 
    242     @Override
    243     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    244         int count = getChildCount();
    245         for (int i = 0; i < count; i++) {
    246             View child = getChildAt(i);
    247             child.layout(getPaddingLeft(), getPaddingTop(),
    248                 r - l - getPaddingRight(), b - t - getPaddingBottom());
    249         }
    250     }
    251 
    252     @Override
    253     public boolean onTouchEvent(MotionEvent event) {
    254         boolean result = super.onTouchEvent(event);
    255         int count = getPageChildCount();
    256         if (count > 0) {
    257             // We only intercept the touch if we are tapping in empty space after the final row
    258             View child = getChildOnPageAt(count - 1);
    259             int bottom = child.getBottom();
    260             int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX());
    261             if (numRows < getCellCountY()) {
    262                 // Add a little bit of buffer if there is room for another row
    263                 bottom += mCellHeight / 2;
    264             }
    265             result = result || (event.getY() < bottom);
    266         }
    267         return result;
    268     }
    269 
    270     public void enableCenteredContent(boolean enabled) {
    271         mChildren.enableCenteredContent(enabled);
    272     }
    273 
    274     @Override
    275     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
    276         mChildren.setChildrenDrawingCacheEnabled(enabled);
    277     }
    278 
    279     public void setCellCount(int xCount, int yCount) {
    280         mCellCountX = xCount;
    281         mCellCountY = yCount;
    282         requestLayout();
    283     }
    284 
    285     public void setGap(int widthGap, int heightGap) {
    286         mOriginalWidthGap = mWidthGap = widthGap;
    287         mOriginalHeightGap = mHeightGap = heightGap;
    288         mChildren.setGap(widthGap, heightGap);
    289     }
    290 
    291     public int[] getCellCountForDimensions(int width, int height) {
    292         // Always assume we're working with the smallest span to make sure we
    293         // reserve enough space in both orientations
    294         int smallerSize = Math.min(mCellWidth, mCellHeight);
    295 
    296         // Always round up to next largest cell
    297         int spanX = (width + smallerSize) / smallerSize;
    298         int spanY = (height + smallerSize) / smallerSize;
    299 
    300         return new int[] { spanX, spanY };
    301     }
    302 
    303     /**
    304      * Start dragging the specified child
    305      *
    306      * @param child The child that is being dragged
    307      */
    308     void onDragChild(View child) {
    309         PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams();
    310         lp.isDragging = true;
    311     }
    312 
    313     /**
    314      * Estimates the number of cells that the specified width would take up.
    315      */
    316     public int estimateCellHSpan(int width) {
    317         // We don't show the next/previous pages any more, so we use the full width, minus the
    318         // padding
    319         int availWidth = width - (getPaddingLeft() + getPaddingRight());
    320 
    321         // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N
    322         int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap));
    323 
    324         // We don't do anything fancy to determine if we squeeze another row in.
    325         return n;
    326     }
    327 
    328     /**
    329      * Estimates the number of cells that the specified height would take up.
    330      */
    331     public int estimateCellVSpan(int height) {
    332         // The space for a page is the height - top padding (current page) - bottom padding (current
    333         // page)
    334         int availHeight = height - (getPaddingTop() + getPaddingBottom());
    335 
    336         // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N
    337         int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap));
    338 
    339         // We don't do anything fancy to determine if we squeeze another row in.
    340         return n;
    341     }
    342 
    343     /** Returns an estimated center position of the cell at the specified index */
    344     public int[] estimateCellPosition(int x, int y) {
    345         return new int[] {
    346                 getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2),
    347                 getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2)
    348         };
    349     }
    350 
    351     public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) {
    352         mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width));
    353         mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height));
    354         requestLayout();
    355     }
    356 
    357     /**
    358      * Estimates the width that the number of hSpan cells will take up.
    359      */
    360     public int estimateCellWidth(int hSpan) {
    361         // TODO: we need to take widthGap into effect
    362         return hSpan * mCellWidth;
    363     }
    364 
    365     /**
    366      * Estimates the height that the number of vSpan cells will take up.
    367      */
    368     public int estimateCellHeight(int vSpan) {
    369         // TODO: we need to take heightGap into effect
    370         return vSpan * mCellHeight;
    371     }
    372 
    373     @Override
    374     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
    375         return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
    376     }
    377 
    378     @Override
    379     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    380         return p instanceof PagedViewCellLayout.LayoutParams;
    381     }
    382 
    383     @Override
    384     protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    385         return new PagedViewCellLayout.LayoutParams(p);
    386     }
    387 
    388     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
    389         /**
    390          * Horizontal location of the item in the grid.
    391          */
    392         @ViewDebug.ExportedProperty
    393         public int cellX;
    394 
    395         /**
    396          * Vertical location of the item in the grid.
    397          */
    398         @ViewDebug.ExportedProperty
    399         public int cellY;
    400 
    401         /**
    402          * Number of cells spanned horizontally by the item.
    403          */
    404         @ViewDebug.ExportedProperty
    405         public int cellHSpan;
    406 
    407         /**
    408          * Number of cells spanned vertically by the item.
    409          */
    410         @ViewDebug.ExportedProperty
    411         public int cellVSpan;
    412 
    413         /**
    414          * Is this item currently being dragged
    415          */
    416         public boolean isDragging;
    417 
    418         // a data object that you can bind to this layout params
    419         private Object mTag;
    420 
    421         // X coordinate of the view in the layout.
    422         @ViewDebug.ExportedProperty
    423         int x;
    424         // Y coordinate of the view in the layout.
    425         @ViewDebug.ExportedProperty
    426         int y;
    427 
    428         public LayoutParams() {
    429             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    430             cellHSpan = 1;
    431             cellVSpan = 1;
    432         }
    433 
    434         public LayoutParams(Context c, AttributeSet attrs) {
    435             super(c, attrs);
    436             cellHSpan = 1;
    437             cellVSpan = 1;
    438         }
    439 
    440         public LayoutParams(ViewGroup.LayoutParams source) {
    441             super(source);
    442             cellHSpan = 1;
    443             cellVSpan = 1;
    444         }
    445 
    446         public LayoutParams(LayoutParams source) {
    447             super(source);
    448             this.cellX = source.cellX;
    449             this.cellY = source.cellY;
    450             this.cellHSpan = source.cellHSpan;
    451             this.cellVSpan = source.cellVSpan;
    452         }
    453 
    454         public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) {
    455             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    456             this.cellX = cellX;
    457             this.cellY = cellY;
    458             this.cellHSpan = cellHSpan;
    459             this.cellVSpan = cellVSpan;
    460         }
    461 
    462         public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap,
    463                 int hStartPadding, int vStartPadding) {
    464 
    465             final int myCellHSpan = cellHSpan;
    466             final int myCellVSpan = cellVSpan;
    467             final int myCellX = cellX;
    468             final int myCellY = cellY;
    469 
    470             width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) -
    471                     leftMargin - rightMargin;
    472             height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) -
    473                     topMargin - bottomMargin;
    474 
    475             if (LauncherApplication.isScreenLarge()) {
    476                 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin;
    477                 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin;
    478             } else {
    479                 x = myCellX * (cellWidth + widthGap) + leftMargin;
    480                 y = myCellY * (cellHeight + heightGap) + topMargin;
    481             }
    482         }
    483 
    484         public Object getTag() {
    485             return mTag;
    486         }
    487 
    488         public void setTag(Object tag) {
    489             mTag = tag;
    490         }
    491 
    492         public String toString() {
    493             return "(" + this.cellX + ", " + this.cellY + ", " +
    494                 this.cellHSpan + ", " + this.cellVSpan + ")";
    495         }
    496     }
    497 }
    498 
    499 interface Page {
    500     public int getPageChildCount();
    501     public View getChildOnPageAt(int i);
    502     public void removeAllViewsOnPage();
    503     public void removeViewOnPageAt(int i);
    504     public int indexOfChildOnPage(View v);
    505 }
    506