c77bc45fa5
=> Also fix the widget tray in these non-default grids, and the external shortcut addition flow. The layout was broken as it was scaling with the icon size. Instead keep it fixed which looks much better, similar to how we maintain all apps. => This also fixes a small jump when dragging shortcuts external to Launcher. issue 154169001 Change-Id: Iad1e3859dd6fedccce9b5c6633e64426a4630c31
765 lines
27 KiB
Java
765 lines
27 KiB
Java
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.folder;
|
|
|
|
import static android.text.TextUtils.isEmpty;
|
|
|
|
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
|
|
import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.ObjectAnimator;
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.util.AttributeSet;
|
|
import android.util.Property;
|
|
import android.view.LayoutInflater;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.ViewDebug;
|
|
import android.view.ViewGroup;
|
|
import android.widget.FrameLayout;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import com.android.launcher3.Alarm;
|
|
import com.android.launcher3.BubbleTextView;
|
|
import com.android.launcher3.CellLayout;
|
|
import com.android.launcher3.CheckLongPressHelper;
|
|
import com.android.launcher3.DeviceProfile;
|
|
import com.android.launcher3.DropTarget.DragObject;
|
|
import com.android.launcher3.Launcher;
|
|
import com.android.launcher3.LauncherSettings;
|
|
import com.android.launcher3.OnAlarmListener;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.Reorderable;
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.Workspace;
|
|
import com.android.launcher3.anim.Interpolators;
|
|
import com.android.launcher3.config.FeatureFlags;
|
|
import com.android.launcher3.dot.FolderDotInfo;
|
|
import com.android.launcher3.dragndrop.BaseItemDragListener;
|
|
import com.android.launcher3.dragndrop.DragLayer;
|
|
import com.android.launcher3.dragndrop.DragView;
|
|
import com.android.launcher3.dragndrop.DraggableView;
|
|
import com.android.launcher3.icons.DotRenderer;
|
|
import com.android.launcher3.model.data.AppInfo;
|
|
import com.android.launcher3.model.data.FolderInfo;
|
|
import com.android.launcher3.model.data.FolderInfo.FolderListener;
|
|
import com.android.launcher3.model.data.ItemInfo;
|
|
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
|
import com.android.launcher3.touch.ItemClickHandler;
|
|
import com.android.launcher3.util.Executors;
|
|
import com.android.launcher3.util.Thunk;
|
|
import com.android.launcher3.views.ActivityContext;
|
|
import com.android.launcher3.views.IconLabelDotView;
|
|
import com.android.launcher3.widget.PendingAddShortcutInfo;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.function.Predicate;
|
|
|
|
/**
|
|
* An icon that can appear on in the workspace representing an {@link Folder}.
|
|
*/
|
|
public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView,
|
|
DraggableView, Reorderable {
|
|
|
|
@Thunk ActivityContext mActivity;
|
|
@Thunk Folder mFolder;
|
|
public FolderInfo mInfo;
|
|
|
|
private CheckLongPressHelper mLongPressHelper;
|
|
|
|
static final int DROP_IN_ANIMATION_DURATION = 400;
|
|
|
|
// Flag whether the folder should open itself when an item is dragged over is enabled.
|
|
public static final boolean SPRING_LOADING_ENABLED = true;
|
|
|
|
// Delay when drag enters until the folder opens, in miliseconds.
|
|
private static final int ON_OPEN_DELAY = 800;
|
|
|
|
@Thunk BubbleTextView mFolderName;
|
|
|
|
PreviewBackground mBackground = new PreviewBackground();
|
|
private boolean mBackgroundIsVisible = true;
|
|
|
|
FolderGridOrganizer mPreviewVerifier;
|
|
ClippedFolderIconLayoutRule mPreviewLayoutRule;
|
|
private PreviewItemManager mPreviewItemManager;
|
|
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
|
|
private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
|
|
|
|
boolean mAnimating = false;
|
|
|
|
private Alarm mOpenAlarm = new Alarm();
|
|
|
|
private boolean mForceHideDot;
|
|
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
|
|
private FolderDotInfo mDotInfo = new FolderDotInfo();
|
|
private DotRenderer mDotRenderer;
|
|
@ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
|
|
private DotRenderer.DrawParams mDotParams;
|
|
private float mDotScale;
|
|
private Animator mDotScaleAnim;
|
|
|
|
private final PointF mTranslationForReorderBounce = new PointF(0, 0);
|
|
private final PointF mTranslationForReorderPreview = new PointF(0, 0);
|
|
private float mScaleForReorderBounce = 1f;
|
|
|
|
private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
|
|
= new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
|
|
@Override
|
|
public Float get(FolderIcon folderIcon) {
|
|
return folderIcon.mDotScale;
|
|
}
|
|
|
|
@Override
|
|
public void set(FolderIcon folderIcon, Float value) {
|
|
folderIcon.mDotScale = value;
|
|
folderIcon.invalidate();
|
|
}
|
|
};
|
|
|
|
public FolderIcon(Context context, AttributeSet attrs) {
|
|
super(context, attrs);
|
|
init();
|
|
}
|
|
|
|
public FolderIcon(Context context) {
|
|
super(context);
|
|
init();
|
|
}
|
|
|
|
private void init() {
|
|
mLongPressHelper = new CheckLongPressHelper(this);
|
|
mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
|
|
mPreviewItemManager = new PreviewItemManager(this);
|
|
mDotParams = new DotRenderer.DrawParams();
|
|
}
|
|
|
|
public static FolderIcon inflateFolderAndIcon(int resId, Launcher launcher, ViewGroup group,
|
|
FolderInfo folderInfo) {
|
|
Folder folder = Folder.fromXml(launcher);
|
|
folder.setDragController(launcher.getDragController());
|
|
|
|
FolderIcon icon = inflateIcon(resId, launcher, group, folderInfo);
|
|
folder.setFolderIcon(icon);
|
|
folder.bind(folderInfo);
|
|
icon.setFolder(folder);
|
|
|
|
icon.setOnFocusChangeListener(launcher.getFocusHandler());
|
|
return icon;
|
|
}
|
|
|
|
public static FolderIcon inflateIcon(int resId, ActivityContext activity, ViewGroup group,
|
|
FolderInfo folderInfo) {
|
|
@SuppressWarnings("all") // suppress dead code warning
|
|
final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
|
|
if (error) {
|
|
throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
|
|
"INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
|
|
"is dependent on this");
|
|
}
|
|
|
|
DeviceProfile grid = activity.getDeviceProfile();
|
|
FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
|
|
.inflate(resId, group, false);
|
|
|
|
icon.setClipToPadding(false);
|
|
icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
|
|
icon.mFolderName.setText(folderInfo.title);
|
|
icon.mFolderName.setCompoundDrawablePadding(0);
|
|
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
|
|
lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
|
|
|
|
icon.setTag(folderInfo);
|
|
icon.setOnClickListener(ItemClickHandler.INSTANCE);
|
|
icon.mInfo = folderInfo;
|
|
icon.mActivity = activity;
|
|
icon.mDotRenderer = grid.mDotRendererWorkSpace;
|
|
|
|
icon.setContentDescription(
|
|
group.getContext().getString(R.string.folder_name_format, folderInfo.title));
|
|
|
|
// Keep the notification dot up to date with the sum of all the content's dots.
|
|
FolderDotInfo folderDotInfo = new FolderDotInfo();
|
|
for (WorkspaceItemInfo si : folderInfo.contents) {
|
|
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
|
|
}
|
|
icon.setDotInfo(folderDotInfo);
|
|
|
|
icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
|
|
|
|
icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
|
|
icon.mPreviewVerifier.setFolderInfo(folderInfo);
|
|
icon.updatePreviewItems(false);
|
|
|
|
folderInfo.addListener(icon);
|
|
|
|
return icon;
|
|
}
|
|
|
|
public void animateBgShadowAndStroke() {
|
|
mBackground.fadeInBackgroundShadow();
|
|
mBackground.animateBackgroundStroke();
|
|
}
|
|
|
|
public BubbleTextView getFolderName() {
|
|
return mFolderName;
|
|
}
|
|
|
|
public void getPreviewBounds(Rect outBounds) {
|
|
mPreviewItemManager.recomputePreviewDrawingParams();
|
|
mBackground.getBounds(outBounds);
|
|
}
|
|
|
|
public float getBackgroundStrokeWidth() {
|
|
return mBackground.getStrokeWidth();
|
|
}
|
|
|
|
public Folder getFolder() {
|
|
return mFolder;
|
|
}
|
|
|
|
private void setFolder(Folder folder) {
|
|
mFolder = folder;
|
|
}
|
|
|
|
private boolean willAcceptItem(ItemInfo item) {
|
|
final int itemType = item.itemType;
|
|
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
|
|
itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
|
|
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
|
|
item != mInfo && !mFolder.isOpen());
|
|
}
|
|
|
|
public boolean acceptDrop(ItemInfo dragInfo) {
|
|
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
|
|
}
|
|
|
|
public void addItem(WorkspaceItemInfo item) {
|
|
mInfo.add(item, true);
|
|
}
|
|
|
|
public void removeItem(WorkspaceItemInfo item, boolean animate) {
|
|
mInfo.remove(item, animate);
|
|
}
|
|
|
|
public void onDragEnter(ItemInfo dragInfo) {
|
|
if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
|
|
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
|
|
CellLayout cl = (CellLayout) getParent().getParent();
|
|
|
|
mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
|
|
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
|
|
if (SPRING_LOADING_ENABLED &&
|
|
((dragInfo instanceof AppInfo)
|
|
|| (dragInfo instanceof WorkspaceItemInfo)
|
|
|| (dragInfo instanceof PendingAddShortcutInfo))) {
|
|
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
|
|
}
|
|
}
|
|
|
|
OnAlarmListener mOnOpenListener = new OnAlarmListener() {
|
|
public void onAlarm(Alarm alarm) {
|
|
mFolder.beginExternalDrag();
|
|
}
|
|
};
|
|
|
|
public Drawable prepareCreateAnimation(final View destView) {
|
|
return mPreviewItemManager.prepareCreateAnimation(destView);
|
|
}
|
|
|
|
public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
|
|
final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
|
|
float scaleRelativeToDragLayer) {
|
|
final DragView srcView = d.dragView;
|
|
prepareCreateAnimation(destView);
|
|
addItem(destInfo);
|
|
// This will animate the first item from it's position as an icon into its
|
|
// position as the first item in the preview
|
|
mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null)
|
|
.start();
|
|
|
|
// This will animate the dragView (srcView) into the new folder
|
|
onDrop(srcInfo, d, dstRect, scaleRelativeToDragLayer, 1,
|
|
false /* itemReturnedOnFailedDrop */);
|
|
}
|
|
|
|
public void performDestroyAnimation(Runnable onCompleteRunnable) {
|
|
// This will animate the final item in the preview to be full size.
|
|
mPreviewItemManager.createFirstItemAnimation(true /* reverse */, onCompleteRunnable)
|
|
.start();
|
|
}
|
|
|
|
public void onDragExit() {
|
|
mBackground.animateToRest();
|
|
mOpenAlarm.cancelAlarm();
|
|
}
|
|
|
|
private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
|
|
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
|
|
item.cellX = -1;
|
|
item.cellY = -1;
|
|
DragView animateView = d.dragView;
|
|
// Typically, the animateView corresponds to the DragView; however, if this is being done
|
|
// after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
|
|
// will not have a view to animate
|
|
if (animateView != null && mActivity instanceof Launcher) {
|
|
final Launcher launcher = (Launcher) mActivity;
|
|
DragLayer dragLayer = launcher.getDragLayer();
|
|
Rect from = new Rect();
|
|
dragLayer.getViewRectRelativeToSelf(animateView, from);
|
|
Rect to = finalRect;
|
|
if (to == null) {
|
|
to = new Rect();
|
|
Workspace workspace = launcher.getWorkspace();
|
|
// Set cellLayout and this to it's final state to compute final animation locations
|
|
workspace.setFinalTransitionTransform();
|
|
float scaleX = getScaleX();
|
|
float scaleY = getScaleY();
|
|
setScaleX(1.0f);
|
|
setScaleY(1.0f);
|
|
scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
|
|
// Finished computing final animation locations, restore current state
|
|
setScaleX(scaleX);
|
|
setScaleY(scaleY);
|
|
workspace.resetTransitionTransform();
|
|
}
|
|
|
|
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
|
|
boolean itemAdded = false;
|
|
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
|
|
List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
|
|
mInfo.add(item, index, false);
|
|
mCurrentPreviewItems.clear();
|
|
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
|
|
|
|
if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
|
|
int newIndex = mCurrentPreviewItems.indexOf(item);
|
|
if (newIndex >= 0) {
|
|
// If the item dropped is going to be in the preview, we update the
|
|
// index here to reflect its position in the preview.
|
|
index = newIndex;
|
|
}
|
|
|
|
mPreviewItemManager.hidePreviewItem(index, true);
|
|
mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item);
|
|
itemAdded = true;
|
|
} else {
|
|
removeItem(item, false);
|
|
}
|
|
}
|
|
|
|
if (!itemAdded) {
|
|
mInfo.add(item, index, true);
|
|
}
|
|
|
|
int[] center = new int[2];
|
|
float scale = getLocalCenterForIndex(index, numItemsInPreview, center);
|
|
center[0] = Math.round(scaleRelativeToDragLayer * center[0]);
|
|
center[1] = Math.round(scaleRelativeToDragLayer * center[1]);
|
|
|
|
to.offset(center[0] - animateView.getMeasuredWidth() / 2,
|
|
center[1] - animateView.getMeasuredHeight() / 2);
|
|
|
|
float finalAlpha = index < MAX_NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
|
|
|
|
float finalScale = scale * scaleRelativeToDragLayer;
|
|
dragLayer.animateView(animateView, from, to, finalAlpha,
|
|
1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
|
|
Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
|
|
null, DragLayer.ANIMATION_END_DISAPPEAR, null);
|
|
|
|
mFolder.hideItem(item);
|
|
|
|
if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
|
|
final int finalIndex = index;
|
|
|
|
FolderNameInfo[] nameInfos =
|
|
new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
|
|
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
|
Executors.UI_HELPER_EXECUTOR.post(() -> {
|
|
d.folderNameProvider.getSuggestedFolderName(
|
|
getContext(), mInfo.contents, nameInfos);
|
|
showFinalView(finalIndex, item, nameInfos);
|
|
});
|
|
} else {
|
|
showFinalView(finalIndex, item, nameInfos);
|
|
}
|
|
} else {
|
|
addItem(item);
|
|
}
|
|
}
|
|
|
|
private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
|
|
FolderNameInfo[] nameInfos) {
|
|
postDelayed(() -> {
|
|
mPreviewItemManager.hidePreviewItem(finalIndex, false);
|
|
mFolder.showItem(item);
|
|
setLabelSuggestion(nameInfos);
|
|
mFolder.logCurrentFolderLabelState();
|
|
invalidate();
|
|
}, DROP_IN_ANIMATION_DURATION);
|
|
}
|
|
|
|
/**
|
|
* Set the suggested folder name.
|
|
*/
|
|
public void setLabelSuggestion(FolderNameInfo[] nameInfos) {
|
|
if (!FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
|
|
return;
|
|
}
|
|
if (!isEmpty(mFolderName.getText().toString())
|
|
|| mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
|
|
return;
|
|
}
|
|
if (nameInfos == null || nameInfos[0] == null || isEmpty(nameInfos[0].getLabel())) {
|
|
return;
|
|
}
|
|
mInfo.title = nameInfos[0].getLabel();
|
|
onTitleChanged(mInfo.title);
|
|
mFolder.mFolderName.setText(mInfo.title);
|
|
mFolder.mLauncher.getModelWriter().updateItemInDatabase(mInfo);
|
|
}
|
|
|
|
|
|
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
|
|
WorkspaceItemInfo item;
|
|
if (d.dragInfo instanceof AppInfo) {
|
|
// Came from all apps -- make a copy
|
|
item = ((AppInfo) d.dragInfo).makeWorkspaceItem();
|
|
} else if (d.dragSource instanceof BaseItemDragListener){
|
|
// Came from a different window -- make a copy
|
|
item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
|
|
} else {
|
|
item = (WorkspaceItemInfo) d.dragInfo;
|
|
}
|
|
mFolder.notifyDrop();
|
|
onDrop(item, d, null, 1.0f,
|
|
itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
|
|
itemReturnedOnFailedDrop
|
|
);
|
|
}
|
|
|
|
public void setDotInfo(FolderDotInfo dotInfo) {
|
|
updateDotScale(mDotInfo.hasDot(), dotInfo.hasDot());
|
|
mDotInfo = dotInfo;
|
|
}
|
|
|
|
public ClippedFolderIconLayoutRule getLayoutRule() {
|
|
return mPreviewLayoutRule;
|
|
}
|
|
|
|
@Override
|
|
public void setForceHideDot(boolean forceHideDot) {
|
|
if (mForceHideDot == forceHideDot) {
|
|
return;
|
|
}
|
|
mForceHideDot = forceHideDot;
|
|
|
|
if (forceHideDot) {
|
|
invalidate();
|
|
} else if (hasDot()) {
|
|
animateDotScale(0, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
|
|
* (the dot is being added or removed).
|
|
*/
|
|
private void updateDotScale(boolean wasDotted, boolean isDotted) {
|
|
float newDotScale = isDotted ? 1f : 0f;
|
|
// Animate when a dot is first added or when it is removed.
|
|
if ((wasDotted ^ isDotted) && isShown()) {
|
|
animateDotScale(newDotScale);
|
|
} else {
|
|
cancelDotScaleAnim();
|
|
mDotScale = newDotScale;
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
private void cancelDotScaleAnim() {
|
|
if (mDotScaleAnim != null) {
|
|
mDotScaleAnim.cancel();
|
|
}
|
|
}
|
|
|
|
public void animateDotScale(float... dotScales) {
|
|
cancelDotScaleAnim();
|
|
mDotScaleAnim = ObjectAnimator.ofFloat(this, DOT_SCALE_PROPERTY, dotScales);
|
|
mDotScaleAnim.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
mDotScaleAnim = null;
|
|
}
|
|
});
|
|
mDotScaleAnim.start();
|
|
}
|
|
|
|
public boolean hasDot() {
|
|
return mDotInfo != null && mDotInfo.hasDot();
|
|
}
|
|
|
|
private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
|
|
mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams(
|
|
Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index), curNumItems, mTmpParams);
|
|
|
|
mTmpParams.transX += mBackground.basePreviewOffsetX;
|
|
mTmpParams.transY += mBackground.basePreviewOffsetY;
|
|
|
|
float intrinsicIconSize = mPreviewItemManager.getIntrinsicIconSize();
|
|
float offsetX = mTmpParams.transX + (mTmpParams.scale * intrinsicIconSize) / 2;
|
|
float offsetY = mTmpParams.transY + (mTmpParams.scale * intrinsicIconSize) / 2;
|
|
|
|
center[0] = Math.round(offsetX);
|
|
center[1] = Math.round(offsetY);
|
|
return mTmpParams.scale;
|
|
}
|
|
|
|
public void setFolderBackground(PreviewBackground bg) {
|
|
mBackground = bg;
|
|
mBackground.setInvalidateDelegate(this);
|
|
}
|
|
|
|
@Override
|
|
public void setIconVisible(boolean visible) {
|
|
mBackgroundIsVisible = visible;
|
|
invalidate();
|
|
}
|
|
|
|
public boolean getIconVisible() {
|
|
return mBackgroundIsVisible;
|
|
}
|
|
|
|
public PreviewBackground getFolderBackground() {
|
|
return mBackground;
|
|
}
|
|
|
|
public PreviewItemManager getPreviewItemManager() {
|
|
return mPreviewItemManager;
|
|
}
|
|
|
|
@Override
|
|
protected void dispatchDraw(Canvas canvas) {
|
|
super.dispatchDraw(canvas);
|
|
|
|
if (!mBackgroundIsVisible) return;
|
|
|
|
mPreviewItemManager.recomputePreviewDrawingParams();
|
|
|
|
if (!mBackground.drawingDelegated()) {
|
|
mBackground.drawBackground(canvas);
|
|
}
|
|
|
|
if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
|
|
|
|
final int saveCount = canvas.save();
|
|
canvas.clipPath(mBackground.getClipPath());
|
|
mPreviewItemManager.draw(canvas);
|
|
canvas.restoreToCount(saveCount);
|
|
|
|
if (!mBackground.drawingDelegated()) {
|
|
mBackground.drawBackgroundStroke(canvas);
|
|
}
|
|
|
|
drawDot(canvas);
|
|
}
|
|
|
|
public void drawDot(Canvas canvas) {
|
|
if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
|
|
Rect iconBounds = mDotParams.iconBounds;
|
|
BubbleTextView.getIconBounds(this, iconBounds, mActivity.getDeviceProfile().iconSizePx);
|
|
float iconScale = (float) mBackground.previewSize / iconBounds.width();
|
|
Utilities.scaleRectAboutCenter(iconBounds, iconScale);
|
|
|
|
// If we are animating to the accepting state, animate the dot out.
|
|
mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
|
|
mDotParams.color = mBackground.getDotColor();
|
|
mDotRenderer.draw(canvas, mDotParams);
|
|
}
|
|
}
|
|
|
|
public void setTextVisible(boolean visible) {
|
|
if (visible) {
|
|
mFolderName.setVisibility(VISIBLE);
|
|
} else {
|
|
mFolderName.setVisibility(INVISIBLE);
|
|
}
|
|
}
|
|
|
|
public boolean getTextVisible() {
|
|
return mFolderName.getVisibility() == VISIBLE;
|
|
}
|
|
|
|
/**
|
|
* Returns the list of items which should be visible in the preview
|
|
*/
|
|
public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
|
|
return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
|
|
}
|
|
|
|
@Override
|
|
protected boolean verifyDrawable(@NonNull Drawable who) {
|
|
return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who);
|
|
}
|
|
|
|
@Override
|
|
public void onItemsChanged(boolean animate) {
|
|
updatePreviewItems(animate);
|
|
invalidate();
|
|
requestLayout();
|
|
}
|
|
|
|
private void updatePreviewItems(boolean animate) {
|
|
mPreviewItemManager.updatePreviewItems(animate);
|
|
mCurrentPreviewItems.clear();
|
|
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
|
|
}
|
|
|
|
/**
|
|
* Updates the preview items which match the provided condition
|
|
*/
|
|
public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
|
|
mPreviewItemManager.updatePreviewItems(itemCheck);
|
|
}
|
|
|
|
@Override
|
|
public void onAdd(WorkspaceItemInfo item, int rank) {
|
|
boolean wasDotted = mDotInfo.hasDot();
|
|
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
|
|
boolean isDotted = mDotInfo.hasDot();
|
|
updateDotScale(wasDotted, isDotted);
|
|
invalidate();
|
|
requestLayout();
|
|
}
|
|
|
|
@Override
|
|
public void onRemove(WorkspaceItemInfo item) {
|
|
boolean wasDotted = mDotInfo.hasDot();
|
|
mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
|
|
boolean isDotted = mDotInfo.hasDot();
|
|
updateDotScale(wasDotted, isDotted);
|
|
invalidate();
|
|
requestLayout();
|
|
}
|
|
|
|
public void onTitleChanged(CharSequence title) {
|
|
mFolderName.setText(title);
|
|
setContentDescription(getContext().getString(R.string.folder_name_format, title));
|
|
}
|
|
|
|
@Override
|
|
public boolean onTouchEvent(MotionEvent event) {
|
|
// Call the superclass onTouchEvent first, because sometimes it changes the state to
|
|
// isPressed() on an ACTION_UP
|
|
super.onTouchEvent(event);
|
|
mLongPressHelper.onTouchEvent(event);
|
|
// Keep receiving the rest of the events
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void cancelLongPress() {
|
|
super.cancelLongPress();
|
|
mLongPressHelper.cancelLongPress();
|
|
}
|
|
|
|
public void removeListeners() {
|
|
mInfo.removeListener(this);
|
|
mInfo.removeListener(mFolder);
|
|
}
|
|
|
|
public void clearLeaveBehindIfExists() {
|
|
((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
|
|
if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
|
|
CellLayout cl = (CellLayout) getParent().getParent();
|
|
cl.clearFolderLeaveBehind();
|
|
}
|
|
}
|
|
|
|
public void drawLeaveBehindIfExists() {
|
|
CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
|
|
// While the folder is open, the position of the icon cannot change.
|
|
lp.canReorder = false;
|
|
if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
|
|
CellLayout cl = (CellLayout) getParent().getParent();
|
|
cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
|
|
}
|
|
}
|
|
|
|
public void onFolderClose(int currentPage) {
|
|
mPreviewItemManager.onFolderClose(currentPage);
|
|
}
|
|
|
|
private void updateTranslation() {
|
|
super.setTranslationX(mTranslationForReorderBounce.x + mTranslationForReorderPreview.x);
|
|
super.setTranslationY(mTranslationForReorderBounce.y + mTranslationForReorderPreview.y);
|
|
}
|
|
|
|
public void setReorderBounceOffset(float x, float y) {
|
|
mTranslationForReorderBounce.set(x, y);
|
|
updateTranslation();
|
|
}
|
|
|
|
public void getReorderBounceOffset(PointF offset) {
|
|
offset.set(mTranslationForReorderBounce);
|
|
}
|
|
|
|
@Override
|
|
public void setReorderPreviewOffset(float x, float y) {
|
|
mTranslationForReorderPreview.set(x, y);
|
|
updateTranslation();
|
|
}
|
|
|
|
@Override
|
|
public void getReorderPreviewOffset(PointF offset) {
|
|
offset.set(mTranslationForReorderPreview);
|
|
}
|
|
|
|
public void setReorderBounceScale(float scale) {
|
|
mScaleForReorderBounce = scale;
|
|
super.setScaleX(scale);
|
|
super.setScaleY(scale);
|
|
}
|
|
|
|
public float getReorderBounceScale() {
|
|
return mScaleForReorderBounce;
|
|
}
|
|
|
|
public View getView() {
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public int getViewType() {
|
|
return DRAGGABLE_ICON;
|
|
}
|
|
|
|
@Override
|
|
public void getWorkspaceVisualDragBounds(Rect bounds) {
|
|
getPreviewBounds(bounds);
|
|
}
|
|
}
|