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