/* * 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.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 mCellWidth; private int mCellHeight; private int mWidthGap; private int mHeightGap; private float mPeekWidth; protected PagedViewCellLayoutChildren mChildren; private PagedViewCellLayoutChildren mHolographicChildren; private boolean mAllowHardwareLayerCreation = false; private boolean mCreateHardwareLayersIfAllowed = false; 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(); mCellWidth = resources.getDimensionPixelSize(R.dimen.apps_customize_cell_width); mCellHeight = resources.getDimensionPixelSize(R.dimen.apps_customize_cell_height); mPeekWidth = resources.getDimensionPixelSize(R.dimen.apps_customize_peek_width); mCellCountX = LauncherModel.getCellCountX(); mCellCountY = LauncherModel.getCellCountY(); mWidthGap = mHeightGap = -1; mChildren = new PagedViewCellLayoutChildren(context); mChildren.setCellDimensions(mCellWidth, mCellHeight); mChildren.setGap(mWidthGap, mHeightGap); addView(mChildren); mHolographicChildren = new PagedViewCellLayoutChildren(context); mHolographicChildren.setAlpha(0f); mHolographicChildren.setCellDimensions(mCellWidth, mCellHeight); mHolographicChildren.setGap(mWidthGap, mHeightGap); addView(mHolographicChildren); } public int getCellWidth() { return mCellWidth; } public int getCellHeight() { return mCellHeight; } public void allowHardwareLayerCreation() { // This is called after the first time we launch into All Apps. Before that point, // there's no need for hardware layers here since there's a hardware layer set on the // parent, AllAppsTabbed, during the AllApps transition -- creating hardware layers here // before the animation is done slows down the animation if (!mAllowHardwareLayerCreation) { mAllowHardwareLayerCreation = true; if (mCreateHardwareLayersIfAllowed) { createHardwareLayers(); } } } @Override public void setAlpha(float alpha) { mChildren.setAlpha(alpha); mHolographicChildren.setAlpha(1.0f - alpha); } void destroyHardwareLayers() { // called when a page is no longer visible (triggered by loadAssociatedPages -> // removeAllViewsOnPage) mCreateHardwareLayersIfAllowed = false; if (mAllowHardwareLayerCreation) { mChildren.destroyHardwareLayer(); mHolographicChildren.destroyHardwareLayer(); } } void createHardwareLayers() { // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) mCreateHardwareLayersIfAllowed = true; if (mAllowHardwareLayerCreation) { mChildren.createHardwareLayer(); mHolographicChildren.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(); } } /** Syncs the holographic icon views to the child icon views */ public void reloadHolographicIcons(boolean createHolographicOutlines) { if (createHolographicOutlines) { mChildren.loadHolographicOutlines(); } else { mChildren.clearHolographicOutlines(); } } 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; if (mAllowHardwareLayerCreation) { pagedViewIcon.disableCache(); } mHolographicChildren.addView(pagedViewIcon.getHolographicOutlineView(), index, lp); } return true; } return false; } @Override public void removeAllViewsOnPage() { mChildren.removeAllViews(); mHolographicChildren.removeAllViews(); destroyHardwareLayers(); } @Override public void removeViewOnPageAt(int index) { mChildren.removeViewAt(index); mHolographicChildren.removeViewAt(index); } @Override public int getPageChildCount() { return mChildren.getChildCount(); } @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"); } final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; int numWidthGaps = mCellCountX - 1; int numHeightGaps = mCellCountY - 1; int vSpaceLeft = heightSpecSize - mPaddingTop - mPaddingBottom - (cellHeight * mCellCountY); int heightGap = (numHeightGaps <= 0) ? 0 : (vSpaceLeft / numHeightGaps); int hSpaceLeft = widthSpecSize - mPaddingLeft - mPaddingRight - (cellWidth * mCellCountX); int widthGap = (numWidthGaps <= 0) ? 0 : (hSpaceLeft / numWidthGaps); // center it around the min gaps int minGap = Math.min(widthGap, heightGap); /* if (minGap < heightGap) { // vertical space has shrunken, so change padding accordingly paddingTop += ((heightGap - minGap) * (mCellCountY - 1)) / 2; } else if (minGap < widthGap) { // horizontal space has shrunken, so change padding accordingly paddingLeft += ((widthGap - minGap) * (mCellCountX - 1)) / 2; } */ if (mWidthGap > -1 && mHeightGap > -1) { widthGap = mWidthGap; heightGap = mHeightGap; } else { widthGap = heightGap = minGap; } int newWidth = (mCellCountX * cellWidth) + ((mCellCountX - 1) * widthGap); int newHeight = (mCellCountY * cellHeight) + ((mCellCountY - 1) * heightGap); final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY); int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY); child.measure(childWidthMeasureSpec, childheightMeasureSpec); } setMeasuredDimension(newWidth + mPaddingLeft + mPaddingRight, newHeight + mPaddingTop + mPaddingBottom); } 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); mHolographicChildren.enableCenteredContent(enabled); } @Override protected void setChildrenDrawingCacheEnabled(boolean enabled) { mChildren.setChildrenDrawingCacheEnabled(enabled); mHolographicChildren.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); mHolographicChildren.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) - (2 * mPeekWidth)); // 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); }