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