Folder.java revision 3d503fbd9468fb2b9fa645f4f7b91e11229edbfa
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 android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.content.Context; 26import android.content.res.Resources; 27import android.graphics.Rect; 28import android.graphics.drawable.Drawable; 29import android.text.InputType; 30import android.util.AttributeSet; 31import android.view.ActionMode; 32import android.view.KeyEvent; 33import android.view.LayoutInflater; 34import android.view.Menu; 35import android.view.MenuItem; 36import android.view.MotionEvent; 37import android.view.View; 38import android.view.View.MeasureSpec; 39import android.view.animation.AccelerateInterpolator; 40import android.view.animation.DecelerateInterpolator; 41import android.view.inputmethod.EditorInfo; 42import android.view.inputmethod.InputMethodManager; 43import android.widget.AdapterView; 44import android.widget.LinearLayout; 45import android.widget.TextView; 46 47import com.android.launcher.R; 48import com.android.launcher2.FolderInfo.FolderListener; 49 50import java.util.ArrayList; 51 52/** 53 * Represents a set of icons chosen by the user or generated by the system. 54 */ 55public class Folder extends LinearLayout implements DragSource, View.OnClickListener, 56 View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener { 57 58 private static final String TAG = "Launcher.Folder"; 59 60 protected DragController mDragController; 61 protected Launcher mLauncher; 62 protected FolderInfo mInfo; 63 64 static final int STATE_NONE = -1; 65 static final int STATE_SMALL = 0; 66 static final int STATE_ANIMATING = 1; 67 static final int STATE_OPEN = 2; 68 69 private int mExpandDuration; 70 protected CellLayout mContent; 71 private final LayoutInflater mInflater; 72 private final IconCache mIconCache; 73 private int mState = STATE_NONE; 74 private static final int FULL_GROW = 0; 75 private static final int PARTIAL_GROW = 1; 76 private static final int REORDER_ANIMATION_DURATION = 230; 77 private static final int ON_EXIT_CLOSE_DELAY = 800; 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 Rect mIconRect = new Rect(); 85 private ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); 86 private Drawable mIconDrawable; 87 boolean mItemsInvalidated = false; 88 private ShortcutInfo mCurrentDragInfo; 89 private View mCurrentDragView; 90 boolean mSuppressOnAdd = false; 91 private int[] mTargetCell = new int[2]; 92 private int[] mPreviousTargetCell = new int[2]; 93 private int[] mEmptyCell = new int[2]; 94 private int[] mTempXY = new int[2]; 95 private Alarm mReorderAlarm = new Alarm(); 96 private Alarm mOnExitAlarm = new Alarm(); 97 private TextView mFolderName; 98 private int mFolderNameHeight; 99 private Rect mHitRect = new Rect(); 100 private Rect mTempRect = new Rect(); 101 private boolean mFirstOpen = true; 102 103 private boolean mIsEditingName = false; 104 private InputMethodManager mInputMethodManager; 105 106 private static String sDefaultFolderName; 107 private static String sHintText; 108 109 /** 110 * Used to inflate the Workspace from XML. 111 * 112 * @param context The application's context. 113 * @param attrs The attribtues set containing the Workspace's customization values. 114 */ 115 public Folder(Context context, AttributeSet attrs) { 116 super(context, attrs); 117 setAlwaysDrawnWithCacheEnabled(false); 118 mInflater = LayoutInflater.from(context); 119 mIconCache = ((LauncherApplication)context.getApplicationContext()).getIconCache(); 120 mMaxCountX = LauncherModel.getCellCountX(); 121 mMaxCountY = LauncherModel.getCellCountY(); 122 123 mInputMethodManager = (InputMethodManager) 124 mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 125 126 Resources res = getResources(); 127 mExpandDuration = res.getInteger(R.integer.config_folderAnimDuration); 128 129 if (sDefaultFolderName == null) { 130 sDefaultFolderName = res.getString(R.string.folder_name); 131 } 132 if (sHintText == null) { 133 sHintText = res.getString(R.string.folder_hint_text); 134 } 135 136 mLauncher = (Launcher) context; 137 } 138 139 @Override 140 protected void onFinishInflate() { 141 super.onFinishInflate(); 142 mContent = (CellLayout) findViewById(R.id.folder_content); 143 mContent.setGridSize(0, 0); 144 mFolderName = (TextView) findViewById(R.id.folder_name); 145 146 // We find out how tall the text view wants to be (it is set to wrap_content), so that 147 // we can allocate the appropriate amount of space for it. 148 int measureSpec = MeasureSpec.UNSPECIFIED; 149 mFolderName.measure(measureSpec, measureSpec); 150 mFolderNameHeight = mFolderName.getMeasuredHeight(); 151 152 // We disable action mode for now since it messes up the view on phones 153 mFolderName.setCustomSelectionActionModeCallback(mActionModeCallback); 154 mFolderName.setCursorVisible(false); 155 mFolderName.setOnEditorActionListener(this); 156 mFolderName.setSelectAllOnFocus(true); 157 mFolderName.setInputType(mFolderName.getInputType() | 158 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_CAP_WORDS); 159 } 160 161 private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { 162 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 163 return false; 164 } 165 166 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 167 return false; 168 } 169 170 public void onDestroyActionMode(ActionMode mode) { 171 } 172 173 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 174 return false; 175 } 176 }; 177 178 public void onClick(View v) { 179 Object tag = v.getTag(); 180 if (tag instanceof ShortcutInfo) { 181 // refactor this code from Folder 182 ShortcutInfo item = (ShortcutInfo) tag; 183 int[] pos = new int[2]; 184 v.getLocationOnScreen(pos); 185 item.intent.setSourceBounds(new Rect(pos[0], pos[1], 186 pos[0] + v.getWidth(), pos[1] + v.getHeight())); 187 mLauncher.startActivitySafely(item.intent, item); 188 } 189 } 190 191 public boolean onInterceptTouchEvent(MotionEvent ev) { 192 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 193 mFolderName.getHitRect(mHitRect); 194 if (mHitRect.contains((int) ev.getX(), (int) ev.getY()) && !mIsEditingName) { 195 startEditingFolderName(); 196 } 197 } 198 return false; 199 } 200 201 public boolean onLongClick(View v) { 202 Object tag = v.getTag(); 203 if (tag instanceof ShortcutInfo) { 204 ShortcutInfo item = (ShortcutInfo) tag; 205 if (!v.isInTouchMode()) { 206 return false; 207 } 208 209 mLauncher.getWorkspace().onDragStartedWithItem(v); 210 mLauncher.getWorkspace().beginDragShared(v, this); 211 mIconDrawable = ((TextView) v).getCompoundDrawables()[1]; 212 213 mCurrentDragInfo = item; 214 mEmptyCell[0] = item.cellX; 215 mEmptyCell[1] = item.cellY; 216 mCurrentDragView = v; 217 } 218 return true; 219 } 220 221 public void onDragViewVisible() { 222 mContent.removeView(mCurrentDragView); 223 mInfo.remove(mCurrentDragInfo); 224 } 225 226 public boolean isEditingName() { 227 return mIsEditingName; 228 } 229 230 public void startEditingFolderName() { 231 mFolderName.setHint(""); 232 mFolderName.setCursorVisible(true); 233 mIsEditingName = true; 234 } 235 236 public void dismissEditingName() { 237 mInputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); 238 doneEditingFolderName(true); 239 } 240 241 public void doneEditingFolderName(boolean commit) { 242 mFolderName.setHint(sHintText); 243 mInfo.setTitle(mFolderName.getText()); 244 LauncherModel.updateItemInDatabase(mLauncher, mInfo); 245 mFolderName.setCursorVisible(false); 246 mFolderName.clearFocus(); 247 mIsEditingName = false; 248 } 249 250 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 251 if (actionId == EditorInfo.IME_ACTION_DONE) { 252 dismissEditingName(); 253 return true; 254 } 255 return false; 256 } 257 258 public View getEditTextRegion() { 259 return mFolderName; 260 } 261 262 public Drawable getDragDrawable() { 263 return mIconDrawable; 264 } 265 266 /** 267 * We need to handle touch events to prevent them from falling through to the workspace below. 268 */ 269 @Override 270 public boolean onTouchEvent(MotionEvent ev) { 271 return true; 272 } 273 274 public void setDragController(DragController dragController) { 275 mDragController = dragController; 276 } 277 278 void setFolderIcon(FolderIcon icon) { 279 mFolderIcon = icon; 280 } 281 282 /** 283 * @return the FolderInfo object associated with this folder 284 */ 285 FolderInfo getInfo() { 286 return mInfo; 287 } 288 289 void onOpen() { 290 // When the folder opens, we need to refresh the GridView's selection by 291 // forcing a layout 292 // TODO: find out if this is still necessary 293 mContent.requestLayout(); 294 } 295 296 void onClose() { 297 DragLayer parent = (DragLayer) getParent(); 298 parent.removeView(Folder.this); 299 clearFocus(); 300 } 301 302 void bind(FolderInfo info) { 303 mInfo = info; 304 ArrayList<ShortcutInfo> children = info.contents; 305 ArrayList<ShortcutInfo> overflow = new ArrayList<ShortcutInfo>(); 306 setupContentForNumItems(children.size()); 307 for (int i = 0; i < children.size(); i++) { 308 ShortcutInfo child = (ShortcutInfo) children.get(i); 309 if (!createAndAddShortcut(child)) { 310 overflow.add(child); 311 } 312 } 313 314 // If our folder has too many items we prune them from the list. This is an issue 315 // when upgrading from the old Folders implementation which could contain an unlimited 316 // number of items. 317 for (ShortcutInfo item: overflow) { 318 mInfo.remove(item); 319 LauncherModel.deleteItemFromDatabase(mLauncher, item); 320 } 321 322 mItemsInvalidated = true; 323 mInfo.addListener(this); 324 325 if (!sDefaultFolderName.contentEquals(mInfo.title)) { 326 mFolderName.setText(mInfo.title); 327 } else { 328 mFolderName.setText(""); 329 } 330 } 331 332 /** 333 * Creates a new UserFolder, inflated from R.layout.user_folder. 334 * 335 * @param context The application's context. 336 * 337 * @return A new UserFolder. 338 */ 339 static Folder fromXml(Context context) { 340 return (Folder) LayoutInflater.from(context).inflate(R.layout.user_folder, null); 341 } 342 343 /** 344 * This method is intended to make the UserFolder to be visually identical in size and position 345 * to its associated FolderIcon. This allows for a seamless transition into the expanded state. 346 */ 347 private void positionAndSizeAsIcon() { 348 if (!(getParent() instanceof DragLayer)) return; 349 350 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 351 352 if (mMode == PARTIAL_GROW) { 353 setScaleX(0.8f); 354 setScaleY(0.8f); 355 setAlpha(0f); 356 } else { 357 mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mFolderIcon, mIconRect); 358 lp.width = mIconRect.width(); 359 lp.height = mIconRect.height(); 360 lp.x = mIconRect.left; 361 lp.y = mIconRect.top; 362 mContent.setAlpha(0); 363 } 364 mState = STATE_SMALL; 365 } 366 367 public void animateOpen() { 368 if (mFirstOpen) { 369 setLayerType(LAYER_TYPE_HARDWARE, null); 370 buildLayer(); 371 mFirstOpen = false; 372 } 373 374 positionAndSizeAsIcon(); 375 376 if (!(getParent() instanceof DragLayer)) return; 377 378 ObjectAnimator oa; 379 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 380 381 centerAboutIcon(); 382 if (mMode == PARTIAL_GROW) { 383 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1); 384 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f); 385 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1.0f); 386 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 387 } else { 388 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mNewSize.width()); 389 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mNewSize.height()); 390 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mNewSize.left); 391 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mNewSize.top); 392 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 393 oa.addUpdateListener(new AnimatorUpdateListener() { 394 public void onAnimationUpdate(ValueAnimator animation) { 395 requestLayout(); 396 } 397 }); 398 399 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1.0f); 400 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 401 alphaOa.setDuration(mExpandDuration); 402 alphaOa.setInterpolator(new AccelerateInterpolator(2.0f)); 403 alphaOa.start(); 404 } 405 406 oa.addListener(new AnimatorListenerAdapter() { 407 @Override 408 public void onAnimationStart(Animator animation) { 409 mState = STATE_ANIMATING; 410 } 411 @Override 412 public void onAnimationEnd(Animator animation) { 413 mState = STATE_OPEN; 414 setLayerType(LAYER_TYPE_NONE, null); 415 enableHardwareLayersForChildren(); 416 } 417 }); 418 oa.setDuration(mExpandDuration); 419 oa.start(); 420 } 421 422 void enableHardwareLayersForChildren() { 423 ArrayList<View> children = getItemsInReadingOrder(); 424 for (View child: children) { 425 child.setLayerType(LAYER_TYPE_HARDWARE, null); 426 } 427 } 428 429 public void animateClosed() { 430 if (!(getParent() instanceof DragLayer)) return; 431 setLayerType(LAYER_TYPE_HARDWARE, null); 432 buildLayer(); 433 434 ObjectAnimator oa; 435 if (mMode == PARTIAL_GROW) { 436 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0); 437 PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 0.9f); 438 PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 0.9f); 439 oa = ObjectAnimator.ofPropertyValuesHolder(this, alpha, scaleX, scaleY); 440 } else { 441 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 442 443 PropertyValuesHolder width = PropertyValuesHolder.ofInt("width", mIconRect.width()); 444 PropertyValuesHolder height = PropertyValuesHolder.ofInt("height", mIconRect.height()); 445 PropertyValuesHolder x = PropertyValuesHolder.ofInt("x", mIconRect.left); 446 PropertyValuesHolder y = PropertyValuesHolder.ofInt("y", mIconRect.top); 447 oa = ObjectAnimator.ofPropertyValuesHolder(lp, width, height, x, y); 448 oa.addUpdateListener(new AnimatorUpdateListener() { 449 public void onAnimationUpdate(ValueAnimator animation) { 450 requestLayout(); 451 } 452 }); 453 454 PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 0f); 455 ObjectAnimator alphaOa = ObjectAnimator.ofPropertyValuesHolder(mContent, alpha); 456 alphaOa.setDuration(mExpandDuration); 457 alphaOa.setInterpolator(new DecelerateInterpolator(2.0f)); 458 alphaOa.start(); 459 } 460 461 oa.addListener(new AnimatorListenerAdapter() { 462 @Override 463 public void onAnimationEnd(Animator animation) { 464 onClose(); 465 onCloseComplete(); 466 mState = STATE_SMALL; 467 } 468 @Override 469 public void onAnimationStart(Animator animation) { 470 mState = STATE_ANIMATING; 471 } 472 }); 473 oa.setDuration(mExpandDuration); 474 oa.start(); 475 } 476 477 void notifyDataSetChanged() { 478 // recreate all the children if the data set changes under us. We may want to do this more 479 // intelligently (ie just removing the views that should no longer exist) 480 mContent.removeAllViewsInLayout(); 481 bind(mInfo); 482 } 483 484 public boolean acceptDrop(DragObject d) { 485 final ItemInfo item = (ItemInfo) d.dragInfo; 486 final int itemType = item.itemType; 487 return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION || 488 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) && 489 !isFull()); 490 } 491 492 protected boolean findAndSetEmptyCells(ShortcutInfo item) { 493 int[] emptyCell = new int[2]; 494 if (mContent.findCellForSpan(emptyCell, item.spanX, item.spanY)) { 495 item.cellX = emptyCell[0]; 496 item.cellY = emptyCell[1]; 497 return true; 498 } else { 499 return false; 500 } 501 } 502 503 protected boolean createAndAddShortcut(ShortcutInfo item) { 504 final TextView textView = 505 (TextView) mInflater.inflate(R.layout.application, this, false); 506 textView.setCompoundDrawablesWithIntrinsicBounds(null, 507 new FastBitmapDrawable(item.getIcon(mIconCache)), null, null); 508 textView.setText(item.title); 509 textView.setTag(item); 510 511 textView.setOnClickListener(this); 512 textView.setOnLongClickListener(this); 513 514 // We need to check here to verify that the given item's location isn't already occupied 515 // by another item. If it is, we need to find the next available slot and assign 516 // it that position. This is an issue when upgrading from the old Folders implementation 517 // which could contain an unlimited number of items. 518 if (mContent.getChildAt(item.cellX, item.cellY) != null) { 519 if (!findAndSetEmptyCells(item)) { 520 return false; 521 } 522 } 523 524 CellLayout.LayoutParams lp = 525 new CellLayout.LayoutParams(item.cellX, item.cellY, item.spanX, item.spanY); 526 boolean insert = false; 527 mContent.addViewToCellLayout(textView, insert ? 0 : -1, (int)item.id, lp, true); 528 return true; 529 } 530 531 public void onDragEnter(DragObject d) { 532 mPreviousTargetCell[0] = -1; 533 mPreviousTargetCell[1] = -1; 534 mOnExitAlarm.cancelAlarm(); 535 } 536 537 OnAlarmListener mReorderAlarmListener = new OnAlarmListener() { 538 public void onAlarm(Alarm alarm) { 539 realTimeReorder(mEmptyCell, mTargetCell); 540 } 541 }; 542 543 boolean readingOrderGreaterThan(int[] v1, int[] v2) { 544 if (v1[1] > v2[1] || (v1[1] == v2[1] && v1[0] > v2[0])) { 545 return true; 546 } else { 547 return false; 548 } 549 } 550 551 private void realTimeReorder(int[] empty, int[] target) { 552 boolean wrap; 553 int startX; 554 int endX; 555 int startY; 556 int delay = 0; 557 float delayAmount = 30; 558 if (readingOrderGreaterThan(target, empty)) { 559 wrap = empty[0] >= mContent.getCountX() - 1; 560 startY = wrap ? empty[1] + 1 : empty[1]; 561 for (int y = startY; y <= target[1]; y++) { 562 startX = y == empty[1] ? empty[0] + 1 : 0; 563 endX = y < target[1] ? mContent.getCountX() - 1 : target[0]; 564 for (int x = startX; x <= endX; x++) { 565 View v = mContent.getChildAt(x,y); 566 if (mContent.animateChildToPosition(v, empty[0], empty[1], 567 REORDER_ANIMATION_DURATION, delay)) { 568 empty[0] = x; 569 empty[1] = y; 570 delay += delayAmount; 571 delayAmount *= 0.9; 572 } 573 } 574 } 575 } else { 576 wrap = empty[0] == 0; 577 startY = wrap ? empty[1] - 1 : empty[1]; 578 for (int y = startY; y >= target[1]; y--) { 579 startX = y == empty[1] ? empty[0] - 1 : mContent.getCountX() - 1; 580 endX = y > target[1] ? 0 : target[0]; 581 for (int x = startX; x >= endX; x--) { 582 View v = mContent.getChildAt(x,y); 583 if (mContent.animateChildToPosition(v, empty[0], empty[1], 584 REORDER_ANIMATION_DURATION, delay)) { 585 empty[0] = x; 586 empty[1] = y; 587 delay += delayAmount; 588 delayAmount *= 0.9; 589 } 590 } 591 } 592 } 593 } 594 595 public void onDragOver(DragObject d) { 596 float[] r = getDragViewVisualCenter(d.x, d.y, d.xOffset, d.yOffset, d.dragView, null); 597 mTargetCell = mContent.findNearestArea((int) r[0], (int) r[1], 1, 1, mTargetCell); 598 599 if (mTargetCell[0] != mPreviousTargetCell[0] || mTargetCell[1] != mPreviousTargetCell[1]) { 600 mReorderAlarm.cancelAlarm(); 601 mReorderAlarm.setOnAlarmListener(mReorderAlarmListener); 602 mReorderAlarm.setAlarm(150); 603 mPreviousTargetCell[0] = mTargetCell[0]; 604 mPreviousTargetCell[1] = mTargetCell[1]; 605 } 606 } 607 608 // This is used to compute the visual center of the dragView. The idea is that 609 // the visual center represents the user's interpretation of where the item is, and hence 610 // is the appropriate point to use when determining drop location. 611 private float[] getDragViewVisualCenter(int x, int y, int xOffset, int yOffset, 612 DragView dragView, float[] recycle) { 613 float res[]; 614 if (recycle == null) { 615 res = new float[2]; 616 } else { 617 res = recycle; 618 } 619 620 // These represent the visual top and left of drag view if a dragRect was provided. 621 // If a dragRect was not provided, then they correspond to the actual view left and 622 // top, as the dragRect is in that case taken to be the entire dragView. 623 // R.dimen.dragViewOffsetY. 624 int left = x - xOffset; 625 int top = y - yOffset; 626 627 // In order to find the visual center, we shift by half the dragRect 628 res[0] = left + dragView.getDragRegion().width() / 2; 629 res[1] = top + dragView.getDragRegion().height() / 2; 630 631 return res; 632 } 633 634 OnAlarmListener mOnExitAlarmListener = new OnAlarmListener() { 635 public void onAlarm(Alarm alarm) { 636 completeDragExit(); 637 } 638 }; 639 640 public void completeDragExit() { 641 mLauncher.closeFolder(); 642 mCurrentDragInfo = null; 643 mCurrentDragView = null; 644 mSuppressOnAdd = false; 645 mRearrangeOnClose = true; 646 } 647 648 public void onDragExit(DragObject d) { 649 // We only close the folder if this is a true drag exit, ie. not because a drop 650 // has occurred above the folder. 651 if (!d.dragComplete) { 652 mOnExitAlarm.setOnAlarmListener(mOnExitAlarmListener); 653 mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY); 654 } 655 mReorderAlarm.cancelAlarm(); 656 } 657 658 public void onDropCompleted(View target, DragObject d, boolean success) { 659 mCurrentDragInfo = null; 660 mCurrentDragView = null; 661 mSuppressOnAdd = false; 662 if (!success) { 663 if (d.dragView != null) { 664 if (target instanceof CellLayout) { 665 // TODO: we should animate the item back to the folder in this case 666 } 667 } 668 // TODO: if the drag fails, we need to re-add the item 669 } else { 670 if (target != this) { 671 mOnExitAlarm.cancelAlarm(); 672 completeDragExit(); 673 } 674 } 675 } 676 677 public boolean isDropEnabled() { 678 return true; 679 } 680 681 public DropTarget getDropTargetDelegate(DragObject d) { 682 return null; 683 } 684 685 private void setupContentDimension(int count) { 686 ArrayList<View> list = getItemsInReadingOrder(); 687 688 int countX = mContent.getCountX(); 689 int countY = mContent.getCountY(); 690 boolean done = false; 691 692 while (!done) { 693 int oldCountX = countX; 694 int oldCountY = countY; 695 if (countX * countY < count) { 696 // Current grid is too small, expand it 697 if (countX <= countY && countX < mMaxCountX) { 698 countX++; 699 } else if (countY < mMaxCountY) { 700 countY++; 701 } 702 if (countY == 0) countY++; 703 } else if ((countY - 1) * countX >= count && countY >= countX) { 704 countY = Math.max(0, countY - 1); 705 } else if ((countX - 1) * countY >= count) { 706 countX = Math.max(0, countX - 1); 707 } 708 done = countX == oldCountX && countY == oldCountY; 709 } 710 mContent.setGridSize(countX, countY); 711 arrangeChildren(list); 712 } 713 714 public boolean isFull() { 715 return getItemCount() >= mMaxCountX * mMaxCountY; 716 } 717 718 private void centerAboutIcon() { 719 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 720 721 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 722 // Technically there is no padding at the bottom, but we add space equal to the padding 723 // and have to account for that here. 724 int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight; 725 DragLayer parent = (DragLayer) mLauncher.findViewById(R.id.drag_layer); 726 727 parent.getDescendantRectRelativeToSelf(mFolderIcon, mTempRect); 728 729 int centerX = mTempRect.centerX(); 730 int centerY = mTempRect.centerY(); 731 int centeredLeft = centerX - width / 2; 732 int centeredTop = centerY - height / 2; 733 734 // We first fetch the currently visible CellLayoutChildren 735 CellLayout currentPage = mLauncher.getWorkspace().getCurrentDropLayout(); 736 CellLayoutChildren boundingLayout = currentPage.getChildrenLayout(); 737 Rect bounds = new Rect(); 738 parent.getDescendantRectRelativeToSelf(boundingLayout, bounds); 739 740 // We need to bound the folder to the currently visible CellLayoutChildren 741 int left = Math.min(Math.max(bounds.left, centeredLeft), 742 bounds.left + bounds.width() - width); 743 int top = Math.min(Math.max(bounds.top, centeredTop), 744 bounds.top + bounds.height() - height); 745 // If the folder doesn't fit within the bounds, center it about the desired bounds 746 if (width >= bounds.width()) { 747 left = bounds.left + (bounds.width() - width) / 2; 748 } 749 if (height >= bounds.height()) { 750 top = bounds.top + (bounds.height() - height) / 2; 751 } 752 753 int folderPivotX = width / 2 + (centeredLeft - left); 754 int folderPivotY = height / 2 + (centeredTop - top); 755 setPivotX(folderPivotX); 756 setPivotY(folderPivotY); 757 int folderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * 758 (1.0f * folderPivotX / width)); 759 int folderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * 760 (1.0f * folderPivotY / height)); 761 mFolderIcon.setPivotX(folderIconPivotX); 762 mFolderIcon.setPivotY(folderIconPivotY); 763 764 if (mMode == PARTIAL_GROW) { 765 lp.width = width; 766 lp.height = height; 767 lp.x = left; 768 lp.y = top; 769 } else { 770 mNewSize.set(left, top, left + width, top + height); 771 } 772 } 773 774 private void setupContentForNumItems(int count) { 775 setupContentDimension(count); 776 777 DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); 778 if (lp == null) { 779 lp = new DragLayer.LayoutParams(0, 0); 780 lp.customPosition = true; 781 setLayoutParams(lp); 782 } 783 centerAboutIcon(); 784 } 785 786 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 787 int width = getPaddingLeft() + getPaddingRight() + mContent.getDesiredWidth(); 788 // Technically there is no padding at the bottom, but we add space equal to the padding 789 // and have to account for that here. 790 int height = getPaddingTop() + mContent.getDesiredHeight() + mFolderNameHeight; 791 792 int contentWidthSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredWidth(), 793 MeasureSpec.EXACTLY); 794 int contentHeightSpec = MeasureSpec.makeMeasureSpec(mContent.getDesiredHeight(), 795 MeasureSpec.EXACTLY); 796 mContent.measure(contentWidthSpec, contentHeightSpec); 797 798 mFolderName.measure(contentWidthSpec, 799 MeasureSpec.makeMeasureSpec(mFolderNameHeight, MeasureSpec.EXACTLY)); 800 setMeasuredDimension(width, height); 801 } 802 803 private void arrangeChildren(ArrayList<View> list) { 804 int[] vacant = new int[2]; 805 if (list == null) { 806 list = getItemsInReadingOrder(); 807 } 808 mContent.removeAllViews(); 809 810 for (int i = 0; i < list.size(); i++) { 811 View v = list.get(i); 812 mContent.getVacantCell(vacant, 1, 1); 813 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); 814 lp.cellX = vacant[0]; 815 lp.cellY = vacant[1]; 816 ItemInfo info = (ItemInfo) v.getTag(); 817 info.cellX = vacant[0]; 818 info.cellY = vacant[1]; 819 boolean insert = false; 820 mContent.addViewToCellLayout(v, insert ? 0 : -1, (int)info.id, lp, true); 821 LauncherModel.addOrMoveItemInDatabase(mLauncher, info, mInfo.id, 0, 822 info.cellX, info.cellY); 823 } 824 mItemsInvalidated = true; 825 } 826 827 public int getItemCount() { 828 return mContent.getChildrenLayout().getChildCount(); 829 } 830 831 public View getItemAt(int index) { 832 return mContent.getChildrenLayout().getChildAt(index); 833 } 834 835 private void onCloseComplete() { 836 if (mRearrangeOnClose) { 837 setupContentForNumItems(getItemCount()); 838 mRearrangeOnClose = false; 839 } 840 if (getItemCount() <= 1) { 841 replaceFolderWithFinalItem(); 842 } 843 } 844 845 private void replaceFolderWithFinalItem() { 846 ItemInfo finalItem = null; 847 848 if (getItemCount() == 1) { 849 finalItem = mInfo.contents.get(0); 850 } 851 852 // Remove the folder completely 853 CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container, mInfo.screen); 854 cellLayout.removeView(mFolderIcon); 855 if (mFolderIcon instanceof DropTarget) { 856 mDragController.removeDropTarget((DropTarget) mFolderIcon); 857 } 858 mLauncher.removeFolder(mInfo); 859 860 if (finalItem != null) { 861 LauncherModel.addOrMoveItemInDatabase(mLauncher, finalItem, mInfo.container, 862 mInfo.screen, mInfo.cellX, mInfo.cellY); 863 } 864 LauncherModel.deleteItemFromDatabase(mLauncher, mInfo); 865 866 // Add the last remaining child to the workspace in place of the folder 867 if (finalItem != null) { 868 View child = mLauncher.createShortcut(R.layout.application, cellLayout, 869 (ShortcutInfo) finalItem); 870 871 mLauncher.getWorkspace().addInScreen(child, mInfo.container, mInfo.screen, mInfo.cellX, 872 mInfo.cellY, mInfo.spanX, mInfo.spanY); 873 } 874 } 875 876 public void onDrop(DragObject d) { 877 ShortcutInfo item; 878 if (d.dragInfo instanceof ApplicationInfo) { 879 // Came from all apps -- make a copy 880 item = ((ApplicationInfo) d.dragInfo).makeShortcut(); 881 item.spanX = 1; 882 item.spanY = 1; 883 } else { 884 item = (ShortcutInfo) d.dragInfo; 885 } 886 // Dragged from self onto self 887 if (item == mCurrentDragInfo) { 888 ShortcutInfo si = (ShortcutInfo) mCurrentDragView.getTag(); 889 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) mCurrentDragView.getLayoutParams(); 890 si.cellX = lp.cellX = mEmptyCell[0]; 891 si.cellX = lp.cellY = mEmptyCell[1]; 892 mContent.addViewToCellLayout(mCurrentDragView, -1, (int)item.id, lp, true); 893 mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, mCurrentDragView); 894 mItemsInvalidated = true; 895 setupContentDimension(getItemCount()); 896 mSuppressOnAdd = true; 897 } 898 mInfo.add(item); 899 } 900 901 public void onAdd(ShortcutInfo item) { 902 mItemsInvalidated = true; 903 if (mSuppressOnAdd) return; 904 if (!findAndSetEmptyCells(item)) { 905 // The current layout is full, can we expand it? 906 setupContentForNumItems(getItemCount() + 1); 907 findAndSetEmptyCells(item); 908 } 909 createAndAddShortcut(item); 910 LauncherModel.addOrMoveItemInDatabase( 911 mLauncher, item, mInfo.id, 0, item.cellX, item.cellY); 912 } 913 914 public void onRemove(ShortcutInfo item) { 915 mItemsInvalidated = true; 916 if (item == mCurrentDragInfo) return; 917 View v = getViewForInfo(item); 918 mContent.removeView(v); 919 if (mState == STATE_ANIMATING) { 920 mRearrangeOnClose = true; 921 } else { 922 setupContentForNumItems(getItemCount()); 923 } 924 if (getItemCount() <= 1) { 925 replaceFolderWithFinalItem(); 926 } 927 } 928 929 private View getViewForInfo(ShortcutInfo item) { 930 for (int j = 0; j < mContent.getCountY(); j++) { 931 for (int i = 0; i < mContent.getCountX(); i++) { 932 View v = mContent.getChildAt(i, j); 933 if (v.getTag() == item) { 934 return v; 935 } 936 } 937 } 938 return null; 939 } 940 941 public void onItemsChanged() { 942 } 943 public void onTitleChanged(CharSequence title) { 944 } 945 946 public ArrayList<View> getItemsInReadingOrder() { 947 return getItemsInReadingOrder(true); 948 } 949 950 public ArrayList<View> getItemsInReadingOrder(boolean includeCurrentDragItem) { 951 if (mItemsInvalidated) { 952 mItemsInReadingOrder.clear(); 953 for (int j = 0; j < mContent.getCountY(); j++) { 954 for (int i = 0; i < mContent.getCountX(); i++) { 955 View v = mContent.getChildAt(i, j); 956 if (v != null) { 957 ShortcutInfo info = (ShortcutInfo) v.getTag(); 958 if (info != mCurrentDragInfo || includeCurrentDragItem) { 959 mItemsInReadingOrder.add(v); 960 } 961 } 962 } 963 } 964 mItemsInvalidated = false; 965 } 966 return mItemsInReadingOrder; 967 } 968 969 public void getLocationInDragLayer(int[] loc) { 970 mLauncher.getDragLayer().getLocationInDragLayer(this, loc); 971 } 972} 973