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