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