diff options
29 files changed, 379 insertions, 303 deletions
diff --git a/res/drawable-hdpi/portal_ring_inner.png b/res/drawable-hdpi/portal_ring_inner.png Binary files differdeleted file mode 100644 index 65f5af2e7..000000000 --- a/res/drawable-hdpi/portal_ring_inner.png +++ /dev/null diff --git a/res/drawable-hdpi/portal_ring_inner_nolip.png b/res/drawable-hdpi/portal_ring_inner_nolip.png Binary files differdeleted file mode 100644 index 5be25fc8f..000000000 --- a/res/drawable-hdpi/portal_ring_inner_nolip.png +++ /dev/null diff --git a/res/drawable-hdpi/portal_ring_outer.png b/res/drawable-hdpi/portal_ring_outer.png Binary files differdeleted file mode 100644 index 712eeb20c..000000000 --- a/res/drawable-hdpi/portal_ring_outer.png +++ /dev/null diff --git a/res/drawable-hdpi/portal_ring_rest.png b/res/drawable-hdpi/portal_ring_rest.png Binary files differdeleted file mode 100644 index 33cec3218..000000000 --- a/res/drawable-hdpi/portal_ring_rest.png +++ /dev/null diff --git a/res/drawable-mdpi/portal_ring_inner.png b/res/drawable-mdpi/portal_ring_inner.png Binary files differdeleted file mode 100644 index 7c5e2b744..000000000 --- a/res/drawable-mdpi/portal_ring_inner.png +++ /dev/null diff --git a/res/drawable-mdpi/portal_ring_inner_nolip.png b/res/drawable-mdpi/portal_ring_inner_nolip.png Binary files differdeleted file mode 100644 index 6ccdebbee..000000000 --- a/res/drawable-mdpi/portal_ring_inner_nolip.png +++ /dev/null diff --git a/res/drawable-mdpi/portal_ring_outer.png b/res/drawable-mdpi/portal_ring_outer.png Binary files differdeleted file mode 100644 index 40a73abf7..000000000 --- a/res/drawable-mdpi/portal_ring_outer.png +++ /dev/null diff --git a/res/drawable-mdpi/portal_ring_rest.png b/res/drawable-mdpi/portal_ring_rest.png Binary files differdeleted file mode 100644 index b2c733bdd..000000000 --- a/res/drawable-mdpi/portal_ring_rest.png +++ /dev/null diff --git a/res/drawable-xhdpi/portal_ring_inner.png b/res/drawable-xhdpi/portal_ring_inner.png Binary files differdeleted file mode 100644 index b088042e7..000000000 --- a/res/drawable-xhdpi/portal_ring_inner.png +++ /dev/null diff --git a/res/drawable-xhdpi/portal_ring_inner_nolip.png b/res/drawable-xhdpi/portal_ring_inner_nolip.png Binary files differdeleted file mode 100644 index decf76698..000000000 --- a/res/drawable-xhdpi/portal_ring_inner_nolip.png +++ /dev/null diff --git a/res/drawable-xhdpi/portal_ring_outer.png b/res/drawable-xhdpi/portal_ring_outer.png Binary files differdeleted file mode 100644 index 5ab9a21cf..000000000 --- a/res/drawable-xhdpi/portal_ring_outer.png +++ /dev/null diff --git a/res/drawable-xhdpi/portal_ring_rest.png b/res/drawable-xhdpi/portal_ring_rest.png Binary files differdeleted file mode 100644 index 7d1c84236..000000000 --- a/res/drawable-xhdpi/portal_ring_rest.png +++ /dev/null diff --git a/res/drawable-xxhdpi/portal_ring_inner.png b/res/drawable-xxhdpi/portal_ring_inner.png Binary files differdeleted file mode 100644 index cd23cf7ac..000000000 --- a/res/drawable-xxhdpi/portal_ring_inner.png +++ /dev/null diff --git a/res/drawable-xxhdpi/portal_ring_inner_nolip.png b/res/drawable-xxhdpi/portal_ring_inner_nolip.png Binary files differdeleted file mode 100644 index d82b910e9..000000000 --- a/res/drawable-xxhdpi/portal_ring_inner_nolip.png +++ /dev/null diff --git a/res/drawable-xxhdpi/portal_ring_outer.png b/res/drawable-xxhdpi/portal_ring_outer.png Binary files differdeleted file mode 100644 index e5d33b252..000000000 --- a/res/drawable-xxhdpi/portal_ring_outer.png +++ /dev/null diff --git a/res/drawable-xxhdpi/portal_ring_rest.png b/res/drawable-xxhdpi/portal_ring_rest.png Binary files differdeleted file mode 100644 index d52825c21..000000000 --- a/res/drawable-xxhdpi/portal_ring_rest.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/portal_ring_inner.png b/res/drawable-xxxhdpi/portal_ring_inner.png Binary files differdeleted file mode 100644 index 59e811daa..000000000 --- a/res/drawable-xxxhdpi/portal_ring_inner.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/portal_ring_inner_nolip.png b/res/drawable-xxxhdpi/portal_ring_inner_nolip.png Binary files differdeleted file mode 100644 index c1e75857e..000000000 --- a/res/drawable-xxxhdpi/portal_ring_inner_nolip.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/portal_ring_outer.png b/res/drawable-xxxhdpi/portal_ring_outer.png Binary files differdeleted file mode 100644 index f2f818b3b..000000000 --- a/res/drawable-xxxhdpi/portal_ring_outer.png +++ /dev/null diff --git a/res/drawable-xxxhdpi/portal_ring_rest.png b/res/drawable-xxxhdpi/portal_ring_rest.png Binary files differdeleted file mode 100644 index 2af67b8ee..000000000 --- a/res/drawable-xxxhdpi/portal_ring_rest.png +++ /dev/null diff --git a/res/layout/folder_icon.xml b/res/layout/folder_icon.xml index b8d5c608b..9eb8c9a67 100644 --- a/res/layout/folder_icon.xml +++ b/res/layout/folder_icon.xml @@ -20,13 +20,6 @@ android:layout_height="match_parent" android:orientation="vertical" android:focusable="true" > - <ImageView - android:id="@+id/preview_background" - android:layout_gravity="center_horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:antialias="true" - android:src="@drawable/portal_ring_inner"/> <com.android.launcher3.BubbleTextView style="@style/Icon" android:id="@+id/folder_icon_name" diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 5832b9f0d..af3703314 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -55,7 +55,6 @@ import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.folder.FolderIcon.FolderRingAnimator; import com.android.launcher3.util.ParcelableSparseArray; import com.android.launcher3.util.Thunk; @@ -108,8 +107,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { private OnTouchListener mInterceptTouchListener; private StylusEventHelper mStylusEventHelper; - private ArrayList<FolderRingAnimator> mFolderOuterRings = new ArrayList<FolderRingAnimator>(); - private int[] mFolderLeaveBehindCell = {-1, -1}; + private ArrayList<FolderIcon.PreviewBackground> mFolderBackgrounds = new ArrayList<FolderIcon.PreviewBackground>(); + FolderIcon.PreviewBackground mFolderLeaveBehind = new FolderIcon.PreviewBackground(); + Paint mFolderBgPaint = new Paint(); private float mBackgroundAlpha; @@ -209,6 +209,9 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { mPreviousReorderDirection[0] = INVALID_DIRECTION; mPreviousReorderDirection[1] = INVALID_DIRECTION; + mFolderLeaveBehind.delegateCellX = -1; + mFolderLeaveBehind.delegateCellY = -1; + setAlwaysDrawnWithCacheEnabled(false); final Resources res = getResources(); mHotseatScale = (float) grid.hotseatIconSizePx / grid.iconSizePx; @@ -501,88 +504,62 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { } } - int previewOffset = FolderRingAnimator.sPreviewSize; - - // The folder outer / inner ring image(s) - DeviceProfile grid = mLauncher.getDeviceProfile(); - for (int i = 0; i < mFolderOuterRings.size(); i++) { - FolderRingAnimator fra = mFolderOuterRings.get(i); - - Drawable d; - int width, height; - cellToPoint(fra.mCellX, fra.mCellY, mTempLocation); - View child = getChildAt(fra.mCellX, fra.mCellY); - - if (child != null) { - int centerX = mTempLocation[0] + mCellWidth / 2; - int centerY = mTempLocation[1] + previewOffset / 2 + - child.getPaddingTop() + grid.folderBackgroundOffset; - - // Draw outer ring, if it exists - if (FolderIcon.HAS_OUTER_RING) { - d = FolderRingAnimator.sSharedOuterRingDrawable; - width = (int) (fra.getOuterRingSize() * getChildrenScale()); - height = width; - canvas.save(); - canvas.translate(centerX - width / 2, centerY - height / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - } - - // Draw inner ring - d = FolderRingAnimator.sSharedInnerRingDrawable; - width = (int) (fra.getInnerRingSize() * getChildrenScale()); - height = width; - canvas.save(); - canvas.translate(centerX - width / 2, centerY - width / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - } + for (int i = 0; i < mFolderBackgrounds.size(); i++) { + FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i); + cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); + canvas.save(); + canvas.translate(mTempLocation[0], mTempLocation[1]); + bg.drawBackground(canvas, mFolderBgPaint); + canvas.restore(); } - if (mFolderLeaveBehindCell[0] >= 0 && mFolderLeaveBehindCell[1] >= 0) { - Drawable d = FolderIcon.sSharedFolderLeaveBehind; - int width = d.getIntrinsicWidth(); - int height = d.getIntrinsicHeight(); + if (mFolderLeaveBehind.delegateCellX >= 0 && mFolderLeaveBehind.delegateCellY >= 0) { + cellToPoint(mFolderLeaveBehind.delegateCellX, + mFolderLeaveBehind.delegateCellY, mTempLocation); + canvas.save(); + canvas.translate(mTempLocation[0], mTempLocation[1]); + mFolderLeaveBehind.drawLeaveBehind(canvas, mFolderBgPaint); + canvas.restore(); + } + } - cellToPoint(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1], mTempLocation); - View child = getChildAt(mFolderLeaveBehindCell[0], mFolderLeaveBehindCell[1]); - if (child != null) { - int centerX = mTempLocation[0] + mCellWidth / 2; - int centerY = mTempLocation[1] + previewOffset / 2 + - child.getPaddingTop() + grid.folderBackgroundOffset; + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); - canvas.save(); - canvas.translate(centerX - width / 2, centerY - width / 2); - d.setBounds(0, 0, width, height); - d.draw(canvas); - canvas.restore(); - } + for (int i = 0; i < mFolderBackgrounds.size(); i++) { + FolderIcon.PreviewBackground bg = mFolderBackgrounds.get(i); + cellToPoint(bg.delegateCellX, bg.delegateCellY, mTempLocation); + canvas.save(); + canvas.translate(mTempLocation[0], mTempLocation[1]); + bg.drawBackgroundStroke(canvas, mFolderBgPaint); + canvas.restore(); } } - public void showFolderAccept(FolderRingAnimator fra) { - mFolderOuterRings.add(fra); + public void addFolderBackground(FolderIcon.PreviewBackground bg) { + mFolderBackgrounds.add(bg); } - - public void hideFolderAccept(FolderRingAnimator fra) { - if (mFolderOuterRings.contains(fra)) { - mFolderOuterRings.remove(fra); - } - invalidate(); + public void removeFolderBackground(FolderIcon.PreviewBackground bg) { + mFolderBackgrounds.remove(bg); } public void setFolderLeaveBehindCell(int x, int y) { - mFolderLeaveBehindCell[0] = x; - mFolderLeaveBehindCell[1] = y; + + DeviceProfile grid = mLauncher.getDeviceProfile(); + View child = getChildAt(x, y); + + mFolderLeaveBehind.setup(getResources().getDisplayMetrics(), grid, null, + child.getMeasuredWidth(), child.getPaddingTop()); + + mFolderLeaveBehind.delegateCellX = x; + mFolderLeaveBehind.delegateCellY = y; invalidate(); } public void clearFolderLeaveBehind() { - mFolderLeaveBehindCell[0] = -1; - mFolderLeaveBehindCell[1] = -1; + mFolderLeaveBehind.delegateCellX = -1; + mFolderLeaveBehind.delegateCellY = -1; invalidate(); } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index b67e07b33..5bfa716ee 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -85,6 +85,7 @@ public class DeviceProfile { // Folder public int folderBackgroundOffset; public int folderIconSizePx; + public int folderIconPreviewPadding; public int folderCellWidthPx; public int folderCellHeightPx; @@ -262,6 +263,7 @@ public class DeviceProfile { folderCellHeightPx = cellHeightPx + edgeMarginPx; folderBackgroundOffset = -edgeMarginPx; folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; + folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); } /** diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 70d982081..2d52341a7 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -3029,13 +3029,17 @@ public class Launcher extends Activity // We remove and re-draw the FolderIcon in-case it has changed mDragLayer.removeView(mFolderIconImageView); copyFolderIconToImage(fi); + + if (cl != null) { + cl.clearFolderLeaveBehind(); + } + ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(mFolderIconImageView, 1, 1, 1); oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration)); oa.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (cl != null) { - cl.clearFolderLeaveBehind(); // Remove the ImageView copy of the FolderIcon and make the original visible. mDragLayer.removeView(mFolderIconImageView); fi.setVisibility(View.VISIBLE); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7bfb7d58f..0f8d834da 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -59,7 +59,6 @@ import android.widget.TextView; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.folder.FolderIcon.FolderRingAnimator; import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.Launcher.LauncherOverlay; import com.android.launcher3.UninstallDropTarget.UninstallSource; @@ -224,7 +223,7 @@ public class Workspace extends PagedView public static final int REORDER_TIMEOUT = 350; private final Alarm mFolderCreationAlarm = new Alarm(); private final Alarm mReorderAlarm = new Alarm(); - @Thunk FolderRingAnimator mDragFolderRingAnimator = null; + private FolderIcon.PreviewBackground mFolderCreateBg = new FolderIcon.PreviewBackground(); private FolderIcon mDragOverFolderIcon = null; private boolean mCreateUserFolderOnDrop = false; private boolean mAddToExistingFolderOnDrop = false; @@ -2493,6 +2492,10 @@ public class Workspace extends PagedView // If the dragView is null, we can't animate boolean animate = dragView != null; if (animate) { + // In order to keep everything continuous, we hand off the currently rendered + // folder background to the newly created icon. This preserves animation state. + fi.setFolderBackground(mFolderCreateBg); + mFolderCreateBg = new FolderIcon.PreviewBackground(); fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale, postAnimationRunnable); } else { @@ -2855,10 +2858,7 @@ public class Workspace extends PagedView } private void cleanupFolderCreation() { - if (mDragFolderRingAnimator != null) { - mDragFolderRingAnimator.animateToNaturalState(); - mDragFolderRingAnimator = null; - } + mFolderCreateBg.animateToRest(); mFolderCreationAlarm.setOnAlarmListener(null); mFolderCreationAlarm.cancelAlarm(); } @@ -3166,18 +3166,16 @@ public class Workspace extends PagedView this.layout = layout; this.cellX = cellX; this.cellY = cellY; + + DeviceProfile grid = mLauncher.getDeviceProfile(); + BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY); + + mFolderCreateBg.setup(getResources().getDisplayMetrics(), grid, null, + cell.getMeasuredWidth(), cell.getPaddingTop()); } public void onAlarm(Alarm alarm) { - if (mDragFolderRingAnimator != null) { - // This shouldn't happen ever, but just in case, make sure we clean up the mess. - mDragFolderRingAnimator.animateToNaturalState(); - } - mDragFolderRingAnimator = new FolderRingAnimator(mLauncher, null); - mDragFolderRingAnimator.setCell(cellX, cellY); - mDragFolderRingAnimator.setCellLayout(layout); - mDragFolderRingAnimator.animateToAcceptState(); - layout.showFolderAccept(mDragFolderRingAnimator); + mFolderCreateBg.animateToAccept(layout, cellX, cellY); layout.clearDragOutlines(); setDragMode(DRAG_MODE_CREATE_FOLDER); } diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 285f2c1df..2b3727a8a 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -34,7 +34,7 @@ public final class FeatureFlags { // As opposed to the new spring-loaded workspace. public static boolean LAUNCHER3_LEGACY_WORKSPACE_DND = false; public static boolean LAUNCHER3_ICON_NORMALIZATION = true; - public static boolean LAUNCHER3_CLIPPED_FOLDER_ICON = false; + public static boolean LAUNCHER3_LEGACY_FOLDER_ICON = false; public static boolean LAUNCHER3_LEGACY_LOGGING = false; public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = false; } diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index 48988d7f2..68b756b24 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -15,6 +15,7 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule final float MIN_SCALE = 0.48f; final float MAX_SCALE = 0.58f; final float MAX_RADIUS_DILATION = 0.15f; + final float ITEM_RADIUS_SCALE_FACTOR = 1.33f; private float[] mTmpPoint = new float[2]; @@ -22,27 +23,19 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule private float mRadius; private float mIconSize; private boolean mIsRtl; - private Path mClipPath = new Path(); @Override public void init(int availableSpace, int intrinsicIconSize, boolean rtl) { mAvailableSpace = availableSpace; - mRadius = 0.66f * availableSpace; + mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f; mIconSize = intrinsicIconSize; mIsRtl = rtl; - - // We make the clip radius just slightly smaller than the background drawable - // TODO(adamcohen): this is hacky, needs cleanup (likely through programmatic drawing). - int clipRadius = (int) mAvailableSpace / 2 - 1; - - mClipPath.addCircle(mAvailableSpace / 2, mAvailableSpace / 2, clipRadius, Path.Direction.CW); } @Override public FolderIcon.PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems, FolderIcon.PreviewItemDrawingParams params) { - float totalScale = scaleForNumItems(curNumItems); float transX; float transY; @@ -55,7 +48,6 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule getPosition(index, curNumItems, mTmpPoint); transX = mTmpPoint[0]; transY = mTmpPoint[1]; - totalScale = scaleForNumItems(curNumItems); } if (params == null) { @@ -126,8 +118,8 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule } @Override - public Path getClipPath() { - return mClipPath; + public boolean clipToBackground() { + return true; } } diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 7b71a36d1..62007f072 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -21,16 +21,17 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.content.Context; -import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.Rect; +import android.graphics.Region; import android.graphics.drawable.Drawable; -import android.os.Looper; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -39,7 +40,6 @@ import android.view.ViewGroup; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.TextView; import com.android.launcher3.Alarm; @@ -77,15 +77,14 @@ import java.util.ArrayList; * An icon that can appear on in the workspace representing an {@link Folder}. */ public class FolderIcon extends FrameLayout implements FolderListener { - @Thunk - Launcher mLauncher; + @Thunk Launcher mLauncher; @Thunk Folder mFolder; private FolderInfo mInfo; @Thunk static boolean sStaticValuesDirty = true; - public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? - ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW : - StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; + public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ? + StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW : + ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; private CheckLongPressHelper mLongPressHelper; private StylusEventHelper mStylusEventHelper; @@ -96,36 +95,19 @@ public class FolderIcon extends FrameLayout implements FolderListener { private static final int INITIAL_ITEM_ANIMATION_DURATION = 350; private static final int FINAL_ITEM_ANIMATION_DURATION = 200; - // The degree to which the inner ring grows when accepting drop - private static final float INNER_RING_GROWTH_FACTOR = 0.15f; - - // The degree to which the outer ring is scaled in its natural state - private static final float OUTER_RING_GROWTH_FACTOR = 0.3f; - - // Flag as to whether or not to draw an outer ring. Currently none is designed. - public static final boolean HAS_OUTER_RING = true; - // 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; - public static Drawable sSharedFolderLeaveBehind = null; - - @Thunk ImageView mPreviewBackground; - @Thunk - BubbleTextView mFolderName; - - FolderRingAnimator mFolderRingAnimator = null; + @Thunk BubbleTextView mFolderName; // 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 private int mIntrinsicIconSize; - private int mAvailableSpaceInPreview; - private int mPreviewOffsetX; - private int mPreviewOffsetY; private int mTotalWidth; + PreviewBackground mBackground = new PreviewBackground(); private PreviewLayoutRule mPreviewLayoutRule; @@ -138,6 +120,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>(); private Drawable mReferenceDrawable = null; + Paint mBgPaint = new Paint(); + private Alarm mOpenAlarm = new Alarm(); @Thunk ItemInfo mDragInfo; @@ -155,17 +139,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void init() { mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); - mPreviewLayoutRule = FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON ? - new ClippedFolderIconLayoutRule() : - new StackFolderIconLayoutRule(); - setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); - } + mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ? + new StackFolderIconLayoutRule() : + new ClippedFolderIconLayoutRule(); - public boolean isDropEnabled() { - final ViewGroup cellLayoutChildren = (ViewGroup) getParent(); - final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent(); - final Workspace workspace = (Workspace) cellLayout.getParent(); - return !workspace.workspaceInModalState(); + setAccessibilityDelegate(LauncherAppState.getInstance().getAccessibilityDelegate()); } public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group, @@ -179,8 +157,13 @@ public class FolderIcon extends FrameLayout implements FolderListener { } DeviceProfile grid = launcher.getDeviceProfile(); - FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false); + + // For performance and compatibility reasons we render the preview using a software layer. + // In particular, hardware path clipping has spotty ecosystem support and bad performance. + // Software rendering also allows us to use shadow layers. + icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG)); + icon.setClipToPadding(false); icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name); icon.mFolderName.setText(folderInfo.title); @@ -188,13 +171,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams(); lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; - // Offset the preview background to center this view accordingly - icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background); - lp = (FrameLayout.LayoutParams) icon.mPreviewBackground.getLayoutParams(); - lp.topMargin = grid.folderBackgroundOffset; - lp.width = grid.folderIconSizePx; - lp.height = grid.folderIconSizePx; - icon.setTag(folderInfo); icon.setOnClickListener(launcher); icon.mInfo = folderInfo; @@ -206,7 +182,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { folder.bind(folderInfo); icon.setFolder(folder); - icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon); folderInfo.addListener(icon); icon.setOnFocusChangeListener(launcher.mFocusHandler); @@ -219,129 +194,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { return super.onSaveInstanceState(); } - public static class FolderRingAnimator { - public int mCellX; - public int mCellY; - @Thunk - CellLayout mCellLayout; - public float mOuterRingSize; - public float mInnerRingSize; - public FolderIcon mFolderIcon = null; - public static Drawable sSharedOuterRingDrawable = null; - public static Drawable sSharedInnerRingDrawable = null; - public static int sPreviewSize = -1; - public static int sPreviewPadding = -1; - - private ValueAnimator mAcceptAnimator; - private ValueAnimator mNeutralAnimator; - - public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) { - mFolderIcon = folderIcon; - Resources res = launcher.getResources(); - - // We need to reload the static values when configuration changes in case they are - // different in another configuration - if (sStaticValuesDirty) { - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new RuntimeException("FolderRingAnimator loading drawables on non-UI thread " - + Thread.currentThread()); - } - - DeviceProfile grid = launcher.getDeviceProfile(); - sPreviewSize = grid.folderIconSizePx; - sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); - sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer); - sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_nolip); - sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest); - sStaticValuesDirty = false; - } - } - - public void animateToAcceptState() { - if (mNeutralAnimator != null) { - mNeutralAnimator.cancel(); - } - mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); - mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = animation.getAnimatedFraction(); - mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mAcceptAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE); - } - } - }); - mAcceptAnimator.start(); - } - - public void animateToNaturalState() { - if (mAcceptAnimator != null) { - mAcceptAnimator.cancel(); - } - mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f); - mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); - - final int previewSize = sPreviewSize; - mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() { - public void onAnimationUpdate(ValueAnimator animation) { - final float percent = (Float) animation.getAnimatedValue(); - mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize; - mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize; - if (mCellLayout != null) { - mCellLayout.invalidate(); - } - } - }); - mNeutralAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (mCellLayout != null) { - mCellLayout.hideFolderAccept(FolderRingAnimator.this); - } - if (mFolderIcon != null) { - mFolderIcon.mPreviewBackground.setVisibility(VISIBLE); - } - } - }); - mNeutralAnimator.start(); - } - - // Location is expressed in window coordinates - public void getCell(int[] loc) { - loc[0] = mCellX; - loc[1] = mCellY; - } - - // Location is expressed in window coordinates - public void setCell(int x, int y) { - mCellX = x; - mCellY = y; - } - public void setCellLayout(CellLayout layout) { - mCellLayout = layout; - } - - public float getOuterRingSize() { - return mOuterRingSize; - } - - public float getInnerRingSize() { - return mInnerRingSize; - } - } public Folder getFolder() { return mFolder; @@ -375,11 +228,9 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void onDragEnter(ItemInfo dragInfo) { if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return; CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); - CellLayout layout = (CellLayout) getParent().getParent(); - mFolderRingAnimator.setCell(lp.cellX, lp.cellY); - mFolderRingAnimator.setCellLayout(layout); - mFolderRingAnimator.animateToAcceptState(); - layout.showFolderAccept(mFolderRingAnimator); + CellLayout cl = (CellLayout) getParent().getParent(); + + mBackground.animateToAccept(cl, lp.cellX, lp.cellY); mOpenAlarm.setOnAlarmListener(mOnOpenListener); if (SPRING_LOADING_ENABLED && ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) { @@ -445,7 +296,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } public void onDragExit() { - mFolderRingAnimator.animateToNaturalState(); + mBackground.animateToRest(); mOpenAlarm.cancelAlarm(); } @@ -531,16 +382,11 @@ public class FolderIcon extends FrameLayout implements FolderListener { mIntrinsicIconSize = drawableSize; mTotalWidth = totalSize; - final int previewSize = FolderRingAnimator.sPreviewSize; - final int previewPadding = FolderRingAnimator.sPreviewPadding; - - mAvailableSpaceInPreview = (previewSize - 2 * previewPadding); - - mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2; - mPreviewOffsetY = previewPadding + grid.folderBackgroundOffset + getPaddingTop(); - - mPreviewLayoutRule.init(mAvailableSpaceInPreview, mIntrinsicIconSize, + mBackground.setup(getResources().getDisplayMetrics(), grid, this, mTotalWidth, + getPaddingTop()); + mPreviewLayoutRule.init(mBackground.previewSize, mIntrinsicIconSize, Utilities.isRtl(getResources())); + updateItemDrawingParams(false); } } @@ -586,8 +432,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index), curNumItems, mTmpParams); - mTmpParams.transX += mPreviewOffsetX; - mTmpParams.transY += mPreviewOffsetY; + mTmpParams.transX += mBackground.basePreviewOffsetX; + mTmpParams.transY += mBackground.basePreviewOffsetY; float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2; float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2; @@ -610,7 +456,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { float iconSize = mLauncher.getDeviceProfile().iconSizePx; final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth(); - final float trans = (mAvailableSpaceInPreview - iconSize) / 2; + final float trans = (mBackground.previewSize - iconSize) / 2; params.update(trans, trans, scale); return params; @@ -642,24 +488,284 @@ public class FolderIcon extends FrameLayout implements FolderListener { canvas.restore(); } + public static class PreviewBackground { + private float mScale = 1f; + private float mColorMultiplier = 1f; + private Path mClipPath = new Path(); + private int mStrokeWidth; + private View mInvalidateDeligate; + + public int previewSize; + private int basePreviewOffsetX; + private int basePreviewOffsetY; + + private CellLayout mDrawingDelegate; + public int delegateCellX; + public int delegateCellY; + + // Drawing / animation configurations + private static final float ACCEPT_SCALE_FACTOR = 1.25f; + private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f; + + // Expressed on a scale from 0 to 255. + private static final int BG_OPACITY = 160; + private static final int MAX_BG_OPACITY = 225; + private static final int BG_INTENSITY = 245; + private static final int SHADOW_OPACITY = 80; + + ValueAnimator mScaleAnimator; + + public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDeligate, + int availableSpace, int topPadding) { + mInvalidateDeligate = invalidateDeligate; + + final int previewSize = grid.folderIconSizePx; + final int previewPadding = grid.folderIconPreviewPadding; + + this.previewSize = (previewSize - 2 * previewPadding); + + basePreviewOffsetX = (availableSpace - this.previewSize) / 2; + basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding; + + mStrokeWidth = Utilities.pxFromDp(1, dm); + + invalidate(); + } + + int getRadius() { + return previewSize / 2; + } + + int getScaledRadius() { + return (int) (mScale * getRadius()); + } + + int getOffsetX() { + return basePreviewOffsetX - (getScaledRadius() - getRadius()); + } + + int getOffsetY() { + return basePreviewOffsetY - (getScaledRadius() - getRadius()); + } + + void invalidate() { + int radius = getScaledRadius(); + mClipPath.reset(); + mClipPath.addCircle(radius, radius, radius, Path.Direction.CW); + + if (mInvalidateDeligate != null) { + mInvalidateDeligate.invalidate(); + } + + if (mDrawingDelegate != null) { + mDrawingDelegate.invalidate(); + } + } + + void setInvalidateDeligate(View invalidateDeligate) { + mInvalidateDeligate = invalidateDeligate; + invalidate(); + } + + public void drawBackground(Canvas canvas, Paint paint) { + canvas.save(); + canvas.translate(getOffsetX(), getOffsetY()); + + paint.reset(); + paint.setStyle(Paint.Style.FILL); + paint.setXfermode(null); + paint.setAntiAlias(true); + + int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + + float radius = getScaledRadius(); + + canvas.drawCircle(radius, radius, radius, paint); + canvas.clipPath(mClipPath, Region.Op.DIFFERENCE); + + paint.setStyle(Paint.Style.STROKE); + paint.setColor(Color.TRANSPARENT); + paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0)); + canvas.drawCircle(radius, radius, radius, paint); + + canvas.restore(); + } + + public void drawBackgroundStroke(Canvas canvas, Paint paint) { + canvas.save(); + canvas.translate(getOffsetX(), getOffsetY()); + + paint.reset(); + paint.setAntiAlias(true); + paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY)); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(mStrokeWidth); + + float radius = getScaledRadius(); + canvas.drawCircle(radius, radius, radius - 1, paint); + + canvas.restore(); + } + + public void drawLeaveBehind(Canvas canvas, Paint paint) { + float originalScale = mScale; + mScale = 0.5f; + + canvas.save(); + canvas.translate(getOffsetX(), getOffsetY()); + + paint.reset(); + paint.setAntiAlias(true); + paint.setColor(Color.argb(160, 245, 245, 245)); + + float radius = getScaledRadius(); + canvas.drawCircle(radius, radius, radius, paint); + + canvas.restore(); + mScale = originalScale; + } + + // It is the callers responsibility to save and restore the canvas. + private void clipCanvas(Canvas canvas) { + canvas.translate(getOffsetX(), getOffsetY()); + canvas.clipPath(mClipPath); + canvas.translate(-getOffsetX(), -getOffsetY()); + } + + private void delegateDrawing(CellLayout delegate, int cellX, int cellY) { + if (mDrawingDelegate != delegate) { + delegate.addFolderBackground(this); + } + + mDrawingDelegate = delegate; + delegateCellX = cellX; + delegateCellY = cellY; + + invalidate(); + } + + private void clearDrawingDelegate() { + if (mDrawingDelegate != null) { + mDrawingDelegate.removeFolderBackground(this); + } + + mDrawingDelegate = null; + invalidate(); + } + + private boolean drawingDelegated() { + return mDrawingDelegate != null; + } + + private void animateScale(float finalScale, float finalMultiplier, + final Runnable onStart, final Runnable onEnd) { + final float scale0 = mScale; + final float scale1 = finalScale; + + final float bgMultiplier0 = mColorMultiplier; + final float bgMultiplier1 = finalMultiplier; + + if (mScaleAnimator != null) { + mScaleAnimator.cancel(); + } + + mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f); + + mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float prog = animation.getAnimatedFraction(); + mScale = prog * scale1 + (1 - prog) * scale0; + mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0; + invalidate(); + } + }); + mScaleAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (onStart != null) { + onStart.run(); + } + } + + @Override + public void onAnimationEnd(Animator animation) { + if (onEnd != null) { + onEnd.run(); + } + mScaleAnimator = null; + } + }); + + mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION); + mScaleAnimator.start(); + } + + public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) { + Runnable onStart = new Runnable() { + @Override + public void run() { + delegateDrawing(cl, cellX, cellY); + } + }; + animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null); + } + + public void animateToRest() { + // This can be called multiple times -- we need to make sure the drawing delegate + // is saved and restored at the beginning of the animation, since cancelling the + // existing animation can clear the delgate. + final CellLayout cl = mDrawingDelegate; + final int cellX = delegateCellX; + final int cellY = delegateCellY; + + Runnable onStart = new Runnable() { + @Override + public void run() { + delegateDrawing(cl, cellX, cellY); + } + }; + Runnable onEnd = new Runnable() { + @Override + public void run() { + clearDrawingDelegate(); + } + }; + animateScale(1f, 1f, onStart, onEnd); + } + } + + public void setFolderBackground(PreviewBackground bg) { + mBackground = bg; + mBackground.setInvalidateDeligate(this); + } + @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); - if (mFolder == null) return; - if (mFolder.getItemCount() == 0 && !mAnimating) return; - if (mReferenceDrawable != null) { computePreviewDrawingParams(mReferenceDrawable); } + if (!mBackground.drawingDelegated()) { + mBackground.drawBackground(canvas, mBgPaint); + } + + if (mFolder == null) return; + if (mFolder.getItemCount() == 0 && !mAnimating) return; + canvas.save(); - canvas.translate(mPreviewOffsetX, mPreviewOffsetY); - Path clipPath = mPreviewLayoutRule.getClipPath(); - if (clipPath != null) { - canvas.clipPath(clipPath); + + + if (mPreviewLayoutRule.clipToBackground()) { + mBackground.clipCanvas(canvas); } + // The items are drawn in coordinates relative to the preview offset + canvas.translate(mBackground.basePreviewOffsetX, mBackground.basePreviewOffsetY); + // The first item should be drawn last (ie. on top of later items) for (int i = mDrawingParams.size() - 1; i >= 0; i--) { PreviewItemDrawingParams p = mDrawingParams.get(i); @@ -668,6 +774,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { } } canvas.restore(); + + if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) { + mBackground.drawBackgroundStroke(canvas, mBgPaint); + } } private Drawable getTopDrawable(TextView v) { @@ -793,7 +903,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { PreviewItemDrawingParams p = mDrawingParams.get(i); p.drawable = getTopDrawable((TextView) items.get(i)); - if (!animate || !FeatureFlags.LAUNCHER3_CLIPPED_FOLDER_ICON) { + if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { computePreviewItemDrawingParams(i, nItemsInPreview, p); if (mReferenceDrawable == null) { mReferenceDrawable = p.drawable; @@ -884,6 +994,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { public void init(int availableSpace, int intrinsicIconSize, boolean rtl); public int numItems(); - public Path getClipPath(); + public boolean clipToBackground(); } } diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java index 005307209..7fb02e313 100644 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -85,7 +85,7 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { } @Override - public Path getClipPath() { - return null; + public boolean clipToBackground() { + return false; } } |