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