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 
PagedViewCellLayout(Context context)50     public PagedViewCellLayout(Context context) {
51         this(context, null);
52     }
53 
PagedViewCellLayout(Context context, AttributeSet attrs)54     public PagedViewCellLayout(Context context, AttributeSet attrs) {
55         this(context, attrs, 0);
56     }
57 
PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle)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 
getCellWidth()81     public int getCellWidth() {
82         return mCellWidth;
83     }
84 
getCellHeight()85     public int getCellHeight() {
86         return mCellHeight;
87     }
88 
89     @Override
cancelLongPress()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 
addViewToCellLayout(View child, int index, int childId, PagedViewCellLayout.LayoutParams params)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
removeAllViewsOnPage()123     public void removeAllViewsOnPage() {
124         mChildren.removeAllViews();
125         setLayerType(LAYER_TYPE_NONE, null);
126     }
127 
128     @Override
removeViewOnPageAt(int index)129     public void removeViewOnPageAt(int index) {
130         mChildren.removeViewAt(index);
131     }
132 
133     /**
134      * Clears all the key listeners for the individual icons.
135      */
resetChildrenOnKeyListeners()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
getPageChildCount()144     public int getPageChildCount() {
145         return mChildren.getChildCount();
146     }
147 
getChildrenLayout()148     public PagedViewCellLayoutChildren getChildrenLayout() {
149         return mChildren;
150     }
151 
152     @Override
getChildOnPageAt(int i)153     public View getChildOnPageAt(int i) {
154         return mChildren.getChildAt(i);
155     }
156 
157     @Override
indexOfChildOnPage(View v)158     public int indexOfChildOnPage(View v) {
159         return mChildren.indexOfChild(v);
160     }
161 
getCellCountX()162     public int getCellCountX() {
163         return mCellCountX;
164     }
165 
getCellCountY()166     public int getCellCountY() {
167         return mCellCountY;
168     }
169 
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
getContentWidth()224     int getContentWidth() {
225         return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight();
226     }
227 
getContentHeight()228     int getContentHeight() {
229         if (mCellCountY > 0) {
230             return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap);
231         }
232         return 0;
233     }
234 
getWidthBeforeFirstLayout()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
onLayout(boolean changed, int l, int t, int r, int b)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
onTouchEvent(MotionEvent event)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 
enableCenteredContent(boolean enabled)270     public void enableCenteredContent(boolean enabled) {
271         mChildren.enableCenteredContent(enabled);
272     }
273 
274     @Override
setChildrenDrawingCacheEnabled(boolean enabled)275     protected void setChildrenDrawingCacheEnabled(boolean enabled) {
276         mChildren.setChildrenDrawingCacheEnabled(enabled);
277     }
278 
setCellCount(int xCount, int yCount)279     public void setCellCount(int xCount, int yCount) {
280         mCellCountX = xCount;
281         mCellCountY = yCount;
282         requestLayout();
283     }
284 
setGap(int widthGap, int heightGap)285     public void setGap(int widthGap, int heightGap) {
286         mOriginalWidthGap = mWidthGap = widthGap;
287         mOriginalHeightGap = mHeightGap = heightGap;
288         mChildren.setGap(widthGap, heightGap);
289     }
290 
getCellCountForDimensions(int width, int height)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      */
onDragChild(View child)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      */
estimateCellHSpan(int width)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      */
estimateCellVSpan(int height)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 */
estimateCellPosition(int x, int y)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 
calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY)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      */
estimateCellWidth(int hSpan)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      */
estimateCellHeight(int vSpan)368     public int estimateCellHeight(int vSpan) {
369         // TODO: we need to take heightGap into effect
370         return vSpan * mCellHeight;
371     }
372 
373     @Override
generateLayoutParams(AttributeSet attrs)374     public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
375         return new PagedViewCellLayout.LayoutParams(getContext(), attrs);
376     }
377 
378     @Override
checkLayoutParams(ViewGroup.LayoutParams p)379     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
380         return p instanceof PagedViewCellLayout.LayoutParams;
381     }
382 
383     @Override
generateLayoutParams(ViewGroup.LayoutParams p)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 
LayoutParams()428         public LayoutParams() {
429             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
430             cellHSpan = 1;
431             cellVSpan = 1;
432         }
433 
LayoutParams(Context c, AttributeSet attrs)434         public LayoutParams(Context c, AttributeSet attrs) {
435             super(c, attrs);
436             cellHSpan = 1;
437             cellVSpan = 1;
438         }
439 
LayoutParams(ViewGroup.LayoutParams source)440         public LayoutParams(ViewGroup.LayoutParams source) {
441             super(source);
442             cellHSpan = 1;
443             cellVSpan = 1;
444         }
445 
LayoutParams(LayoutParams source)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 
LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan)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 
setup(int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding)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 
getTag()484         public Object getTag() {
485             return mTag;
486         }
487 
setTag(Object tag)488         public void setTag(Object tag) {
489             mTag = tag;
490         }
491 
toString()492         public String toString() {
493             return "(" + this.cellX + ", " + this.cellY + ", " +
494                 this.cellHSpan + ", " + this.cellVSpan + ")";
495         }
496     }
497 }
498 
499 interface Page {
getPageChildCount()500     public int getPageChildCount();
getChildOnPageAt(int i)501     public View getChildOnPageAt(int i);
removeAllViewsOnPage()502     public void removeAllViewsOnPage();
removeViewOnPageAt(int i)503     public void removeViewOnPageAt(int i);
indexOfChildOnPage(View v)504     public int indexOfChildOnPage(View v);
505 }
506