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