Lazily binding folder pages and icons

Folders are bound before they are opened and unbound on close. This
allows us to recycle the views in folders

Bug: 139051851
Change-Id: Ic1ed3265c0f583af54d73dab6f2751bc95266ea9
This commit is contained in:
Sunny Goyal 2019-08-16 11:59:55 -07:00
parent 72fddba837
commit ac6f69f78d
9 changed files with 304 additions and 285 deletions

View File

@ -20,4 +20,5 @@
style="@style/BaseIcon" style="@style/BaseIcon"
android:textColor="?attr/folderTextColor" android:textColor="?attr/folderTextColor"
android:includeFontPadding="false" android:includeFontPadding="false"
android:hapticFeedbackEnabled="false"
launcher:iconDisplay="folder" /> launcher:iconDisplay="folder" />

View File

@ -93,13 +93,6 @@ public class FolderInfo extends ItemInfo {
itemsChanged(animate); itemsChanged(animate);
} }
public void setTitle(CharSequence title) {
this.title = title;
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onTitleChanged(title);
}
}
@Override @Override
public void onAddToDatabase(ContentWriter writer) { public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer); super.onAddToDatabase(writer);
@ -121,18 +114,10 @@ public class FolderInfo extends ItemInfo {
} }
} }
public void prepareAutoUpdate() {
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).prepareAutoUpdate();
}
}
public interface FolderListener { public interface FolderListener {
public void onAdd(WorkspaceItemInfo item, int rank); public void onAdd(WorkspaceItemInfo item, int rank);
public void onRemove(WorkspaceItemInfo item); public void onRemove(WorkspaceItemInfo item);
public void onTitleChanged(CharSequence title);
public void onItemsChanged(boolean animate); public void onItemsChanged(boolean animate);
public void prepareAutoUpdate();
} }
public boolean hasOption(int optionFlag) { public boolean hasOption(int optionFlag) {

View File

@ -2340,6 +2340,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
// override the previous page so we don't log the page switch. // override the previous page so we don't log the page switch.
mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */); mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */);
// Cache one page worth of icons
getViewCache().setCacheSize(R.layout.folder_application,
mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
getViewCache().setCacheSize(R.layout.folder_page, 2);
TraceHelper.endSection("finishBindingItems"); TraceHelper.endSection("finishBindingItems");
} }

View File

@ -92,7 +92,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.Executors; import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PackageUserKey;
@ -134,9 +133,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
private static final int DEFAULT_PAGE = 0; private static final int DEFAULT_PAGE = 0;
public static final boolean MAP_NO_RECURSE = false;
public static final boolean MAP_RECURSE = true;
private LayoutTransition mLayoutTransition; private LayoutTransition mLayoutTransition;
@Thunk final WallpaperManager mWallpaperManager; @Thunk final WallpaperManager mWallpaperManager;
@ -2809,7 +2805,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
* Removes all folder listeners * Removes all folder listeners
*/ */
public void removeFolderListeners() { public void removeFolderListeners() {
mapOverItems(false, new ItemOperator() { mapOverItems(new ItemOperator() {
@Override @Override
public boolean evaluate(ItemInfo info, View view) { public boolean evaluate(ItemInfo info, View view) {
if (view instanceof FolderIcon) { if (view instanceof FolderIcon) {
@ -2961,7 +2957,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
public View getFirstMatch(final ItemOperator operator) { public View getFirstMatch(final ItemOperator operator) {
final View[] value = new View[1]; final View[] value = new View[1];
mapOverItems(MAP_NO_RECURSE, new ItemOperator() { mapOverItems(new ItemOperator() {
@Override @Override
public boolean evaluate(ItemInfo info, View v) { public boolean evaluate(ItemInfo info, View v) {
if (operator.evaluate(info, v)) { if (operator.evaluate(info, v)) {
@ -2984,7 +2980,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
final View[] matches = new View[operators.length]; final View[] matches = new View[operators.length];
// For efficiency, the outer loop should be CellLayout. // For efficiency, the outer loop should be CellLayout.
for (CellLayout cellLayout : cellLayouts) { for (CellLayout cellLayout : cellLayouts) {
mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> { mapOverCellLayout(cellLayout, (info, v) -> {
for (int i = 0; i < operators.length; ++i) { for (int i = 0; i < operators.length; ++i) {
if (matches[i] == null && operators[i].evaluate(info, v)) { if (matches[i] == null && operators[i].evaluate(info, v)) {
matches[i] = v; matches[i] = v;
@ -3009,7 +3005,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
} }
void clearDropTargets() { void clearDropTargets() {
mapOverItems(MAP_NO_RECURSE, new ItemOperator() { mapOverItems(new ItemOperator() {
@Override @Override
public boolean evaluate(ItemInfo info, View v) { public boolean evaluate(ItemInfo info, View v) {
if (v instanceof DropTarget) { if (v instanceof DropTarget) {
@ -3054,10 +3050,12 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
} else if (itemToRemove.container >= 0) { } else if (itemToRemove.container >= 0) {
// The item may belong to a folder. // The item may belong to a folder.
View parent = idToViewMap.get(itemToRemove.container); View parent = idToViewMap.get(itemToRemove.container);
if (parent != null) { if (parent instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) parent.getTag(); FolderInfo folderInfo = (FolderInfo) parent.getTag();
folderInfo.prepareAutoUpdate();
folderInfo.remove((WorkspaceItemInfo) itemToRemove, false); folderInfo.remove((WorkspaceItemInfo) itemToRemove, false);
if (((FolderIcon) parent).getFolder().isOpen()) {
((FolderIcon) parent).getFolder().close(false /* animate */);
}
} }
} }
} }
@ -3081,18 +3079,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
/** /**
* Map the operator over the shortcuts and widgets, return the first-non-null value. * Map the operator over the shortcuts and widgets, return the first-non-null value.
* *
* @param recurse true: iterate over folder children. false: op get the folders themselves.
* @param op the operator to map over the shortcuts * @param op the operator to map over the shortcuts
*/ */
public void mapOverItems(boolean recurse, ItemOperator op) { public void mapOverItems(ItemOperator op) {
for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
if (mapOverCellLayout(recurse, layout, op)) { if (mapOverCellLayout(layout, op)) {
return; return;
} }
} }
} }
private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) {
// TODO(b/128460496) Potential race condition where layout is not yet loaded // TODO(b/128460496) Potential race condition where layout is not yet loaded
if (layout == null) { if (layout == null) {
return false; return false;
@ -3102,103 +3099,68 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
final int itemCount = container.getChildCount(); final int itemCount = container.getChildCount();
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
View item = container.getChildAt(itemIdx); View item = container.getChildAt(itemIdx);
ItemInfo info = (ItemInfo) item.getTag(); if (op.evaluate((ItemInfo) item.getTag(), item)) {
if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) {
FolderIcon folder = (FolderIcon) item;
ArrayList<View> folderChildren = folder.getFolder().getIconsInReadingOrder();
// map over all the children in the folder
final int childCount = folderChildren.size();
for (int childIdx = 0; childIdx < childCount; childIdx++) {
View child = folderChildren.get(childIdx);
info = (ItemInfo) child.getTag();
if (op.evaluate(info, child)) {
return true; return true;
} }
} }
} else {
if (op.evaluate(info, item)) {
return true;
}
}
}
return false; return false;
} }
void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) { void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
int total = shortcuts.size(); final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
final HashSet<WorkspaceItemInfo> updates = new HashSet<>(total); ItemOperator op = (info, v) -> {
final IntSet folderIds = new IntSet(); if (v instanceof BubbleTextView && updates.contains(info)) {
for (int i = 0; i < total; i++) {
WorkspaceItemInfo s = shortcuts.get(i);
updates.add(s);
folderIds.add(s.container);
}
mapOverItems(MAP_RECURSE, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView &&
updates.contains(info)) {
WorkspaceItemInfo si = (WorkspaceItemInfo) info; WorkspaceItemInfo si = (WorkspaceItemInfo) info;
BubbleTextView shortcut = (BubbleTextView) v; BubbleTextView shortcut = (BubbleTextView) v;
Drawable oldIcon = shortcut.getIcon(); Drawable oldIcon = shortcut.getIcon();
boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable) boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
&& ((PreloadIconDrawable) oldIcon).hasNotCompleted(); && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState); shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
((FolderIcon) v).updatePreviewItems(updates::contains);
} }
// process all the shortcuts
return false;
}
});
// Update folder icons // Iterate all items
mapOverItems(MAP_NO_RECURSE, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof FolderInfo && folderIds.contains(info.id)) {
((FolderInfo) info).itemsChanged(false);
}
// process all the shortcuts
return false; return false;
};
mapOverItems(op);
Folder openFolder = Folder.getOpen(mLauncher);
if (openFolder != null) {
openFolder.iterateOverItems(op);
} }
});
} }
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) { public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null); final PackageUserKey packageUserKey = new PackageUserKey(null, null);
final IntSet folderIds = new IntSet(); Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
mapOverItems(MAP_RECURSE, new ItemOperator() { || updatedDots.test(packageUserKey);
@Override
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
if (!packageUserKey.updateFromItemInfo(info)
|| updatedDots.test(packageUserKey)) {
((BubbleTextView) v).applyDotState(info, true /* animate */);
folderIds.add(info.container);
}
}
// process all the shortcuts
return false;
}
});
// Update folder icons ItemOperator op = (info, v) -> {
mapOverItems(MAP_NO_RECURSE, new ItemOperator() { if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
@Override if (matcher.test(info)) {
public boolean evaluate(ItemInfo info, View v) { ((BubbleTextView) v).applyDotState(info, true /* animate */);
if (info instanceof FolderInfo && folderIds.contains(info.id) }
&& v instanceof FolderIcon) { } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
FolderInfo fi = (FolderInfo) info;
if (fi.contents.stream().anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo(); FolderDotInfo folderDotInfo = new FolderDotInfo();
for (WorkspaceItemInfo si : ((FolderInfo) info).contents) { for (WorkspaceItemInfo si : fi.contents) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
} }
((FolderIcon) v).setDotInfo(folderDotInfo); ((FolderIcon) v).setDotInfo(folderDotInfo);
} }
}
// process all the shortcuts // process all the shortcuts
return false; return false;
};
mapOverItems(op);
Folder folder = Folder.getOpen(mLauncher);
if (folder != null) {
folder.iterateOverItems(op);
} }
});
} }
public void removeAbandonedPromise(String packageName, UserHandle user) { public void removeAbandonedPromise(String packageName, UserHandle user) {
@ -3210,9 +3172,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
} }
public void updateRestoreItems(final HashSet<ItemInfo> updates) { public void updateRestoreItems(final HashSet<ItemInfo> updates) {
mapOverItems(MAP_RECURSE, new ItemOperator() { ItemOperator op = (info, v) -> {
@Override
public boolean evaluate(ItemInfo info, View v) {
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
&& updates.contains(info)) { && updates.contains(info)) {
((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */);
@ -3220,11 +3180,17 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
&& info instanceof LauncherAppWidgetInfo && info instanceof LauncherAppWidgetInfo
&& updates.contains(info)) { && updates.contains(info)) {
((PendingAppWidgetHostView) v).applyState(); ((PendingAppWidgetHostView) v).applyState();
} else if (v instanceof FolderIcon && info instanceof FolderInfo) {
((FolderIcon) v).updatePreviewItems(updates::contains);
} }
// process all the shortcuts // process all the shortcuts
return false; return false;
};
mapOverItems(op);
Folder folder = Folder.getOpen(mLauncher);
if (folder != null) {
folder.iterateOverItems(op);
} }
});
} }
public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) { public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
@ -3248,7 +3214,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
} else { } else {
// widgetRefresh will automatically run when the packages are updated. // widgetRefresh will automatically run when the packages are updated.
// For now just update the progress bars // For now just update the progress bars
mapOverItems(MAP_NO_RECURSE, new ItemOperator() { mapOverItems(new ItemOperator() {
@Override @Override
public boolean evaluate(ItemInfo info, View view) { public boolean evaluate(ItemInfo info, View view) {
if (view instanceof PendingAppWidgetHostView if (view instanceof PendingAppWidgetHostView
@ -3372,7 +3338,7 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mRefreshPending = false; mRefreshPending = false;
ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size()); ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
mapOverItems(MAP_NO_RECURSE, (info, view) -> { mapOverItems((info, view) -> {
if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
views.add((PendingAppWidgetHostView) view); views.add((PendingAppWidgetHostView) view);
} }

View File

@ -170,8 +170,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
private boolean mDeleteFolderOnDropCompleted = false; private boolean mDeleteFolderOnDropCompleted = false;
private boolean mSuppressFolderDeletion = false; private boolean mSuppressFolderDeletion = false;
private boolean mItemAddedBackToSelfViaIcon = false; private boolean mItemAddedBackToSelfViaIcon = false;
@Thunk float mFolderIconPivotX;
@Thunk float mFolderIconPivotY;
private boolean mIsEditingName = false; private boolean mIsEditingName = false;
@ViewDebug.ExportedProperty(category = "launcher") @ViewDebug.ExportedProperty(category = "launcher")
@ -310,7 +308,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
// Convert to a string here to ensure that no other state associated with the text field // Convert to a string here to ensure that no other state associated with the text field
// gets saved. // gets saved.
String newTitle = mFolderName.getText().toString(); String newTitle = mFolderName.getText().toString();
mInfo.setTitle(newTitle); mInfo.title = newTitle;
mFolderIcon.onTitleChanged(newTitle);
mLauncher.getModelWriter().updateItemInDatabase(mInfo); mLauncher.getModelWriter().updateItemInDatabase(mInfo);
if (TextUtils.isEmpty(mInfo.title)) { if (TextUtils.isEmpty(mInfo.title)) {
@ -385,7 +384,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
ArrayList<WorkspaceItemInfo> children = info.contents; ArrayList<WorkspaceItemInfo> children = info.contents;
Collections.sort(children, ITEM_POS_COMPARATOR); Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(); updateItemLocationsInDatabaseBatch();
mContent.bindItems(children);
DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
if (lp == null) { if (lp == null) {
@ -393,13 +391,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
lp.customPosition = true; lp.customPosition = true;
setLayoutParams(lp); setLayoutParams(lp);
} }
centerAboutIcon();
mItemsInvalidated = true; mItemsInvalidated = true;
updateTextViewFocus();
mInfo.addListener(this); mInfo.addListener(this);
if (TextUtils.isEmpty(mInfo.title)) { if (!TextUtils.isEmpty(mInfo.title)) {
mFolderName.setText(mInfo.title); mFolderName.setText(mInfo.title);
mFolderName.setHint(null); mFolderName.setHint(null);
} else { } else {
@ -408,12 +403,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
} }
// In case any children didn't come across during loading, clean up the folder accordingly // In case any children didn't come across during loading, clean up the folder accordingly
mFolderIcon.post(new Runnable() { mFolderIcon.post(() -> {
public void run() {
if (getItemCount() <= 1) { if (getItemCount() <= 1) {
replaceFolderWithFinalItem(); replaceFolderWithFinalItem();
} }
}
}); });
} }
@ -472,18 +465,50 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
return folderCount >= MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION; return folderCount >= MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION;
} }
/**
* Opens the folder as part of a drag operation
*/
public void beginExternalDrag() {
mIsExternalDrag = true;
mDragInProgress = true;
// Since this folder opened by another controller, it might not get onDrop or
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.contents);
mEmptyCellRank = items.size();
items.add(null); // Add an empty spot at the end
animateOpen(items, mEmptyCellRank / mContent.itemsPerPage());
}
/** /**
* Opens the user folder described by the specified tag. The opening of the folder * Opens the user folder described by the specified tag. The opening of the folder
* is animated relative to the specified View. If the View is null, no animation * is animated relative to the specified View. If the View is null, no animation
* is played. * is played.
*/ */
public void animateOpen() { public void animateOpen() {
animateOpen(mInfo.contents, 0);
}
/**
* Opens the user folder described by the specified tag. The opening of the folder
* is animated relative to the specified View. If the View is null, no animation
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
Folder openFolder = getOpen(mLauncher); Folder openFolder = getOpen(mLauncher);
if (openFolder != null && openFolder != this) { if (openFolder != null && openFolder != this) {
// Close any open folder before opening a folder. // Close any open folder before opening a folder.
openFolder.close(true); openFolder.close(true);
} }
mContent.bindItems(items);
centerAboutIcon();
mItemsInvalidated = true;
updateTextViewFocus();
mIsOpen = true; mIsOpen = true;
DragLayer dragLayer = mLauncher.getDragLayer(); DragLayer dragLayer = mLauncher.getDragLayer();
@ -500,10 +525,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
} }
mContent.completePendingPageChanges(); mContent.completePendingPageChanges();
if (!mDragInProgress) { mContent.snapToPageImmediately(pageNo);
// Open on the first page.
mContent.snapToPageImmediately(0);
}
// This is set to true in close(), but isn't reset to false until onDropCompleted(). This // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
// leads to an inconsistent state if you drag out of the folder and drag back in without // leads to an inconsistent state if you drag out of the folder and drag back in without
@ -574,16 +596,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
mContent.verifyVisibleHighResIcons(mContent.getNextPage()); mContent.verifyVisibleHighResIcons(mContent.getNextPage());
} }
public void beginExternalDrag() {
mEmptyCellRank = mContent.allocateRankForNewItem();
mIsExternalDrag = true;
mDragInProgress = true;
// Since this folder opened by another controller, it might not get onDrop or
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
}
@Override @Override
protected boolean isOfType(int type) { protected boolean isOfType(int type) {
return (type & TYPE_FOLDER) != 0; return (type & TYPE_FOLDER) != 0;
@ -668,6 +680,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
} else if (mDragInProgress) { } else if (mDragInProgress) {
mDeleteFolderOnDropCompleted = true; mDeleteFolderOnDropCompleted = true;
} }
} else if (!mDragInProgress) {
mContent.unbindItems();
} }
mSuppressFolderDeletion = false; mSuppressFolderDeletion = false;
clearDragInfo(); clearDragInfo();
@ -953,24 +967,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
setPivotX(folderPivotX); setPivotX(folderPivotX);
setPivotY(folderPivotY); setPivotY(folderPivotY);
mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() *
(1.0f * folderPivotX / width));
mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() *
(1.0f * folderPivotY / height));
lp.width = width; lp.width = width;
lp.height = height; lp.height = height;
lp.x = left; lp.x = left;
lp.y = top; lp.y = top;
} }
public float getPivotXForIconAnimation() {
return mFolderIconPivotX;
}
public float getPivotYForIconAnimation() {
return mFolderIconPivotY;
}
private int getContentAreaHeight() { private int getContentAreaHeight() {
DeviceProfile grid = mLauncher.getDeviceProfile(); DeviceProfile grid = mLauncher.getDeviceProfile();
int maxContentAreaHeight = grid.availableHeightPx int maxContentAreaHeight = grid.availableHeightPx
@ -1031,7 +1033,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
} }
public int getItemCount() { public int getItemCount() {
return mContent.getItemCount(); return mInfo.contents.size();
} }
@Thunk void replaceFolderWithFinalItem() { @Thunk void replaceFolderWithFinalItem() {
@ -1039,7 +1041,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
Runnable onCompleteRunnable = new Runnable() { Runnable onCompleteRunnable = new Runnable() {
@Override @Override
public void run() { public void run() {
int itemCount = mInfo.contents.size(); int itemCount = getItemCount();
if (itemCount <= 1) { if (itemCount <= 1) {
View newIcon = null; View newIcon = null;
@ -1116,6 +1118,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
return false; return false;
} }
}); });
} else {
setOnKeyListener(null);
} }
} }
@ -1242,9 +1246,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
item.cellY); item.cellY);
updateItemLocationsInDatabaseBatch(); updateItemLocationsInDatabaseBatch();
ArrayList<View> items = new ArrayList<>(getIconsInReadingOrder()); if (mContent.areViewsBound()) {
items.add(rank, mContent.createAndAddViewForRank(item, rank)); mContent.createAndAddViewForRank(item, rank);
mContent.arrangeChildren(items); }
mItemsInvalidated = true; mItemsInvalidated = true;
} }
@ -1275,12 +1279,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
updateTextViewFocus(); updateTextViewFocus();
} }
@Override /**
public void prepareAutoUpdate() { * Utility methods to iterate over items of the view
close(false); */
} public void iterateOverItems(ItemOperator op) {
mContent.iterateOverItems(op);
public void onTitleChanged(CharSequence title) {
} }
/** /**
@ -1289,14 +1292,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public ArrayList<View> getIconsInReadingOrder() { public ArrayList<View> getIconsInReadingOrder() {
if (mItemsInvalidated) { if (mItemsInvalidated) {
mItemsInReadingOrder.clear(); mItemsInReadingOrder.clear();
mContent.iterateOverItems(new ItemOperator() { mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v));
@Override
public boolean evaluate(ItemInfo info, View view) {
mItemsInReadingOrder.add(view);
return false;
}
});
mItemsInvalidated = false; mItemsInvalidated = false;
} }
return mItemsInReadingOrder; return mItemsInReadingOrder;

View File

@ -69,6 +69,7 @@ import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
/** /**
* An icon that can appear on in the workspace representing an {@link Folder}. * An icon that can appear on in the workspace representing an {@link Folder}.
@ -99,7 +100,7 @@ public class FolderIcon extends FrameLayout implements FolderListener {
ClippedFolderIconLayoutRule mPreviewLayoutRule; ClippedFolderIconLayoutRule mPreviewLayoutRule;
private PreviewItemManager mPreviewItemManager; private PreviewItemManager mPreviewItemManager;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
private List<BubbleTextView> mCurrentPreviewItems = new ArrayList<>(); private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
boolean mAnimating = false; boolean mAnimating = false;
@ -255,7 +256,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
OnAlarmListener mOnOpenListener = new OnAlarmListener() { OnAlarmListener mOnOpenListener = new OnAlarmListener() {
public void onAlarm(Alarm alarm) { public void onAlarm(Alarm alarm) {
mFolder.beginExternalDrag(); mFolder.beginExternalDrag();
mFolder.animateOpen();
} }
}; };
@ -321,18 +321,17 @@ public class FolderIcon extends FrameLayout implements FolderListener {
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1); int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
boolean itemAdded = false; boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
List<BubbleTextView> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
mInfo.add(item, index, false); mInfo.add(item, index, false);
mCurrentPreviewItems.clear(); mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0)); mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
if (!oldPreviewItems.equals(mCurrentPreviewItems)) { if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
for (int i = 0; i < mCurrentPreviewItems.size(); ++i) { int newIndex = mCurrentPreviewItems.indexOf(item);
if (mCurrentPreviewItems.get(i).getTag().equals(item)) { if (newIndex >= 0) {
// If the item dropped is going to be in the preview, we update the // If the item dropped is going to be in the preview, we update the
// index here to reflect its position in the preview. // index here to reflect its position in the preview.
index = i; index = newIndex;
}
} }
mPreviewItemManager.hidePreviewItem(index, true); mPreviewItemManager.hidePreviewItem(index, true);
@ -531,11 +530,10 @@ public class FolderIcon extends FrameLayout implements FolderListener {
} }
/** /**
* Returns the list of "preview items" on {@param page}. * Returns the list of items which should be visible in the preview
*/ */
public List<BubbleTextView> getPreviewIconsOnPage(int page) { public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mFolder.mInfo) return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
.previewItemsForPage(page, mFolder.getIconsInReadingOrder());
} }
@Override @Override
@ -553,11 +551,14 @@ public class FolderIcon extends FrameLayout implements FolderListener {
private void updatePreviewItems(boolean animate) { private void updatePreviewItems(boolean animate) {
mPreviewItemManager.updatePreviewItems(animate); mPreviewItemManager.updatePreviewItems(animate);
mCurrentPreviewItems.clear(); mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0)); mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
} }
@Override /**
public void prepareAutoUpdate() { * Updates the preview items which match the provided condition
*/
public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
mPreviewItemManager.updatePreviewItems(itemCheck);
} }
@Override @Override
@ -580,7 +581,6 @@ public class FolderIcon extends FrameLayout implements FolderListener {
requestLayout(); requestLayout();
} }
@Override
public void onTitleChanged(CharSequence title) { public void onTitleChanged(CharSequence title) {
mFolderName.setText(title); mFolderName.setText(title);
setContentDescription(getContext().getString(R.string.folder_name_format, title)); setContentDescription(getContext().getString(R.string.folder_name_format, title));

View File

@ -24,10 +24,10 @@ import android.util.ArrayMap;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewDebug; import android.view.ViewDebug;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BubbleTextView; import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout; import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile; import com.android.launcher3.DeviceProfile;
@ -46,11 +46,14 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Thunk; import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewCache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.ToIntFunction; import java.util.function.ToIntFunction;
import java.util.stream.Collectors;
public class FolderPagedView extends PagedView<PageIndicatorDots> { public class FolderPagedView extends PagedView<PageIndicatorDots> {
@ -69,12 +72,12 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
public final boolean mIsRtl; public final boolean mIsRtl;
private final LayoutInflater mInflater;
private final ViewGroupFocusHelper mFocusIndicatorHelper; private final ViewGroupFocusHelper mFocusIndicatorHelper;
@Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>(); @Thunk final ArrayMap<View, Runnable> mPendingAnimations = new ArrayMap<>();
private final FolderGridOrganizer mOrganizer; private final FolderGridOrganizer mOrganizer;
private final ViewCache mViewCache;
private int mAllocatedContentSize; private int mAllocatedContentSize;
@ViewDebug.ExportedProperty(category = "launcher") @ViewDebug.ExportedProperty(category = "launcher")
@ -84,17 +87,20 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
private Folder mFolder; private Folder mFolder;
// If the views are attached to the folder or not. A folder should be bound when its
// animating or is open.
private boolean mViewsBound = false;
public FolderPagedView(Context context, AttributeSet attrs) { public FolderPagedView(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
InvariantDeviceProfile profile = LauncherAppState.getIDP(context); InvariantDeviceProfile profile = LauncherAppState.getIDP(context);
mOrganizer = new FolderGridOrganizer(profile); mOrganizer = new FolderGridOrganizer(profile);
mInflater = LayoutInflater.from(context);
mIsRtl = Utilities.isRtl(getResources()); mIsRtl = Utilities.isRtl(getResources());
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
mFocusIndicatorHelper = new ViewGroupFocusHelper(this); mFocusIndicatorHelper = new ViewGroupFocusHelper(this);
mViewCache = BaseActivity.fromContext(context).getViewCache();
} }
public void setFolder(Folder folder) { public void setFolder(Folder folder) {
@ -127,35 +133,50 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
/** /**
* Binds items to the layout. * Binds items to the layout.
*/ */
public void bindItems(ArrayList<WorkspaceItemInfo> items) { public void bindItems(List<WorkspaceItemInfo> items) {
ArrayList<View> icons = new ArrayList<>(); if (mViewsBound) {
for (WorkspaceItemInfo item : items) { unbindItems();
icons.add(createNewView(item));
} }
arrangeChildren(icons); arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList()));
} mViewsBound = true;
public void allocateSpaceForRank(int rank) {
ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder());
views.add(rank, null);
arrangeChildren(views);
} }
/** /**
* Create space for a new item at the end, and returns the rank for that item. * Removes all the icons from the folder
* Also sets the current page to the last page.
*/ */
public int allocateRankForNewItem() { public void unbindItems() {
int rank = getItemCount(); for (int i = getChildCount() - 1; i >= 0; i--) {
allocateSpaceForRank(rank); CellLayout page = (CellLayout) getChildAt(i);
setCurrentPage(rank / mOrganizer.getMaxItemsPerPage()); ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
return rank; for (int j = container.getChildCount() - 1; j >= 0; j--) {
mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
}
page.removeAllViews();
mViewCache.recycleView(R.layout.folder_page, page);
}
removeAllViews();
mViewsBound = false;
} }
/**
* Returns true if the icons are bound to the folder
*/
public boolean areViewsBound() {
return mViewsBound;
}
/**
* Creates and adds an icon corresponding to the provided rank
* @return the created icon
*/
public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) { public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
View icon = createNewView(item); View icon = createNewView(item);
allocateSpaceForRank(rank); if (!mViewsBound) {
addViewForRank(icon, item, rank); return icon;
}
ArrayList<View> views = new ArrayList<>(mFolder.getIconsInReadingOrder());
views.add(rank, icon);
arrangeChildren(views);
return icon; return icon;
} }
@ -173,16 +194,24 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
public View createNewView(WorkspaceItemInfo item) { public View createNewView(WorkspaceItemInfo item) {
final BubbleTextView textView = (BubbleTextView) mInflater.inflate( if (item == null) {
R.layout.folder_application, null, false); return null;
}
final BubbleTextView textView = mViewCache.getView(
R.layout.folder_application, getContext(), null);
textView.applyFromWorkspaceItem(item); textView.applyFromWorkspaceItem(item);
textView.setHapticFeedbackEnabled(false);
textView.setOnClickListener(ItemClickHandler.INSTANCE); textView.setOnClickListener(ItemClickHandler.INSTANCE);
textView.setOnLongClickListener(mFolder); textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorHelper); textView.setOnFocusChangeListener(mFocusIndicatorHelper);
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams();
if (lp == null) {
textView.setLayoutParams(new CellLayout.LayoutParams( textView.setLayoutParams(new CellLayout.LayoutParams(
item.cellX, item.cellY, item.spanX, item.spanY)); item.cellX, item.cellY, item.spanX, item.spanY));
} else {
lp.cellX = item.cellX;
lp.cellY = item.cellY;
lp.cellHSpan = lp.cellVSpan = 1;
}
return textView; return textView;
} }
@ -197,7 +226,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
private CellLayout createAndAddNewPage() { private CellLayout createAndAddNewPage() {
DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false); CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this);
page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx);
page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false);
page.setInvertIfRtl(true); page.setInvertIfRtl(true);
@ -240,7 +269,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
* @param list the ordered list of children. * @param list the ordered list of children.
*/ */
@SuppressLint("RtlHardcoded") @SuppressLint("RtlHardcoded")
public void arrangeChildren(ArrayList<View> list) { public void arrangeChildren(List<View> list) {
int itemCount = list.size(); int itemCount = list.size();
ArrayList<CellLayout> pages = new ArrayList<>(); ArrayList<CellLayout> pages = new ArrayList<>();
for (int i = 0; i < getChildCount(); i++) { for (int i = 0; i < getChildCount(); i++) {
@ -313,16 +342,6 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> {
(getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0; (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0;
} }
public int getItemCount() {
int lastPageIndex = getChildCount() - 1;
if (lastPageIndex < 0) {
// If there are no pages, nothing has yet been added to the folder.
return 0;
}
return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount()
+ lastPageIndex * mOrganizer.getMaxItemsPerPage();
}
/** /**
* @return the rank of the cell nearest to the provided pixel position. * @return the rank of the cell nearest to the provided pixel position.
*/ */

View File

@ -17,6 +17,8 @@ package com.android.launcher3.folder;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import com.android.launcher3.WorkspaceItemInfo;
/** /**
* Manages the parameters used to draw a Folder preview item. * Manages the parameters used to draw a Folder preview item.
*/ */
@ -25,9 +27,10 @@ class PreviewItemDrawingParams {
float transY; float transY;
float scale; float scale;
float overlayAlpha; float overlayAlpha;
FolderPreviewItemAnim anim; public FolderPreviewItemAnim anim;
public boolean hidden; public boolean hidden;
Drawable drawable; public Drawable drawable;
public WorkspaceItemInfo item;
PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
this.transX = transX; this.transX = transX;

View File

@ -23,28 +23,51 @@ import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.launcher3.BubbleTextView; import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.graphics.PreloadIconDrawable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
/** /**
* Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}. * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}.
*/ */
public class PreviewItemManager { public class PreviewItemManager {
private FolderIcon mIcon; private static final FloatProperty<PreviewItemManager> CURRENT_PAGE_ITEMS_TRANS_X =
new FloatProperty<PreviewItemManager>("currentPageItemsTransX") {
@Override
public void setValue(PreviewItemManager manager, float v) {
manager.mCurrentPageItemsTransX = v;
manager.onParamsChanged();
}
@Override
public Float get(PreviewItemManager manager) {
return manager.mCurrentPageItemsTransX;
}
};
private final Context mContext;
private final FolderIcon mIcon;
private final DrawableFactory mDrawableFactory;
private final int mIconSize;
// These variables are all associated with the drawing of the preview; they are stored // These variables are all associated with the drawing of the preview; they are stored
// as member variables for shared usage and to avoid computation on each frame // as member variables for shared usage and to avoid computation on each frame
@ -69,7 +92,10 @@ public class PreviewItemManager {
private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200; private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200;
public PreviewItemManager(FolderIcon icon) { public PreviewItemManager(FolderIcon icon) {
mContext = icon.getContext();
mIcon = icon; mIcon = icon;
mDrawableFactory = DrawableFactory.INSTANCE.get(mContext);
mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx;
} }
/** /**
@ -200,7 +226,7 @@ public class PreviewItemManager {
} }
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) { void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
List<BubbleTextView> items = mIcon.getPreviewIconsOnPage(page); List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
int prevNumItems = params.size(); int prevNumItems = params.size();
// We adjust the size of the list to match the number of items in the preview. // We adjust the size of the list to match the number of items in the preview.
@ -214,13 +240,7 @@ public class PreviewItemManager {
int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW; int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW;
for (int i = 0; i < params.size(); i++) { for (int i = 0; i < params.size(); i++) {
PreviewItemDrawingParams p = params.get(i); PreviewItemDrawingParams p = params.get(i);
p.drawable = items.get(i).getCompoundDrawables()[1]; setDrawable(p, items.get(i));
if (p.drawable != null && !mIcon.mFolder.isOpen()) {
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
p.drawable.setCallback(mIcon);
}
if (!animate) { if (!animate) {
computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p); computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p);
@ -253,14 +273,8 @@ public class PreviewItemManager {
buildParamsForPage(currentPage, mCurrentPageParams, false); buildParamsForPage(currentPage, mCurrentPageParams, false);
onParamsChanged(); onParamsChanged();
ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); ValueAnimator slideAnimator = ObjectAnimator
slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX);
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue();
onParamsChanged();
}
});
slideAnimator.addListener(new AnimatorListenerAdapter() { slideAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
@ -277,6 +291,25 @@ public class PreviewItemManager {
buildParamsForPage(0, mFirstPageParams, animate); buildParamsForPage(0, mFirstPageParams, animate);
} }
void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
boolean modified = false;
for (PreviewItemDrawingParams param : mFirstPageParams) {
if (itemCheck.test(param.item)) {
setDrawable(param, param.item);
modified = true;
}
}
for (PreviewItemDrawingParams param : mCurrentPageParams) {
if (itemCheck.test(param.item)) {
setDrawable(param, param.item);
modified = true;
}
}
if (modified) {
mIcon.invalidate();
}
}
boolean verifyDrawable(@NonNull Drawable who) { boolean verifyDrawable(@NonNull Drawable who) {
for (int i = 0; i < mFirstPageParams.size(); i++) { for (int i = 0; i < mFirstPageParams.size(); i++) {
if (mFirstPageParams.get(i).drawable == who) { if (mFirstPageParams.get(i).drawable == who) {
@ -296,46 +329,46 @@ public class PreviewItemManager {
* - Moving into a new position * - Moving into a new position
* - Moving out of the preview * - Moving out of the preview
* *
* @param oldParams The list of items in the old preview. * @param oldItems The list of items in the old preview.
* @param newParams The list of items in the new preview. * @param newItems The list of items in the new preview.
* @param dropped The item that was dropped onto the FolderIcon. * @param dropped The item that was dropped onto the FolderIcon.
*/ */
public void onDrop(List<BubbleTextView> oldParams, List<BubbleTextView> newParams, public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
WorkspaceItemInfo dropped) { WorkspaceItemInfo dropped) {
int numItems = newParams.size(); int numItems = newItems.size();
final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams; final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
buildParamsForPage(0, params, false); buildParamsForPage(0, params, false);
// New preview items for items that are moving in (except for the dropped item). // New preview items for items that are moving in (except for the dropped item).
List<BubbleTextView> moveIn = new ArrayList<>(); List<WorkspaceItemInfo> moveIn = new ArrayList<>();
for (BubbleTextView btv : newParams) { for (WorkspaceItemInfo newItem : newItems) {
if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) { if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
moveIn.add(btv); moveIn.add(newItem);
} }
} }
for (int i = 0; i < moveIn.size(); ++i) { for (int i = 0; i < moveIn.size(); ++i) {
int prevIndex = newParams.indexOf(moveIn.get(i)); int prevIndex = newItems.indexOf(moveIn.get(i));
PreviewItemDrawingParams p = params.get(prevIndex); PreviewItemDrawingParams p = params.get(prevIndex);
computePreviewItemDrawingParams(prevIndex, numItems, p); computePreviewItemDrawingParams(prevIndex, numItems, p);
updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)), updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)),
numItems); numItems);
} }
// Items that are moving into new positions within the preview. // Items that are moving into new positions within the preview.
for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) { for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) {
int oldIndex = oldParams.indexOf(newParams.get(newIndex)); int oldIndex = oldItems.indexOf(newItems.get(newIndex));
if (oldIndex >= 0 && newIndex != oldIndex) { if (oldIndex >= 0 && newIndex != oldIndex) {
PreviewItemDrawingParams p = params.get(newIndex); PreviewItemDrawingParams p = params.get(newIndex);
updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems); updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems);
} }
} }
// Old preview items that need to be moved out. // Old preview items that need to be moved out.
List<BubbleTextView> moveOut = new ArrayList<>(oldParams); List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
moveOut.removeAll(newParams); moveOut.removeAll(newItems);
for (int i = 0; i < moveOut.size(); ++i) { for (int i = 0; i < moveOut.size(); ++i) {
BubbleTextView item = moveOut.get(i); WorkspaceItemInfo item = moveOut.get(i);
int oldIndex = oldParams.indexOf(item); int oldIndex = oldItems.indexOf(item);
PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null); PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems); updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
params.add(0, p); // We want these items first so that they are on drawn last. params.add(0, p); // We want these items first so that they are on drawn last.
@ -348,14 +381,9 @@ public class PreviewItemManager {
} }
} }
private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv, private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
int prevIndex, int newIndex, int numItems) { int prevIndex, int newIndex, int numItems) {
p.drawable = btv.getCompoundDrawables()[1]; setDrawable(p, item);
if (!mIcon.mFolder.isOpen()) {
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
p.drawable.setCallback(mIcon);
}
FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems, FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems,
newIndex, numItems, DROP_IN_ANIMATION_DURATION, null); newIndex, numItems, DROP_IN_ANIMATION_DURATION, null);
@ -364,4 +392,20 @@ public class PreviewItemManager {
} }
p.anim = anim; p.anim = anim;
} }
private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
if (item.hasPromiseIconUi()) {
PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item);
drawable.setLevel(item.getInstallProgress());
p.drawable = drawable;
} else {
p.drawable = mDrawableFactory.newIcon(mContext, item);
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
p.item = item;
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
p.drawable.setCallback(mIcon);
}
} }