PagedViewCellLayout.java revision 7d7541e7b48fdc114c24b3b0aa75e70d7228041e
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
17package com.android.launcher2;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.util.AttributeSet;
22import android.util.Log;
23import android.view.MotionEvent;
24import android.view.View;
25import android.view.ViewDebug;
26import android.view.ViewGroup;
27
28import 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 */
35public 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 vSpan 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
511interface 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