diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 919c60a344..b1db7e8db2 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -3792,7 +3792,7 @@ public class Workspace extends PagedView ItemInfo info = (ItemInfo) item.getTag(); if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { FolderIcon folder = (FolderIcon) item; - ArrayList folderChildren = folder.getFolder().getItemsInReadingOrder(); + ArrayList folderChildren = folder.getFolder().getItemsInRankOrder(); // map over all the children in the folder final int childCount = folderChildren.size(); for (int childIdx = 0; childIdx < childCount; childIdx++) { diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index 34335330be..6cd086b387 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -369,7 +369,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme Folder folder = Folder.getOpen(mLauncher); if (folder != null) { - if (!folder.getItemsInReadingOrder().contains(item)) { + if (!folder.getItemsInRankOrder().contains(item)) { folder.close(true); folder = null; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 3c7c698103..f68b394c1b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -133,7 +133,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC private final Alarm mOnScrollHintAlarm = new Alarm(); @Thunk final Alarm mScrollPauseAlarm = new Alarm(); - @Thunk final ArrayList mItemsInReadingOrder = new ArrayList(); + @Thunk final ArrayList mItemsInRankOrder = new ArrayList<>(); private AnimatorSet mCurrentAnimator; @@ -284,7 +284,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC if (tag instanceof ShortcutInfo) { ShortcutInfo item = (ShortcutInfo) tag; - mEmptyCellRank = item.rank; + mEmptyCellRank = mContent.getReadingOrderPosForRank(item.rank); mCurrentDragView = v; mDragController.addDragListener(this); @@ -705,7 +705,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } public void beginExternalDrag() { - mEmptyCellRank = mContent.allocateRankForNewItem(); + mEmptyCellRank = mContent.getReadingOrderPosForRank(mContent.allocateRankForNewItem()); mIsExternalDrag = true; mDragInProgress = true; @@ -997,7 +997,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC ShortcutInfo info = (ShortcutInfo) d.dragInfo; View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info) ? mCurrentDragView : mContent.createNewView(info); - ArrayList views = getItemsInReadingOrder(); + ArrayList views = getItemsInRankOrder(); views.add(info.rank, icon); mContent.arrangeChildren(views, views.size()); mItemsInvalidated = true; @@ -1072,7 +1072,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } private void updateItemLocationsInDatabaseBatch() { - ArrayList list = getItemsInReadingOrder(); + ArrayList list = getItemsInRankOrder(); ArrayList items = new ArrayList(); for (int i = 0; i < list.size(); i++) { View v = list.get(i); @@ -1231,7 +1231,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC * otherwise it is ignored. */ public void rearrangeChildren(int itemCount) { - ArrayList views = getItemsInReadingOrder(); + ArrayList views = getItemsInRankOrder(); mContent.arrangeChildren(views, Math.max(itemCount, views.size())); mItemsInvalidated = true; } @@ -1376,24 +1376,20 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC View currentDragView; if (mIsExternalDrag) { - currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank); - // Actually move the item in the database if it was an external drag. Call this // before creating the view, so that ShortcutInfo is updated appropriately. - mLauncher.getModelWriter().addOrMoveItemInDatabase( - si, mInfo.id, 0, si.cellX, si.cellY); - - // We only need to update the locations if it doesn't get handled in - // #onDropCompleted. - if (d.dragSource != this) { - updateItemLocationsInDatabaseBatch(); - } - mIsExternalDrag = false; - } else { - currentDragView = mCurrentDragView; - mContent.addViewForRank(currentDragView, si, mEmptyCellRank); + mLauncher.getModelWriter().addOrMoveItemInDatabase(si, mInfo.id, 0, si.cellX, si.cellY); } + currentDragView = mIsExternalDrag + ? mContent.createNewView(si) + : mCurrentDragView; + mIsExternalDrag = false; + + // Note: addViewForRankDuringDragAndDrop handles rearranging the children. + mContent.addViewForRankDuringDragAndDrop(currentDragView, si, mEmptyCellRank); + mItemsInvalidated = true; + if (d.dragView.hasDrawn()) { // Temporarily reset the scale such that the animation target gets calculated // correctly. @@ -1410,9 +1406,6 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC currentDragView.setVisibility(VISIBLE); } - mItemsInvalidated = true; - rearrangeChildren(); - // Temporarily suppress the listener, as we did all the work already here. try (SuppressInfoChanges s = new SuppressInfoChanges()) { mInfo.add(si, false); @@ -1450,7 +1443,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mLauncher.getModelWriter().addOrMoveItemInDatabase(item, mInfo.id, 0, item.cellX, item.cellY); - ArrayList items = new ArrayList<>(getItemsInReadingOrder()); + ArrayList items = new ArrayList<>(getItemsInRankOrder()); items.add(rank, view); mContent.arrangeChildren(items, items.size()); mItemsInvalidated = true; @@ -1497,24 +1490,34 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC public void onTitleChanged(CharSequence title) { } - public ArrayList getItemsInReadingOrder() { + public ArrayList getItemsInRankOrder() { if (mItemsInvalidated) { - mItemsInReadingOrder.clear(); - mContent.iterateOverItems(new ItemOperator() { + mItemsInRankOrder.clear(); + mItemsInRankOrder.addAll(getItemsInReadingOrder()); + mItemsInRankOrder.sort(VIEW_RANK_COMPARATOR); - @Override - public boolean evaluate(ItemInfo info, View view) { - mItemsInReadingOrder.add(view); - return false; - } - }); mItemsInvalidated = false; } - return mItemsInReadingOrder; + return mItemsInRankOrder; + } + + /** + * This is an expensive call. Consider using {@link #getItemsInRankOrder()} instead. + */ + public ArrayList getItemsInReadingOrder() { + final ArrayList itemsInReadingOrder = new ArrayList<>(); + mContent.iterateOverItems(new ItemOperator() { + @Override + public boolean evaluate(ItemInfo info, View view) { + itemsInReadingOrder.add(view); + return false; + } + }); + return itemsInReadingOrder; } public List getItemsOnCurrentPage() { - ArrayList allItems = getItemsInReadingOrder(); + ArrayList allItems = getItemsInRankOrder(); int currentPage = mContent.getCurrentPage(); int lastPage = mContent.getPageCount() - 1; int totalItemsInFolder = allItems.size(); @@ -1622,6 +1625,13 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } }; + public static final Comparator VIEW_RANK_COMPARATOR = new Comparator() { + @Override + public int compare(View lhs, View rhs) { + return ITEM_POS_COMPARATOR.compare((ItemInfo) lhs.getTag(), (ItemInfo) rhs.getTag()); + } + }; + /** * Temporary resource held while we don't want to handle info changes */ diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 1cc285ea51..3a0e71fa59 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -575,7 +575,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { mPreviewVerifier.setFolderInfo(mFolder.getInfo()); List itemsToDisplay = new ArrayList<>(); - List allItems = mFolder.getItemsInReadingOrder(); + List allItems = mFolder.getItemsInRankOrder(); int numItems = allItems.size(); for (int rank = 0; rank < numItems; ++rank) { if (mPreviewVerifier.isItemInPreview(rank)) { diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java index de962b021d..d0d8e79d53 100644 --- a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -25,40 +25,15 @@ import com.android.launcher3.config.FeatureFlags; */ public class FolderIconPreviewVerifier { - private final int mMaxGridCountX; - private final int mMaxGridCountY; - private final int mMaxItemsPerPage; - private final int[] mGridSize = new int[2]; - - private int mGridCountX; - private boolean mDisplayingUpperLeftQuadrant = false; - public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { - mMaxGridCountX = profile.numFolderColumns; - mMaxGridCountY = profile.numFolderRows; - mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; + // b/37570804 } public void setFolderInfo(FolderInfo info) { - int numItemsInFolder = info.contents.size(); - mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION - && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON - && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW; - - if (mDisplayingUpperLeftQuadrant) { - FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX, - mMaxGridCountY, mMaxItemsPerPage, mGridSize); - mGridCountX = mGridSize[0]; - } + // b/37570804 } public boolean isItemInPreview(int rank) { - if (mDisplayingUpperLeftQuadrant) { - // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder. - int col = rank % mGridCountX; - int row = rank / mGridCountX; - return col < 2 && row < 2; - } return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW; } } diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index f62568f78c..21631fa24c 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -201,18 +201,29 @@ public class FolderPagedView extends PagedView { } public void allocateSpaceForRank(int rank) { - ArrayList views = new ArrayList<>(mFolder.getItemsInReadingOrder()); + ArrayList views = new ArrayList<>(mFolder.getItemsInRankOrder()); views.add(rank, null); arrangeChildren(views, views.size(), false); } + private ArrayList createListWithViewAtPos(ArrayList list, View v, int position) { + ArrayList newList = new ArrayList<>(list.size() + 1); + newList.addAll(list); + newList.add(position, v); + return newList; + } + /** - * Create space for a new item at the end, and returns the rank for that item. + * Create space for a new item and returns the rank for that item. * Also sets the current page to the last page. */ public int allocateRankForNewItem() { - int rank = getItemCount(); - allocateSpaceForRank(rank); + ArrayList rankOrder = mFolder.getItemsInRankOrder(); + int rank = rankOrder.size(); + + ArrayList views = createListWithViewAtPos(rankOrder, null, rank); + arrangeChildren(views, views.size(), false); + setCurrentPage(rank / mMaxItemsPerPage); return rank; } @@ -229,20 +240,59 @@ public class FolderPagedView extends PagedView { * related attributes. It assumes that {@param item} is already attached to the view. */ public void addViewForRank(View view, ShortcutInfo item, int rank) { - int pagePos = rank % mMaxItemsPerPage; - int pageNo = rank / mMaxItemsPerPage; + updateShortcutInfoWithRank(item, rank); - item.rank = rank; - item.cellX = pagePos % mGridCountX; - item.cellY = pagePos / mGridCountX; + ArrayList views = createListWithViewAtPos(mFolder.getItemsInRankOrder(), view, rank); + arrangeChildren(views, views.size(), false); + } + + /** + * Similar to {@link #addViewForRank(View, ShortcutInfo, int)}}, but specific to real time + * reorder. + * + * The difference here is that during real time reorder, we are moving the Views in a contained + * order. + */ + public void addViewForRankDuringReorder(View view, ShortcutInfo item, int rank) { + updateShortcutInfoWithRank(item, rank); CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams(); lp.cellX = item.cellX; lp.cellY = item.cellY; + + int pageNo = rank / mMaxItemsPerPage; getPageAt(pageNo).addViewToCellLayout( view, -1, mFolder.mLauncher.getViewIdForItem(item), lp, true); } + /** + * Similar to {@link #addViewForRank(View, ShortcutInfo, int)}, but specific to drag and drop. + * + * The difference is that we handle the drag and drop by adjusting the reading order of the + * children, rather than based on their rank. + */ + public void addViewForRankDuringDragAndDrop(View view, ShortcutInfo item, int readingRank) { + updateShortcutInfoWithRank(item, readingRank); + + ArrayList views = createListWithViewAtPos(mFolder.getItemsInReadingOrder(), view, + readingRank); + + int numItems = views.size(); + ArrayList rankOrder = new ArrayList<>(numItems); + for (int i = 0; i < numItems; ++i) { + rankOrder.add(views.get(getReadingOrderPosForRank(i))); + } + + arrangeChildren(rankOrder, numItems, false); + } + + private void updateShortcutInfoWithRank(ShortcutInfo info, int rank) { + info.rank = rank; + getCellXYPositionForRank(rank, sTmpArray); + info.cellX = sTmpArray[0]; + info.cellY = sTmpArray[1]; + } + @SuppressLint("InflateParams") public View createNewView(ShortcutInfo item) { final BubbleTextView textView = (BubbleTextView) mInflater.inflate( @@ -310,18 +360,19 @@ public class FolderPagedView extends PagedView { * It essentially removes all views from all the pages and then adds them again in appropriate * page. * - * @param list the ordered list of children. + * @param rankOrderedList the rank-ordered list of children. * @param itemCount if greater than the total children count, empty spaces are left * at the end, otherwise it is ignored. * */ - public void arrangeChildren(ArrayList list, int itemCount) { - arrangeChildren(list, itemCount, true); + public void arrangeChildren(ArrayList rankOrderedList, int itemCount) { + arrangeChildren(rankOrderedList, itemCount, true); } @SuppressLint("RtlHardcoded") - private void arrangeChildren(ArrayList list, int itemCount, boolean saveChanges) { - ArrayList pages = new ArrayList<>(); + private void arrangeChildren(ArrayList rankOrderedList, int itemCount, + boolean saveChanges) { + ArrayList pages = new ArrayList(); for (int i = 0; i < getChildCount(); i++) { CellLayout page = (CellLayout) getChildAt(i); page.removeAllViews(); @@ -339,7 +390,7 @@ public class FolderPagedView extends PagedView { Launcher.getLauncher(getContext()).getDeviceProfile().inv); rank = 0; for (int i = 0; i < itemCount; i++) { - View v = list.size() > i ? list.get(i) : null; + View v = rankOrderedList.size() > i ? rankOrderedList.get(i) : null; if (currentPage == null || position >= mMaxItemsPerPage) { // Next page if (pageItr.hasNext()) { @@ -352,8 +403,10 @@ public class FolderPagedView extends PagedView { if (v != null) { CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams(); - newX = position % mGridCountX; - newY = position / mGridCountX; + getCellXYPositionForRank(rank, sTmpArray); + newX = sTmpArray[0]; + newY = sTmpArray[1]; + ItemInfo info = (ItemInfo) v.getTag(); if (info.cellX != newX || info.cellY != newY || info.rank != rank) { info.cellX = newX; @@ -651,7 +704,7 @@ public class FolderPagedView extends PagedView { if (v != null) { if (pageToAnimate != p) { page.removeView(v); - addViewForRank(v, (ShortcutInfo) v.getTag(), moveStart); + addViewForRankDuringReorder(v, (ShortcutInfo) v.getTag(), moveStart); } else { // Do a fake animation before removing it. final int newRank = moveStart; @@ -664,14 +717,14 @@ public class FolderPagedView extends PagedView { mPendingAnimations.remove(v); v.setTranslationX(oldTranslateX); ((CellLayout) v.getParent().getParent()).removeView(v); - addViewForRank(v, (ShortcutInfo) v.getTag(), newRank); + addViewForRankDuringReorder(v, (ShortcutInfo) v.getTag(), newRank); } }; v.animate() - .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth()) - .setDuration(REORDER_ANIMATION_DURATION) - .setStartDelay(0) - .withEndAction(endAction); + .translationXBy((direction > 0 ^ mIsRtl) ? -v.getWidth() : v.getWidth()) + .setDuration(REORDER_ANIMATION_DURATION) + .setStartDelay(0) + .withEndAction(endAction); mPendingAnimations.put(v, endAction); } } @@ -701,4 +754,88 @@ public class FolderPagedView extends PagedView { public int itemsPerPage() { return mMaxItemsPerPage; } + + /** + * Returns the reading order position for a given rank. + * + * ie. For the permutation below, rank 0 returns 0, rank 1 returns 1, rank 4 returns 2, + * rank 2 returns 3, rank 3 returns 4, rank 5 returns 5. + * + * R0 R1 R4 + * R2 R3 R5 + */ + public int getReadingOrderPosForRank(int rank) { + if (rank >= mMaxItemsPerPage) { + return rank; + } + + getCellXYPositionForRank(rank, sTmpArray); + return sTmpArray[0] + (mGridCountX * sTmpArray[1]); + } + + /** + * Returns the cell XY position for a Folder item with the given rank. + */ + public void getCellXYPositionForRank(int rank, int[] outXY) { + boolean onFirstPage = rank < mMaxItemsPerPage; + + if (onFirstPage && mGridCountX == 3) { + outXY[0] = FolderPermutation.THREE_COLS[rank][0]; + outXY[1] = FolderPermutation.THREE_COLS[rank][1]; + } else if (onFirstPage && mGridCountX == 4) { + outXY[0] = FolderPermutation.FOUR_COLS[rank][0]; + outXY[1] = FolderPermutation.FOUR_COLS[rank][1]; + } else if (onFirstPage && mGridCountX == 5) { + outXY[0] = FolderPermutation.FIVE_COLS[rank][0]; + outXY[1] = FolderPermutation.FIVE_COLS[rank][1]; + } else { + outXY[0] = (rank % mMaxItemsPerPage) % mGridCountX; + outXY[1] = (rank % mMaxItemsPerPage) / mGridCountX; + } + } + + /** + * Provides the mapping between a folder item's rank and its cell location, based on the + * number of columns. + * + * We use this mapping, rather than the regular reading order, to preserve the items in the + * upper left quadrant of the Folder. This allows a smooth transition between the FolderIcon + * and the opened Folder. + * + * TODO: We will replace these hard coded tables with an algorithm b/62986680 + */ + private static class FolderPermutation { + /** + * R0 R1 R4 + * R2 R3 R5 + * R6 R7 R8 + */ + static final int[][] THREE_COLS = new int[][] { + {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {0, 2}, {1, 2}, {2, 2} + }; + + /** + * R0 R1 R4 R6 + * R2 R3 R5 R7 + * R8 R9 R10 R11 + * R12 R13 R14 R15 + */ + static final int[][] FOUR_COLS = new int[][] { + {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {3, 0}, {3, 1}, {0, 2}, {1, 2}, + {2, 2}, {3, 2}, {0, 3}, {1, 3}, {2, 3}, {3, 3} + }; + + /** + * R0 R1 R4 R6 R12 + * R2 R3 R5 R7 R13 + * R8 R9 R10 R11 R14 + * R15 R16 R17 R18 R19 + * R20 R21 R22 R23 R24 + */ + static final int[][] FIVE_COLS = new int[][] { + {0, 0}, {1, 0}, {0, 1}, {1, 1}, {2, 0}, {2, 1}, {3, 0}, {3, 1}, {0, 2}, {1, 2}, + {2, 2}, {3, 2}, {4, 0}, {4, 1}, {4, 2}, {0, 3}, {1, 3}, {2, 3}, {3, 3}, {4, 3}, + {0, 4}, {1, 4}, {2, 4}, {3, 4}, {4, 4} + }; + } }