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 50 public PagedViewCellLayout(Context context) { 51 this(context, null); 52 } 53 54 public PagedViewCellLayout(Context context, AttributeSet attrs) { 55 this(context, attrs, 0); 56 } 57 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 81 public int getCellWidth() { 82 return mCellWidth; 83 } 84 85 public int getCellHeight() { 86 return mCellHeight; 87 } 88 89 @Override 90 public void setAlpha(float alpha) { 91 mChildren.setAlpha(alpha); 92 } 93 94 void destroyHardwareLayers() { 95 // called when a page is no longer visible (triggered by loadAssociatedPages -> 96 // removeAllViewsOnPage) 97 mChildren.destroyHardwareLayer(); 98 } 99 100 void createHardwareLayers() { 101 // called when a page is visible (triggered by loadAssociatedPages -> syncPageItems) 102 mChildren.createHardwareLayer(); 103 } 104 105 @Override 106 public void cancelLongPress() { 107 super.cancelLongPress(); 108 109 // Cancel long press for all children 110 final int count = getChildCount(); 111 for (int i = 0; i < count; i++) { 112 final View child = getChildAt(i); 113 child.cancelLongPress(); 114 } 115 } 116 117 public boolean addViewToCellLayout(View child, int index, int childId, 118 PagedViewCellLayout.LayoutParams params) { 119 final PagedViewCellLayout.LayoutParams lp = params; 120 121 // Generate an id for each view, this assumes we have at most 256x256 cells 122 // per workspace screen 123 if (lp.cellX >= 0 && lp.cellX <= (mCellCountX - 1) && 124 lp.cellY >= 0 && (lp.cellY <= mCellCountY - 1)) { 125 // If the horizontal or vertical span is set to -1, it is taken to 126 // mean that it spans the extent of the CellLayout 127 if (lp.cellHSpan < 0) lp.cellHSpan = mCellCountX; 128 if (lp.cellVSpan < 0) lp.cellVSpan = mCellCountY; 129 130 child.setId(childId); 131 mChildren.addView(child, index, lp); 132 133 return true; 134 } 135 return false; 136 } 137 138 @Override 139 public void removeAllViewsOnPage() { 140 mChildren.removeAllViews(); 141 destroyHardwareLayers(); 142 } 143 144 @Override 145 public void removeViewOnPageAt(int index) { 146 mChildren.removeViewAt(index); 147 } 148 149 /** 150 * Clears all the key listeners for the individual icons. 151 */ 152 public void resetChildrenOnKeyListeners() { 153 int childCount = mChildren.getChildCount(); 154 for (int j = 0; j < childCount; ++j) { 155 mChildren.getChildAt(j).setOnKeyListener(null); 156 } 157 } 158 159 @Override 160 public int getPageChildCount() { 161 return mChildren.getChildCount(); 162 } 163 164 public PagedViewCellLayoutChildren getChildrenLayout() { 165 return mChildren; 166 } 167 168 @Override 169 public View getChildOnPageAt(int i) { 170 return mChildren.getChildAt(i); 171 } 172 173 @Override 174 public int indexOfChildOnPage(View v) { 175 return mChildren.indexOfChild(v); 176 } 177 178 public int getCellCountX() { 179 return mCellCountX; 180 } 181 182 public int getCellCountY() { 183 return mCellCountY; 184 } 185 186 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 187 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 188 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 189 190 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 191 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 192 193 if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) { 194 throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions"); 195 } 196 197 int numWidthGaps = mCellCountX - 1; 198 int numHeightGaps = mCellCountY - 1; 199 200 if (mOriginalWidthGap < 0 || mOriginalHeightGap < 0) { 201 int hSpace = widthSpecSize - mPaddingLeft - mPaddingRight; 202 int vSpace = heightSpecSize - mPaddingTop - mPaddingBottom; 203 int hFreeSpace = hSpace - (mCellCountX * mOriginalCellWidth); 204 int vFreeSpace = vSpace - (mCellCountY * mOriginalCellHeight); 205 mWidthGap = Math.min(mMaxGap, numWidthGaps > 0 ? (hFreeSpace / numWidthGaps) : 0); 206 mHeightGap = Math.min(mMaxGap,numHeightGaps > 0 ? (vFreeSpace / numHeightGaps) : 0); 207 208 mChildren.setGap(mWidthGap, mHeightGap); 209 } else { 210 mWidthGap = mOriginalWidthGap; 211 mHeightGap = mOriginalHeightGap; 212 } 213 214 // Initial values correspond to widthSpecMode == MeasureSpec.EXACTLY 215 int newWidth = widthSpecSize; 216 int newHeight = heightSpecSize; 217 if (widthSpecMode == MeasureSpec.AT_MOST) { 218 newWidth = mPaddingLeft + mPaddingRight + (mCellCountX * mCellWidth) + 219 ((mCellCountX - 1) * mWidthGap); 220 newHeight = mPaddingTop + mPaddingBottom + (mCellCountY * mCellHeight) + 221 ((mCellCountY - 1) * mHeightGap); 222 setMeasuredDimension(newWidth, newHeight); 223 } 224 225 final int count = getChildCount(); 226 for (int i = 0; i < count; i++) { 227 View child = getChildAt(i); 228 int childWidthMeasureSpec = 229 MeasureSpec.makeMeasureSpec(newWidth - mPaddingLeft - 230 mPaddingRight, MeasureSpec.EXACTLY); 231 int childheightMeasureSpec = 232 MeasureSpec.makeMeasureSpec(newHeight - mPaddingTop - 233 mPaddingBottom, MeasureSpec.EXACTLY); 234 child.measure(childWidthMeasureSpec, childheightMeasureSpec); 235 } 236 237 setMeasuredDimension(newWidth, newHeight); 238 } 239 240 int getContentWidth() { 241 return getWidthBeforeFirstLayout() + mPaddingLeft + mPaddingRight; 242 } 243 244 int getContentHeight() { 245 if (mCellCountY > 0) { 246 return mCellCountY * mCellHeight + (mCellCountY - 1) * Math.max(0, mHeightGap); 247 } 248 return 0; 249 } 250 251 int getWidthBeforeFirstLayout() { 252 if (mCellCountX > 0) { 253 return mCellCountX * mCellWidth + (mCellCountX - 1) * Math.max(0, mWidthGap); 254 } 255 return 0; 256 } 257 258 @Override 259 protected void onLayout(boolean changed, int l, int t, int r, int b) { 260 int count = getChildCount(); 261 for (int i = 0; i < count; i++) { 262 View child = getChildAt(i); 263 child.layout(mPaddingLeft, mPaddingTop, 264 r - l - mPaddingRight, b - t - mPaddingBottom); 265 } 266 } 267 268 @Override 269 public boolean onTouchEvent(MotionEvent event) { 270 boolean result = super.onTouchEvent(event); 271 int count = getPageChildCount(); 272 if (count > 0) { 273 // We only intercept the touch if we are tapping in empty space after the final row 274 View child = getChildOnPageAt(count - 1); 275 int bottom = child.getBottom(); 276 int numRows = (int) Math.ceil((float) getPageChildCount() / getCellCountX()); 277 if (numRows < getCellCountY()) { 278 // Add a little bit of buffer if there is room for another row 279 bottom += mCellHeight / 2; 280 } 281 result = result || (event.getY() < bottom); 282 } 283 return result; 284 } 285 286 public void enableCenteredContent(boolean enabled) { 287 mChildren.enableCenteredContent(enabled); 288 } 289 290 @Override 291 protected void setChildrenDrawingCacheEnabled(boolean enabled) { 292 mChildren.setChildrenDrawingCacheEnabled(enabled); 293 } 294 295 public void setCellCount(int xCount, int yCount) { 296 mCellCountX = xCount; 297 mCellCountY = yCount; 298 requestLayout(); 299 } 300 301 public void setGap(int widthGap, int heightGap) { 302 mOriginalWidthGap = mWidthGap = widthGap; 303 mOriginalHeightGap = mHeightGap = heightGap; 304 mChildren.setGap(widthGap, heightGap); 305 } 306 307 public int[] getCellCountForDimensions(int width, int height) { 308 // Always assume we're working with the smallest span to make sure we 309 // reserve enough space in both orientations 310 int smallerSize = Math.min(mCellWidth, mCellHeight); 311 312 // Always round up to next largest cell 313 int spanX = (width + smallerSize) / smallerSize; 314 int spanY = (height + smallerSize) / smallerSize; 315 316 return new int[] { spanX, spanY }; 317 } 318 319 /** 320 * Start dragging the specified child 321 * 322 * @param child The child that is being dragged 323 */ 324 void onDragChild(View child) { 325 PagedViewCellLayout.LayoutParams lp = (PagedViewCellLayout.LayoutParams) child.getLayoutParams(); 326 lp.isDragging = true; 327 } 328 329 /** 330 * Estimates the number of cells that the specified width would take up. 331 */ 332 public int estimateCellHSpan(int width) { 333 // We don't show the next/previous pages any more, so we use the full width, minus the 334 // padding 335 int availWidth = width - (mPaddingLeft + mPaddingRight); 336 337 // We know that we have to fit N cells with N-1 width gaps, so we just juggle to solve for N 338 int n = Math.max(1, (availWidth + mWidthGap) / (mCellWidth + mWidthGap)); 339 340 // We don't do anything fancy to determine if we squeeze another row in. 341 return n; 342 } 343 344 /** 345 * Estimates the number of cells that the specified height would take up. 346 */ 347 public int estimateCellVSpan(int height) { 348 // The space for a page is the height - top padding (current page) - bottom padding (current 349 // page) 350 int availHeight = height - (mPaddingTop + mPaddingBottom); 351 352 // We know that we have to fit N cells with N-1 height gaps, so we juggle to solve for N 353 int n = Math.max(1, (availHeight + mHeightGap) / (mCellHeight + mHeightGap)); 354 355 // We don't do anything fancy to determine if we squeeze another row in. 356 return n; 357 } 358 359 /** Returns an estimated center position of the cell at the specified index */ 360 public int[] estimateCellPosition(int x, int y) { 361 return new int[] { 362 mPaddingLeft + (x * mCellWidth) + (x * mWidthGap) + (mCellWidth / 2), 363 mPaddingTop + (y * mCellHeight) + (y * mHeightGap) + (mCellHeight / 2) 364 }; 365 } 366 367 public void calculateCellCount(int width, int height, int maxCellCountX, int maxCellCountY) { 368 mCellCountX = Math.min(maxCellCountX, estimateCellHSpan(width)); 369 mCellCountY = Math.min(maxCellCountY, estimateCellVSpan(height)); 370 requestLayout(); 371 } 372 373 /** 374 * Estimates the width that the number of hSpan cells will take up. 375 */ 376 public int estimateCellWidth(int hSpan) { 377 // TODO: we need to take widthGap into effect 378 return hSpan * mCellWidth; 379 } 380 381 /** 382 * Estimates the height that the number of vSpan cells will take up. 383 */ 384 public int estimateCellHeight(int vSpan) { 385 // TODO: we need to take heightGap into effect 386 return vSpan * mCellHeight; 387 } 388 389 @Override 390 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 391 return new PagedViewCellLayout.LayoutParams(getContext(), attrs); 392 } 393 394 @Override 395 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 396 return p instanceof PagedViewCellLayout.LayoutParams; 397 } 398 399 @Override 400 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 401 return new PagedViewCellLayout.LayoutParams(p); 402 } 403 404 public static class LayoutParams extends ViewGroup.MarginLayoutParams { 405 /** 406 * Horizontal location of the item in the grid. 407 */ 408 @ViewDebug.ExportedProperty 409 public int cellX; 410 411 /** 412 * Vertical location of the item in the grid. 413 */ 414 @ViewDebug.ExportedProperty 415 public int cellY; 416 417 /** 418 * Number of cells spanned horizontally by the item. 419 */ 420 @ViewDebug.ExportedProperty 421 public int cellHSpan; 422 423 /** 424 * Number of cells spanned vertically by the item. 425 */ 426 @ViewDebug.ExportedProperty 427 public int cellVSpan; 428 429 /** 430 * Is this item currently being dragged 431 */ 432 public boolean isDragging; 433 434 // a data object that you can bind to this layout params 435 private Object mTag; 436 437 // X coordinate of the view in the layout. 438 @ViewDebug.ExportedProperty 439 int x; 440 // Y coordinate of the view in the layout. 441 @ViewDebug.ExportedProperty 442 int y; 443 444 public LayoutParams() { 445 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 446 cellHSpan = 1; 447 cellVSpan = 1; 448 } 449 450 public LayoutParams(Context c, AttributeSet attrs) { 451 super(c, attrs); 452 cellHSpan = 1; 453 cellVSpan = 1; 454 } 455 456 public LayoutParams(ViewGroup.LayoutParams source) { 457 super(source); 458 cellHSpan = 1; 459 cellVSpan = 1; 460 } 461 462 public LayoutParams(LayoutParams source) { 463 super(source); 464 this.cellX = source.cellX; 465 this.cellY = source.cellY; 466 this.cellHSpan = source.cellHSpan; 467 this.cellVSpan = source.cellVSpan; 468 } 469 470 public LayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan) { 471 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 472 this.cellX = cellX; 473 this.cellY = cellY; 474 this.cellHSpan = cellHSpan; 475 this.cellVSpan = cellVSpan; 476 } 477 478 public void setup(int cellWidth, int cellHeight, int widthGap, int heightGap, 479 int hStartPadding, int vStartPadding) { 480 481 final int myCellHSpan = cellHSpan; 482 final int myCellVSpan = cellVSpan; 483 final int myCellX = cellX; 484 final int myCellY = cellY; 485 486 width = myCellHSpan * cellWidth + ((myCellHSpan - 1) * widthGap) - 487 leftMargin - rightMargin; 488 height = myCellVSpan * cellHeight + ((myCellVSpan - 1) * heightGap) - 489 topMargin - bottomMargin; 490 491 if (LauncherApplication.isScreenLarge()) { 492 x = hStartPadding + myCellX * (cellWidth + widthGap) + leftMargin; 493 y = vStartPadding + myCellY * (cellHeight + heightGap) + topMargin; 494 } else { 495 x = myCellX * (cellWidth + widthGap) + leftMargin; 496 y = myCellY * (cellHeight + heightGap) + topMargin; 497 } 498 } 499 500 public Object getTag() { 501 return mTag; 502 } 503 504 public void setTag(Object tag) { 505 mTag = tag; 506 } 507 508 public String toString() { 509 return "(" + this.cellX + ", " + this.cellY + ", " + 510 this.cellHSpan + ", " + this.cellVSpan + ")"; 511 } 512 } 513 } 514 515 interface Page { 516 public int getPageChildCount(); 517 public View getChildOnPageAt(int i); 518 public void removeAllViewsOnPage(); 519 public void removeViewOnPageAt(int i); 520 public int indexOfChildOnPage(View v); 521 } 522