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