/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher2; import android.content.Context; import android.content.res.Resources; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewDebug; import android.view.ViewGroup; import com.android.launcher.R; /** * An abstraction of the original CellLayout which supports laying out items * which span multiple cells into a grid-like layout. Also supports dimming * to give a preview of its contents. */ public class PagedViewCellLayout extends ViewGroup implements Page { static final String TAG = "PagedViewCellLayout"; private int mCellCountX; private int mCellCountY; private int mOriginalCellWidth; private int mOriginalCellHeight; private int mCellWidth; private int mCellHeight; private int mOriginalWidthGap; private int mOriginalHeightGap; private int mWidthGap; private int mHeightGap; private int mMaxGap; protected PagedViewCellLayoutChildren mChildren; public PagedViewCellLayout(Context context) { this(context, null); } public PagedViewCellLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public PagedViewCellLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setAlwaysDrawnWithCacheEnabled(false); // setup default cell parameters Resources resources = context.getResources(); mOriginalCellWidth = mCellWidth = resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width); mOriginalCellHeight = mCellHeight = resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height); mCellCountX = LauncherModel.getCellCountX(); mCellCountY = LauncherModel.getCellCountY(); mOriginalHeightGap = mOriginalHeightGap = mWidthGap = mHeightGap = -1; mMaxGap = resources.getDimensionPixelSize(R.dimen.apps_customize_max_gap); mChildren = new PagedViewCellLayoutChildren(context); mChildren.setCellDimensions(mCellWidth, mCellHeight); mChildren.setGap(mWidthGap, mHeightGap); addView(mChildren); } public int getCellWidth() { return mCellWidth; } public int getCellHeight() { return mCellHeight; } @Override public void setAlpha(float alpha) { mChildren.setAlpha(alpha); } void destroyHardwareLayers() { // called when a page is no longer visible (triggered by loadAssociatedPages -> // removeAllViewsOnPage) mChildren.destroyHardwareLayer(); } void createHardwareLayers() { // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) mChildren.createHardwareLayer(); } @Override public void cancelLongPress() { super.cancelLongPress(); // Cancel long press for all children final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); child.cancelLongPress(); } } public boolean addViewToCellLayout(View child, int index, int childId, PagedViewCellLayout.LayoutParams params) { final PagedViewCellLayout.LayoutParams lp = params; // Generate an id for each view, this assumes we have at most 256x256 cells // per workspace screen if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) && lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) { // If the horizontal or vertical span is set to -1, it is taken to // mean that it spans the extent of the CellLayout if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX; if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; child.setId(childId); mChildren.addView(child, index, lp); if (child instanceof PagedViewIcon) { PagedViewIcon pagedViewIcon = (PagedViewIcon) child; pagedViewIcon.disableCache(); } return true; } return false; } @Override public void removeAllViewsOnPage() { mChildren.removeAllViews(); destroyHardwareLayers(); } @Override public void removeViewOnPageAt(int index) { mChildren.removeViewAt(index); } @Override public int getPageChildCount() { return mChildren.getChildCount(); } public PagedViewCellLayoutChildren getChildrenLayout() { return mChildren; } @Override public View getChildOnPageAt(int i) { return mChildren.getChildAt(i); } @Override public int indexOfChildOnPage(View v) { return mChildren.indexOfChild(v); } public int getCellCountX() { return mCellCountX; } public int getCellCountY() { return mCellCountY; } protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); } int numWidthGaps = mCellCountX - 1; int numHeightGaps = mCellCountY - 1; if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight; int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom; int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); mChildren.setGap(mWidthGap, mHeightGap); } else { mWidthGap = mOriginalWidthGap; mHeightGap = mOriginalHeightGap; } // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY int newWidth = widthSpecSize; int newHeight = heightSpecSize; if (widthSpecMode == MeasureSpec.AT_MOST) { newWidth = mPaddingLeft + mPaddingRight + (mCellCountX * mCellWidth) + ((mCellCountX - 1) * mWidthGap); newHeight = mPaddingTop + mPaddingBottom + (mCellCountY * mCellHeight) + ((mCellCountY - 1) * mHeightGap); setMeasuredDimension(newWidth, newHeight); } final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft - mPaddingRight, MeasureSpec.EXACTLY); int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop - mPaddingBottom, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childheightMeasureSpec); } setMeasuredDimension(newWidth, newHeight); } int getContentWidth() { return getWidthBeforeFirstLayout() + mPaddingLeft + mPaddingRight; } int getContentHeight() { if (mCellCountY > 0) { return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); } return 0; } int getWidthBeforeFirstLayout() { if (mCellCountX > 0) { return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); } return 0; } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); child.layout(mPaddingLeft, mPaddingTop, r - l - mPaddingRight, b - t - mPaddingBottom); } } @Override public boolean onTouchEvent(MotionEvent event) { boolean result = super.onTouchEvent(event); int count = getPageChildCount(); if (count > 0) { // We only intercept the touch if we are tapping in empty space after the final row View child = getChildOnPageAt(count - 1); int bottom = child.getBottom(); int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); if (numRows < getCellCountY()) { // Add a little bit of buffer if there is room for another row bottom += mCellHeight / 2; } result = result || (event.getY() < bottom); } return result; } public void enableCenteredContent(boolean enabled) { mChildren.enableCenteredContent(enabled); } @Override protected void setChildrenDrawingCacheEnabled(boolean enabled) { mChildren.setChildrenDrawingCacheEnabled(enabled); } public void setCellCount(int xCount, int yCount) { mCellCountX = xCount; mCellCountY = yCount; requestLayout(); } public void setGap(int widthGap, int heightGap) { mWidthGap = widthGap; mHeightGap = heightGap; mChildren.setGap(widthGap, heightGap); } public int[] getCellCountForDimensions(int width, int height) { // Always assume we're working with the smallest span to make sure we // reserve enough space in both orientations int smallerSize = Math.min(mCellWidth, mCellHeight); // Always round up to next largest cell int spanX = (width + smallerSize) / smallerSize; int spanY = (height + smallerSize) / smallerSize; return new int[] { spanX, spanY }; } /** * Start dragging the specified child * * @param child The child that is being dragged */ void onDragChild(View child) { PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); lp.isDragging = true; } /** * Estimates the number of cells that the specified width would take up. */ public int estimateCellHSpan(int width) { // The space for a page assuming that we want to show half of a column of the previous and // next pages is the width - left padding (current & next page) - right padding (previous & // current page) - half cell width (for previous and next pages) int availWidth = (int) (width - (2 * mPaddingLeft + 2 * mPaddingRight)); // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); // We don't do anything fancy to determine if we squeeze another row in. return n; } /** * Estimates the number of cells that the specified height would take up. */ public int estimateCellVSpan(int height) { // The space for a page is the height - top padding (current page) - bottom padding (current // page) int availHeight = height - (mPaddingTop + mPaddingBottom); // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); // We don't do anything fancy to determine if we squeeze another row in. return n; } public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); requestLayout(); } /** * Estimates the width that the number of vSpan cells will take up. */ public int estimateCellWidth(int hSpan) { // TODO: we need to take widthGap into effect return hSpan * mCellWidth; } /** * Estimates the height that the number of vSpan cells will take up. */ public int estimateCellHeight(int vSpan) { // TODO: we need to take heightGap into effect return vSpan * mCellHeight; } @Override public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { return new PagedViewCellLayout.LayoutParams(getContext(), attrs); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof PagedViewCellLayout.LayoutParams; } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new PagedViewCellLayout.LayoutParams(p); } public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * Horizontal location of the item in the grid. */ @ViewDebug.ExportedProperty public int cellX; /** * Vertical location of the item in the grid. */ @ViewDebug.ExportedProperty public int cellY; /** * Number of cells spanned horizontally by the item. */ @ViewDebug.ExportedProperty public int cellHSpan; /** * Number of cells spanned vertically by the item. */ @ViewDebug.ExportedProperty public int cellVSpan; /** * Is this item currently being dragged */ public boolean isDragging; // a data object that you can bind to this layout params private Object mTag; // X coordinate of the view in the layout. @ViewDebug.ExportedProperty int x; // Y coordinate of the view in the layout. @ViewDebug.ExportedProperty int y; public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); cellHSpan = 1; cellVSpan = 1; } public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); cellHSpan = 1; cellVSpan = 1; } public LayoutParams(ViewGroup.LayoutParams source) { super(source); cellHSpan = 1; cellVSpan = 1; } public LayoutParams(LayoutParams source) { super(source); this.cellX = source.cellX; this.cellY = source.cellY; this.cellHSpan = source.cellHSpan; this.cellVSpan = source.cellVSpan; } public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); this.cellX = cellX; this.cellY = cellY; this.cellHSpan = cellHSpan; this.cellVSpan = cellVSpan; } public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding) { final int myCellHSpan = cellHSpan; final int myCellVSpan = cellVSpan; final int myCellX = cellX; final int myCellY = cellY; width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - leftMargin - rightMargin; height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - topMargin - bottomMargin; if (LauncherApplication.isScreenLarge()) { x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; } else { x = myCellX * (cellWidth + widthGap) + leftMargin; y = myCellY * (cellHeight + heightGap) + topMargin; } } public Object getTag() { return mTag; } public void setTag(Object tag) { mTag = tag; } public String toString() { return "(" + this.cellX + ", " + this.cellY + ", " + this.cellHSpan + ", " + this.cellVSpan + ")"; } } } interface Page { public int getPageChildCount(); public View getChildOnPageAt(int i); public void removeAllViewsOnPage(); public void removeViewOnPageAt(int i); public int indexOfChildOnPage(View v); }