Folder.java revision badf71e11fba2d6efa1d1bcca9542001f90a3777
1/* 2 * Copyright (C) 2008 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 17package com.android.launcher2; 18 19import java.util.ArrayList; 20 21import android.animation.Animator; 22import android.animation.AnimatorListenerAdapter; 23import android.animation.ObjectAnimator; 24import android.animation.PropertyValuesHolder; 25import android.animation.ValueAnimator; 26import android.animation.ValueAnimator.AnimatorUpdateListener; 27import android.content.Context; 28import android.graphics.Rect; 29import android.graphics.drawable.Drawable; 30import android.util.AttributeSet; 31import android.view.LayoutInflater; 32import android.view.MotionEvent; 33import android.view.View; 34import android.view.View.OnClickListener; 35import android.view.animation.AccelerateInterpolator; 36import android.view.animation.DecelerateInterpolator; 37import android.widget.AdapterView; 38import android.widget.LinearLayout; 39import android.widget.TextView; 40import android.widget.AdapterView.OnItemClickListener; 41import android.widget.AdapterView.OnItemLongClickListener; 42 43import com.android.launcher.R; 44import com.android.launcher2.FolderInfo.FolderListener; 45 46/** 47 * Represents a set of icons chosen by the user or generated by the system. 48 */ 49public class Folder extends LinearLayout implements DragSource, OnItemLongClickListener, 50 OnItemClickListener, OnClickListener, View.OnLongClickListener, DropTarget, FolderListener { 51 52 protected DragController mDragController; 53 54 protected Launcher mLauncher; 55 56 protected FolderInfo mInfo; 57 58 /** 59 * Which item is being dragged 60 */ 61 protected ShortcutInfo mDragItem; 62 63 private static final String TAG = "Launcher.Folder"; 64 65 static final int STATE_NONE = -1; 66 static final int STATE_SMALL = 0; 67 static final int STATE_ANIMATING = 1; 68 static final int STATE_OPEN = 2; 69 70 private int mExpandDuration; 71 protected CellLayout mContent; 72 private final LayoutInflater mInflater; 73 private final IconCache mIconCache; 74 private int mState = STATE_NONE; 75 private int[] mDragItemPosition = new int[2]; 76 private static final int FULL_GROW = 0; 77 private static final int PARTIAL_GROW = 1; 78 private int mMode = PARTIAL_GROW; 79 private boolean mRearrangeOnClose = false; 80 private FolderIcon mFolderIcon; 81 private int mMaxCountX; 82 private int mMaxCountY; 83 private Rect mNewSize = new Rect(); 84 private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); 85 private Drawable mIconDrawable; 86 boolean mItemsInvalidated = false; 87 88 /** 89 * Used to inflate the Workspace from XML. 90 * 91 * @param context The application's context. 92 * @param attrs The attribtues set containing the Workspace's customization values. 93 */ 94 public Folder(Context context, AttributeSet attrs) { 95 super(context, attrs); 96 setAlwaysDrawnWithCacheEnabled(false); 97 mInflater = LayoutInflater.from(context); 98 mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); 99 mExpandDuration = getResources().getInteger(R.integer.config_folderAnimDuration); 100 101 mMaxCountX = LauncherModel.getCellCountX() - 1; 102 mMaxCountY = LauncherModel.getCellCountY() - 1; 103 } 104 105 @Override 106 protected void onFinishInflate() { 107 super.onFinishInflate(); 108 mContent = (CellLayout) findViewById(R.id.folder_content); 109 mContent.setGridSize(0, 0); 110 mContent.enableHardwareLayers(); 111 } 112 113 public void onItemClick(AdapterView parent, View v, int position, long id) { 114 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position); 115 int[] pos = new int[2]; 116 v.getLocationOnScreen(pos); 117 app.intent.setSourceBounds(new Rect(pos[0], pos[1], 118 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 119 mLauncher.startActivitySafely(app.intent, app); 120 } 121 122 public void onClick(View v) { 123 Object tag = v.getTag(); 124 if (tag instanceof ShortcutInfo) { 125 // refactor this code from Folder 126 ShortcutInfo item = (ShortcutInfo) tag; 127 int[] pos = new int[2]; 128 v.getLocationOnScreen(pos); 129 item.intent.setSourceBounds(new Rect(pos[0], pos[1], 130 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 131 mLauncher.startActivitySafely(item.intent, item); 132 } 133 } 134 135 public boolean onLongClick(View v) { 136 Object tag = v.getTag(); 137 if (tag instanceof ShortcutInfo) { 138 mLauncher.closeFolder(this); 139 140 ShortcutInfo item = (ShortcutInfo) tag; 141 if (!v.isInTouchMode()) { 142 return false; 143 } 144 145 mLauncher.getWorkspace().onDragStartedWithItem(v); 146 mDragController.startDrag(v, this, item, DragController.DRAG_ACTION_COPY); 147 mDragItemPosition[0] = item.cellX; 148 mDragItemPosition[1] = item.cellY; 149 mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; 150 mInfo.remove(item); 151 152 mDragItem = item; 153 } else { 154 mLauncher.closeFolder(this); 155 mLauncher.showRenameDialog(mInfo); 156 } 157 return true; 158 } 159 160 public Drawable getDragDrawable() { 161 return mIconDrawable; 162 } 163 164 /** 165 * We need to handle touch events to prevent them from falling through to the workspace below. 166 */ 167 @Override 168 public boolean onTouchEvent(MotionEvent ev) { 169 return true; 170 } 171 172 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 173 if (!view.isInTouchMode()) { 174 return false; 175 } 176 177 ShortcutInfo app = (ShortcutInfo) parent.getItemAtPosition(position); 178 179 mDragController.startDrag(view, this, app, DragController.DRAG_ACTION_COPY); 180 mLauncher.closeFolder(this); 181 mDragItem = app; 182 183 return true; 184 } 185 186 public void setDragController(DragController dragController) { 187 mDragController = dragController; 188 } 189 190 public void onDragViewVisible() { 191 } 192 193 void setLauncher(Launcher launcher) { 194 mLauncher = launcher; 195 } 196 197 void setFolderIcon(FolderIcon icon) { 198 mFolderIcon = icon; 199 } 200 201 /** 202 * @return the FolderInfo object associated with this folder 203 */ 204 FolderInfo getInfo() { 205 return mInfo; 206 } 207 208 void onOpen() { 209 // When the folder opens, we need to refresh the GridView's selection by 210 // forcing a layout 211 // TODO: find out if this is still necessary 212 mContent.requestLayout(); 213 requestFocus(); 214 } 215 216 void onClose() { 217 final Workspace workspace = mLauncher.getWorkspace(); 218 workspace.getChildAt(workspace.getCurrentPage()).requestFocus(); 219 } 220 221 void bind(FolderInfo info) { 222 mInfo = info; 223 ArrayList<ShortcutInfo> children = info.contents; 224 setupContentForNumItems(children.size()); 225 for (int i = 0; i < children.size(); i++) { 226 ShortcutInfo child = (ShortcutInfo) children.get(i); 227 createAndAddShortcut(child); 228 } 229 mItemsInvalidated = true; 230 mInfo.addListener(this); 231 } 232 233 /** 234 * Creates a new UserFolder, inflated from R.layout.user_folder. 235 * 236 * @param context The application's context. 237 * 238 * @return A new UserFolder. 239 */ 240 static Folder fromXml(Context context) { 241 return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); 242 } 243 244 /** 245 * This method is intended to make the UserFolder to be visually identical in size and position 246 * to its associated FolderIcon. This allows for a seamless transition into the expanded state. 247 */ 248 private void positionAndSizeAsIcon() { 249 if (!(getParent() instanceof CellLayoutChildren)) return; 250 251 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); 252 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 253 254 if (mMode == PARTIAL_GROW) { 255 setScaleX(0.8f); 256 setScaleY(0.8f); 257 setAlpha(0f); 258 } else { 259 lp.width = iconLp.width; 260 lp.height = iconLp.height; 261 lp.x = iconLp.x; 262 lp.y = iconLp.y; 263 mContent.setAlpha(0); 264 } 265 mState = STATE_SMALL; 266 } 267 268 public void animateOpen() { 269 if (mState != STATE_SMALL) { 270 positionAndSizeAsIcon(); 271 } 272 if (!(getParent() instanceof CellLayoutChildren)) return; 273 274 ObjectAnimator oa; 275 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 276 277 centerAboutIcon(); 278 if (mMode == PARTIAL_GROW) { 279 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); 280 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); 281 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); 282 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 283 } else { 284 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width()); 285 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height()); 286 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left); 287 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top); 288 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 289 oa.addUpdateListener(new AnimatorUpdateListener() { 290 public void onAnimationUpdate(ValueAnimator animation) { 291 requestLayout(); 292 } 293 }); 294 295 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); 296 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 297 alphaOa.setDuration(mExpandDuration); 298 alphaOa.setInterpolator(new AccelerateInterpolator(2.0f)); 299 alphaOa.start(); 300 } 301 302 oa.addListener(new AnimatorListenerAdapter() { 303 @Override 304 public void onAnimationStart(Animator animation) { 305 mState = STATE_ANIMATING; 306 } 307 @Override 308 public void onAnimationEnd(Animator animation) { 309 mState = STATE_OPEN; 310 } 311 }); 312 oa.setDuration(mExpandDuration); 313 oa.start(); 314 } 315 316 public void animateClosed() { 317 if (!(getParent() instanceof CellLayoutChildren)) return; 318 319 CellLayoutChildren clc = (CellLayoutChildren) getParent(); 320 final CellLayout cellLayout = (CellLayout) clc.getParent(); 321 ObjectAnimator oa; 322 323 if (mMode == PARTIAL_GROW) { 324 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); 325 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); 326 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); 327 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 328 } else { 329 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); 330 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 331 332 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", iconLp.width); 333 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", iconLp.height); 334 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x",iconLp.x); 335 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", iconLp.y); 336 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 337 oa.addUpdateListener(new AnimatorUpdateListener() { 338 public void onAnimationUpdate(ValueAnimator animation) { 339 requestLayout(); 340 } 341 }); 342 343 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); 344 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 345 alphaOa.setDuration(mExpandDuration); 346 alphaOa.setInterpolator(new DecelerateInterpolator(2.0f)); 347 alphaOa.start(); 348 } 349 350 oa.addListener(new AnimatorListenerAdapter() { 351 @Override 352 public void onAnimationEnd(Animator animation) { 353 onCloseComplete(); 354 cellLayout.removeViewWithoutMarkingCells(Folder.this); 355 mState = STATE_SMALL; 356 } 357 @Override 358 public void onAnimationStart(Animator animation) { 359 mState = STATE_ANIMATING; 360 } 361 }); 362 oa.setDuration(mExpandDuration); 363 oa.start(); 364 } 365 366 void notifyDataSetChanged() { 367 // recreate all the children if the data set changes under us. We may want to do this more 368 // intelligently (ie just removing the views that should no longer exist) 369 mContent.removeAllViewsInLayout(); 370 bind(mInfo); 371 } 372 373 public boolean acceptDrop(DragObject d) { 374 final ItemInfo item = (ItemInfo) d.dragInfo; 375 final int itemType = item.itemType; 376 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 377 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 378 !isFull()); 379 } 380 381 public void onDrop(DragObject d) { 382 ShortcutInfo item; 383 if (d.dragInfo instanceof ApplicationInfo) { 384 // Came from all apps -- make a copy 385 item = ((ApplicationInfo) d.dragInfo).makeShortcut(); 386 item.spanX = 1; 387 item.spanY = 1; 388 } else { 389 item = (ShortcutInfo) d.dragInfo; 390 } 391 mInfo.add(item); 392 LauncherModel.addOrMoveItemInDatabase(mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); 393 } 394 395 protected boolean findAndSetEmptyCells(ShortcutInfo item) { 396 int[] emptyCell = new int[2]; 397 if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { 398 item.cellX = emptyCell[0]; 399 item.cellY = emptyCell[1]; 400 LauncherModel.addOrMoveItemInDatabase( 401 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); 402 return true; 403 } else { 404 return false; 405 } 406 } 407 408 protected void createAndAddShortcut(ShortcutInfo item) { 409 final TextView textView = 410 (TextView) mInflater.inflate(R.layout.application_boxed, this, false); 411 textView.setCompoundDrawablesWithIntrinsicBounds(null, 412 new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); 413 textView.setText(item.title); 414 textView.setTag(item); 415 416 textView.setOnClickListener(this); 417 textView.setOnLongClickListener(this); 418 419 CellLayout.LayoutParams lp = 420 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); 421 boolean insert = false; 422 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); 423 } 424 425 public void onDragEnter(DragObject d) { 426 mContent.onDragEnter(); 427 } 428 429 public void onDragOver(DragObject d) { 430 float[] r = mapPointFromScreenToContent(d.x, d.y, null); 431 mContent.visualizeDropLocation(null, null, (int) r[0], (int) r[1], 1, 1); 432 } 433 434 public void onDragExit(DragObject d) { 435 mContent.onDragExit(); 436 } 437 438 public float[] mapPointFromScreenToContent(int x, int y, float[] r) { 439 if (r == null) { 440 r = new float[2]; 441 } 442 443 int[] screenLocation = new int[2]; 444 mContent.getLocationOnScreen(screenLocation); 445 446 r[0] = x - screenLocation[0]; 447 r[1] = y - screenLocation[1]; 448 return r; 449 } 450 451 public void onDropCompleted(View target, Object dragInfo, boolean success) { 452 } 453 454 public boolean isDropEnabled() { 455 return true; 456 } 457 458 public DropTarget getDropTargetDelegate(DragObject d) { 459 return null; 460 } 461 462 private void setupContentDimension(int count) { 463 ArrayList<View> list = getItemsInReadingOrder(); 464 465 int countX = mContent.getCountX(); 466 int countY = mContent.getCountY(); 467 boolean done = false; 468 469 while (!done) { 470 int oldCountX = countX; 471 int oldCountY = countY; 472 if (countX * countY < count) { 473 // Current grid is too small, expand it 474 if (countX <= countY && countX < mMaxCountX) { 475 countX++; 476 } else if (countY < mMaxCountY) { 477 countY++; 478 } 479 if (countY == 0) countY++; 480 } else if ((countY - 1) * countX >= count && countY >= countX) { 481 countY = Math.max(0, countY - 1); 482 } else if ((countX - 1) * countY >= count) { 483 countX = Math.max(0, countX - 1); 484 } 485 done = countX == oldCountX && countY == oldCountY; 486 } 487 mContent.setGridSize(countX, countY); 488 arrangeChildren(list); 489 } 490 491 public boolean isFull() { 492 return getItemCount() >= mMaxCountX * mMaxCountY; 493 } 494 495 private void centerAboutIcon() { 496 CellLayout.LayoutParams iconLp = (CellLayout.LayoutParams) mFolderIcon.getLayoutParams(); 497 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 498 499 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 500 int height = getPaddingTop() + getPaddingBottom() + mContent.getDesiredHeight(); 501 502 int centerX = iconLp.x + iconLp.width / 2; 503 int centerY = iconLp.y + iconLp.height / 2; 504 int centeredLeft = centerX - width / 2; 505 int centeredTop = centerY - height / 2; 506 507 CellLayoutChildren clc = (CellLayoutChildren) getParent(); 508 int parentWidth = 0; 509 int parentHeight = 0; 510 if (clc != null) { 511 parentWidth = clc.getMeasuredWidth(); 512 parentHeight = clc.getMeasuredHeight(); 513 } 514 515 int left = Math.min(Math.max(0, centeredLeft), parentWidth - width); 516 int top = Math.min(Math.max(0, centeredTop), parentHeight - height); 517 518 int folderPivotX = width / 2 + (centeredLeft - left); 519 int folderPivotY = height / 2 + (centeredTop - top); 520 setPivotX(folderPivotX); 521 setPivotY(folderPivotY); 522 int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * 523 (1.0f * folderPivotX / width)); 524 int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * 525 (1.0f * folderPivotY / height)); 526 mFolderIcon.setPivotX(folderIconPivotX); 527 mFolderIcon.setPivotY(folderIconPivotY); 528 529 if (mMode == PARTIAL_GROW) { 530 lp.width = width; 531 lp.height = height; 532 lp.x = left; 533 lp.y = top; 534 } else { 535 mNewSize.set(left, top, left + width, top + height); 536 } 537 } 538 539 private void setupContentForNumItems(int count) { 540 setupContentDimension(count); 541 542 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); 543 if (lp == null) { 544 lp = new CellLayout.LayoutParams(0, 0, -1, -1); 545 lp.isLockedToGrid = false; 546 setLayoutParams(lp); 547 } 548 centerAboutIcon(); 549 } 550 551 private void arrangeChildren(ArrayList<View> list) { 552 int[] vacant = new int[2]; 553 if (list == null) { 554 list = getItemsInReadingOrder(); 555 } 556 mContent.removeAllViews(); 557 558 for (int i = 0; i < list.size(); i++) { 559 View v = list.get(i); 560 mContent.getVacantCell(vacant, 1, 1); 561 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 562 lp.cellX = vacant[0]; 563 lp.cellY = vacant[1]; 564 ItemInfo info = (ItemInfo) v.getTag(); 565 info.cellX = vacant[0]; 566 info.cellY = vacant[1]; 567 boolean insert = false; 568 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); 569 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, 570 info.cellX, info.cellY); 571 } 572 mItemsInvalidated = true; 573 } 574 575 public void onAdd(ShortcutInfo item) { 576 mItemsInvalidated = true; 577 if (!findAndSetEmptyCells(item)) { 578 // The current layout is full, can we expand it? 579 setupContentForNumItems(getItemCount() + 1); 580 findAndSetEmptyCells(item); 581 } 582 createAndAddShortcut(item); 583 } 584 585 public int getItemCount() { 586 return mContent.getChildrenLayout().getChildCount(); 587 } 588 589 public View getItemAt(int index) { 590 return mContent.getChildrenLayout().getChildAt(index); 591 } 592 593 private void onCloseComplete() { 594 if (mRearrangeOnClose) { 595 setupContentForNumItems(getItemCount()); 596 mRearrangeOnClose = false; 597 } 598 } 599 600 public void onRemove(ShortcutInfo item) { 601 mItemsInvalidated = true; 602 View v = mContent.getChildAt(mDragItemPosition[0], mDragItemPosition[1]); 603 mContent.removeView(v); 604 if (mState == STATE_ANIMATING) { 605 mRearrangeOnClose = true; 606 } else { 607 setupContentForNumItems(getItemCount()); 608 } 609 } 610 611 public ArrayList<View> getItemsInReadingOrder() { 612 if (mItemsInvalidated) { 613 mItemsInReadingOrder.clear(); 614 for (int j = 0; j < mContent.getCountY(); j++) { 615 for (int i = 0; i < mContent.getCountX(); i++) { 616 View v = mContent.getChildAt(i, j); 617 if (v != null) { 618 mItemsInReadingOrder.add(v); 619 } 620 } 621 } 622 mItemsInvalidated = false; 623 } 624 return mItemsInReadingOrder; 625 } 626} 627