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 destroyHardwareLayers()89 void destroyHardwareLayers() { 90 // called when a page is no longer visible (triggered by loadAssociatedPages -> 91 // removeAllViewsOnPage) 92 setLayerType(LAYER_TYPE_NONE, null); 93 } 94 createHardwareLayers()95 void createHardwareLayers() { 96 // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) 97 setLayerType(LAYER_TYPE_HARDWARE, null); 98 } 99 100 @Override cancelLongPress()101 public void cancelLongPress() { 102 super.cancelLongPress(); 103 104 // Cancel long press for all children 105 final int count = getChildCount(); 106 for (int i = 0; i < count; i++) { 107 final View child = getChildAt(i); 108 child.cancelLongPress(); 109 } 110 } 111 addViewToCellLayout(View child, int index, int childId, PagedViewCellLayout.LayoutParams params)112 public boolean addViewToCellLayout(View child, int index, int childId, 113 PagedViewCellLayout.LayoutParams params) { 114 final PagedViewCellLayout.LayoutParams lp = params; 115 116 // Generate an id for each view, this assumes we have at most 256x256 cells 117 // per workspace screen 118 if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) && 119 lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) { 120 // If the horizontal or vertical span is set to -1, it is taken to 121 // mean that it spans the extent of the CellLayout 122 if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX; 123 if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; 124 125 child.setId(childId); 126 mChildren.addView(child, index, lp); 127 128 return true; 129 } 130 return false; 131 } 132 133 @Override removeAllViewsOnPage()134 public void removeAllViewsOnPage() { 135 mChildren.removeAllViews(); 136 destroyHardwareLayers(); 137 } 138 139 @Override removeViewOnPageAt(int index)140 public void removeViewOnPageAt(int index) { 141 mChildren.removeViewAt(index); 142 } 143 144 /** 145 * Clears all the key listeners for the individual icons. 146 */ resetChildrenOnKeyListeners()147 public void resetChildrenOnKeyListeners() { 148 int childCount = mChildren.getChildCount(); 149 for (int j = 0; j < childCount; ++j) { 150 mChildren.getChildAt(j).setOnKeyListener(null); 151 } 152 } 153 154 @Override getPageChildCount()155 public int getPageChildCount() { 156 return mChildren.getChildCount(); 157 } 158 getChildrenLayout()159 public PagedViewCellLayoutChildren getChildrenLayout() { 160 return mChildren; 161 } 162 163 @Override getChildOnPageAt(int i)164 public View getChildOnPageAt(int i) { 165 return mChildren.getChildAt(i); 166 } 167 168 @Override indexOfChildOnPage(View v)169 public int indexOfChildOnPage(View v) { 170 return mChildren.indexOfChild(v); 171 } 172 getCellCountX()173 public int getCellCountX() { 174 return mCellCountX; 175 } 176 getCellCountY()177 public int getCellCountY() { 178 return mCellCountY; 179 } 180 onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 - getPaddingLeft() - getPaddingRight(); 197 int vSpace = heightSpecSize - getPaddingTop() - getPaddingBottom(); 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 = getPaddingLeft() + getPaddingRight() + (mCellCountX * mCellWidth) + 214 ((mCellCountX - 1) * mWidthGap); 215 newHeight = getPaddingTop() + getPaddingBottom() + (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 - getPaddingLeft() - 225 getPaddingRight(), MeasureSpec.EXACTLY); 226 int childheightMeasureSpec = 227 MeasureSpec.makeMeasureSpec(newHeight - getPaddingTop() - 228 getPaddingBottom(), MeasureSpec.EXACTLY); 229 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 230 } 231 232 setMeasuredDimension(newWidth, newHeight); 233 } 234 getContentWidth()235 int getContentWidth() { 236 return getWidthBeforeFirstLayout() + getPaddingLeft() + getPaddingRight(); 237 } 238 getContentHeight()239 int getContentHeight() { 240 if (mCellCountY > 0) { 241 return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); 242 } 243 return 0; 244 } 245 getWidthBeforeFirstLayout()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 onLayout(boolean changed, int l, int t, int r, int b)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(getPaddingLeft(), getPaddingTop(), 259 r - l - getPaddingRight(), b - t - getPaddingBottom()); 260 } 261 } 262 263 @Override onTouchEvent(MotionEvent event)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 enableCenteredContent(boolean enabled)281 public void enableCenteredContent(boolean enabled) { 282 mChildren.enableCenteredContent(enabled); 283 } 284 285 @Override setChildrenDrawingCacheEnabled(boolean enabled)286 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 287 mChildren.setChildrenDrawingCacheEnabled(enabled); 288 } 289 setCellCount(int xCount, int yCount)290 public void setCellCount(int xCount, int yCount) { 291 mCellCountX = xCount; 292 mCellCountY = yCount; 293 requestLayout(); 294 } 295 setGap(int widthGap, int heightGap)296 public void setGap(int widthGap, int heightGap) { 297 mOriginalWidthGap = mWidthGap = widthGap; 298 mOriginalHeightGap = mHeightGap = heightGap; 299 mChildren.setGap(widthGap, heightGap); 300 } 301 getCellCountForDimensions(int width, int height)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 */ onDragChild(View child)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 */ estimateCellHSpan(int width)327 public int estimateCellHSpan(int width) { 328 // We don't show the next/previous pages any more, so we use the full width, minus the 329 // padding 330 int availWidth = width - (getPaddingLeft() + getPaddingRight()); 331 332 // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N 333 int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); 334 335 // We don't do anything fancy to determine if we squeeze another row in. 336 return n; 337 } 338 339 /** 340 * Estimates the number of cells that the specified height would take up. 341 */ estimateCellVSpan(int height)342 public int estimateCellVSpan(int height) { 343 // The space for a page is the height - top padding (current page) - bottom padding (current 344 // page) 345 int availHeight = height - (getPaddingTop() + getPaddingBottom()); 346 347 // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N 348 int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); 349 350 // We don't do anything fancy to determine if we squeeze another row in. 351 return n; 352 } 353 354 /** Returns an estimated center position of the cell at the specified index */ estimateCellPosition(int x, int y)355 public int[] estimateCellPosition(int x, int y) { 356 return new int[] { 357 getPaddingLeft() + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), 358 getPaddingTop() + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) 359 }; 360 } 361 calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY)362 public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { 363 mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); 364 mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); 365 requestLayout(); 366 } 367 368 /** 369 * Estimates the width that the number of hSpan cells will take up. 370 */ estimateCellWidth(int hSpan)371 public int estimateCellWidth(int hSpan) { 372 // TODO: we need to take widthGap into effect 373 return hSpan * mCellWidth; 374 } 375 376 /** 377 * Estimates the height that the number of vSpan cells will take up. 378 */ estimateCellHeight(int vSpan)379 public int estimateCellHeight(int vSpan) { 380 // TODO: we need to take heightGap into effect 381 return vSpan * mCellHeight; 382 } 383 384 @Override generateLayoutParams(AttributeSet attrs)385 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 386 return new PagedViewCellLayout.LayoutParams(getContext(), attrs); 387 } 388 389 @Override checkLayoutParams(ViewGroup.LayoutParams p)390 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 391 return p instanceof PagedViewCellLayout.LayoutParams; 392 } 393 394 @Override generateLayoutParams(ViewGroup.LayoutParams p)395 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 396 return new PagedViewCellLayout.LayoutParams(p); 397 } 398 399 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 400 /** 401 * Horizontal location of the item in the grid. 402 */ 403 @ViewDebug.ExportedProperty 404 public int cellX; 405 406 /** 407 * Vertical location of the item in the grid. 408 */ 409 @ViewDebug.ExportedProperty 410 public int cellY; 411 412 /** 413 * Number of cells spanned horizontally by the item. 414 */ 415 @ViewDebug.ExportedProperty 416 public int cellHSpan; 417 418 /** 419 * Number of cells spanned vertically by the item. 420 */ 421 @ViewDebug.ExportedProperty 422 public int cellVSpan; 423 424 /** 425 * Is this item currently being dragged 426 */ 427 public boolean isDragging; 428 429 // a data object that you can bind to this layout params 430 private Object mTag; 431 432 // X coordinate of the view in the layout. 433 @ViewDebug.ExportedProperty 434 int x; 435 // Y coordinate of the view in the layout. 436 @ViewDebug.ExportedProperty 437 int y; 438 LayoutParams()439 public LayoutParams() { 440 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 441 cellHSpan = 1; 442 cellVSpan = 1; 443 } 444 LayoutParams(Context c, AttributeSet attrs)445 public LayoutParams(Context c, AttributeSet attrs) { 446 super(c, attrs); 447 cellHSpan = 1; 448 cellVSpan = 1; 449 } 450 LayoutParams(ViewGroup.LayoutParams source)451 public LayoutParams(ViewGroup.LayoutParams source) { 452 super(source); 453 cellHSpan = 1; 454 cellVSpan = 1; 455 } 456 LayoutParams(LayoutParams source)457 public LayoutParams(LayoutParams source) { 458 super(source); 459 this.cellX = source.cellX; 460 this.cellY = source.cellY; 461 this.cellHSpan = source.cellHSpan; 462 this.cellVSpan = source.cellVSpan; 463 } 464 LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan)465 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 466 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 467 this.cellX = cellX; 468 this.cellY = cellY; 469 this.cellHSpan = cellHSpan; 470 this.cellVSpan = cellVSpan; 471 } 472 setup(int cellWidth, int cellHeight, int widthGap, int heightGap, int hStartPadding, int vStartPadding)473 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, 474 int hStartPadding, int vStartPadding) { 475 476 final int myCellHSpan = cellHSpan; 477 final int myCellVSpan = cellVSpan; 478 final int myCellX = cellX; 479 final int myCellY = cellY; 480 481 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 482 leftMargin - rightMargin; 483 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 484 topMargin - bottomMargin; 485 486 if (LauncherApplication.isScreenLarge()) { 487 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; 488 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; 489 } else { 490 x = myCellX * (cellWidth + widthGap) + leftMargin; 491 y = myCellY * (cellHeight + heightGap) + topMargin; 492 } 493 } 494 getTag()495 public Object getTag() { 496 return mTag; 497 } 498 setTag(Object tag)499 public void setTag(Object tag) { 500 mTag = tag; 501 } 502 toString()503 public String toString() { 504 return "(" + this.cellX + ", " + this.cellY + ", " + 505 this.cellHSpan + ", " + this.cellVSpan + ")"; 506 } 507 } 508 } 509 510 interface Page { getPageChildCount()511 public int getPageChildCount(); getChildOnPageAt(int i)512 public View getChildOnPageAt(int i); removeAllViewsOnPage()513 public void removeAllViewsOnPage(); removeViewOnPageAt(int i)514 public void removeViewOnPageAt(int i); indexOfChildOnPage(View v)515 public int indexOfChildOnPage(View v); 516 } 517