From 0b6fa71deb6b346e0e6ddbde1acc754f9bb48311 Mon Sep 17 00:00:00 2001 From: Jon Miranda Date: Wed, 5 May 2021 15:36:27 -0700 Subject: [PATCH] Add local wallpaper color extraction to folder backgrounds. - Starts listening to local wallpaper colors as soon as folder is positioned. - The folder open animation already has a background color animator. - For simplicity, folder open animation always starts first, and then we cancel its background color animator, and start a new animator to the extracted color. - setAutoCancel would be handy here, but it does not work, will track in a separate bug. - Since folders are reused, we clear our the color extractor on folder close. Bug: 175329686 Test: multiple folders on rainbow background open each folder, ensure bg animates to proper color Change-Id: Ie0f2c0632fa2361f7710ba247d74f882620dec16 --- .../launcher3/AbstractFloatingView.java | 10 ++ src/com/android/launcher3/folder/Folder.java | 109 ++++++++++++++++-- .../folder/FolderAnimationManager.java | 28 +++-- .../android/launcher3/popup/ArrowPopup.java | 7 -- .../launcher3/widget/LocalColorExtractor.java | 14 +++ 5 files changed, 145 insertions(+), 23 deletions(-) diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 95cdbdd147..32b2c9a755 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -39,6 +39,7 @@ import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import com.android.launcher3.widget.LocalColorExtractor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -108,12 +109,21 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch protected boolean mIsOpen; + // Index used to get background color when using local wallpaper color extraction. + protected int mColorExtractionIndex; + public AbstractFloatingView(Context context, AttributeSet attrs) { super(context, attrs); + init(context); } public AbstractFloatingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(context); + } + + private void init(Context context) { + mColorExtractionIndex = LocalColorExtractor.getColorIndex(context); } /** diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index cf1cb45440..92d891f774 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -31,6 +31,7 @@ import static com.android.launcher3.util.DisplayController.getSingleFrameMs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.appwidget.AppWidgetHostView; import android.content.Context; @@ -38,6 +39,8 @@ import android.graphics.Canvas; import android.graphics.Insets; import android.graphics.Path; import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.text.InputType; import android.text.Selection; @@ -45,6 +48,7 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; +import android.util.SparseIntArray; import android.util.TypedValue; import android.view.FocusFinder; import android.view.KeyEvent; @@ -96,13 +100,16 @@ import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.util.Executors; +import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; import com.android.launcher3.views.ClipPathView; +import com.android.launcher3.widget.LocalColorExtractor; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -152,6 +159,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private static final float ICON_OVERSCROLL_WIDTH_FACTOR = 0.45f; private static final int FOLDER_NAME_ANIMATION_DURATION = 633; + private static final int FOLDER_COLOR_ANIMATION_DURATION = 150; private static final int REORDER_DELAY = 250; private static final int ON_EXIT_CLOSE_DELAY = 400; @@ -224,6 +232,19 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo @Nullable private FolderWindowInsetsAnimationCallback mFolderWindowInsetsAnimationCallback; + // Wallpaper local color extraction + @Nullable private LocalColorExtractor mColorExtractor; + @Nullable private LocalColorExtractor.Listener mColorListener; + private final Rect mTempRect = new Rect(); + private final RectF mTempRectF = new RectF(); + + // For simplicity, we start the color change only after the open animation has started. + private Runnable mColorChangeRunnable; + private Animator mColorChangeAnimator; + // The background color animator used in the folder open animation. We keep a reference to this, + // so that we can cancel it when starting mColorChangeAnimator. + private ObjectAnimator mOpenAnimationColorChangeAnimator; + /** * Used to inflate the Workspace from XML. * @@ -241,7 +262,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // name is complete, we have something to focus on, thus hiding the cursor and giving // reliable behavior when clicking the text field (since it will always gain focus on click). setFocusableInTouchMode(true); - } @Override @@ -276,6 +296,45 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setWindowInsetsAnimationCallback(mFolderWindowInsetsAnimationCallback); } + + if (Utilities.ATLEAST_S) { + mColorExtractor = LocalColorExtractor.newInstance(mLauncher); + mColorListener = (RectF rect, SparseIntArray extractedColors) -> { + mColorChangeRunnable = () -> { + mColorChangeRunnable = null; + int duration = FOLDER_COLOR_ANIMATION_DURATION; + + // Cancel the open animation color change animator. + ObjectAnimator existingAnim = mOpenAnimationColorChangeAnimator; + if (existingAnim != null && existingAnim.isRunning()) { + duration = (int) Math.max(FOLDER_COLOR_ANIMATION_DURATION, + existingAnim.getDuration() * (1f - existingAnim.getDuration())); + existingAnim.cancel(); + mOpenAnimationColorChangeAnimator = null; + } + + // Start a new animator to the extracted color. + int newColor = extractedColors.get(mColorExtractionIndex); + GradientDrawable bg = (GradientDrawable) getBackground(); + mColorChangeAnimator = ObjectAnimator.ofArgb(bg, "color", + bg.getColor().getDefaultColor(), newColor).setDuration(duration); + mColorChangeAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mColorChangeAnimator = null; + } + }); + mColorChangeAnimator.start(); + }; + + // If the folder open animation has started, we can start the color change now. + // Otherwise we wait for it to start. + if (mOpenAnimationColorChangeAnimator != null + && mOpenAnimationColorChangeAnimator.isStarted()) { + post(mColorChangeRunnable); + } + }; + } } public boolean onLongClick(View v) { @@ -652,15 +711,18 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. mDeleteFolderOnDropCompleted = false; - if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { - mCurrentAnimator.cancel(); - } - AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator(); + cancelRunningAnimations(); + FolderAnimationManager fam = new FolderAnimationManager(this, true /* isOpening */); + AnimatorSet anim = fam.getAnimator(); + mOpenAnimationColorChangeAnimator = fam.getBgColorAnimator(); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mFolderIcon.setIconVisible(false); mFolderIcon.drawLeaveBehindIfExists(); + if (mColorChangeRunnable != null) { + mColorChangeRunnable.run(); + } } @Override public void onAnimationEnd(Animator animation) { @@ -752,6 +814,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private void cancelRunningAnimations() { + if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { + mCurrentAnimator.cancel(); + } + if (mColorChangeAnimator != null && mColorChangeAnimator.isRunning()) { + mColorChangeAnimator.cancel(); + } + } + private void animateClosed() { if (mIsAnimatingClosed) { return; @@ -760,9 +831,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.completePendingPageChanges(); mContent.snapToPageImmediately(mContent.getDestinationPage()); - if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) { - mCurrentAnimator.cancel(); - } + cancelRunningAnimations(); AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator(); a.addListener(new AnimatorListenerAdapter() { @Override @@ -837,6 +906,19 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo clearDragInfo(); mState = STATE_SMALL; mContent.setCurrentPage(0); + + mOpenAnimationColorChangeAnimator = null; + mColorChangeRunnable = null; + if (mColorChangeAnimator != null) { + mColorChangeAnimator.cancel(); + mColorChangeAnimator = null; + } + if (mColorExtractor != null) { + mColorExtractor.removeLocations(); + mColorExtractor.setListener(null); + } + GradientDrawable bg = (GradientDrawable) getBackground(); + bg.setColor(Themes.getAttrColor(getContext(), R.attr.folderFillColor)); } @Override @@ -1104,6 +1186,17 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.height = height; lp.x = left; lp.y = top; + + if (mColorExtractor != null) { + mColorExtractor.removeLocations(); + mColorExtractor.setListener(mColorListener); + mTempRect.set(lp.x, lp.y, lp.x + lp.width, lp.y + lp.height); + mColorExtractor.getExtractedRectForViewRect(mLauncher, + mLauncher.getWorkspace().getCurrentPage(), mTempRect, mTempRectF); + if (!mTempRectF.isEmpty()) { + mColorExtractor.addLocation(Arrays.asList(mTempRectF)); + } + } } protected int getContentAreaHeight() { diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java index ee6ea99e77..af8be8d2f9 100644 --- a/src/com/android/launcher3/folder/FolderAnimationManager.java +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -35,8 +35,6 @@ import android.util.Property; import android.view.View; import android.view.animation.AnimationUtils; -import androidx.core.graphics.ColorUtils; - import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.R; @@ -80,6 +78,8 @@ public class FolderAnimationManager { private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); private final FolderGridOrganizer mPreviewVerifier; + private ObjectAnimator mBgColorAnimator; + public FolderAnimationManager(Folder folder, boolean isOpening) { mFolder = folder; mContent = folder.mContent; @@ -105,6 +105,12 @@ public class FolderAnimationManager { R.interpolator.large_folder_preview_item_close_interpolator); } + /** + * Returns the animator that changes the background color. + */ + public ObjectAnimator getBgColorAnimator() { + return mBgColorAnimator; + } /** * Prepares the Folder for animating between open / closed states. @@ -162,10 +168,15 @@ public class FolderAnimationManager { final float yDistance = initialY - lp.y; // Set up the Folder background. - final int finalColor = ColorUtils.setAlphaComponent( - Themes.getAttrColor(mContext, R.attr.folderFillColor), 255); - final int initialColor = setColorAlphaBound( - finalColor, mPreviewBackground.getBackgroundAlpha()); + int previewBackgroundColor = Themes.getAttrColor(mContext, R.attr.folderFillColor); + final int finalColor; + if (mIsOpening) { + finalColor = Themes.getAttrColor(mContext, R.attr.popupColorPrimary); + } else { + finalColor = mFolderBackground.getColor().getDefaultColor(); + } + final int initialColor = setColorAlphaBound(previewBackgroundColor, + mPreviewBackground.getBackgroundAlpha()); mFolderBackground.mutate(); mFolderBackground.setColor(mIsOpening ? initialColor : finalColor); @@ -193,11 +204,12 @@ public class FolderAnimationManager { play(a, anim); } + mBgColorAnimator = getAnimator(mFolderBackground, "color", initialColor, finalColor); + play(a, mBgColorAnimator); play(a, getAnimator(mFolder, View.TRANSLATION_X, xDistance, 0f)); play(a, getAnimator(mFolder, View.TRANSLATION_Y, yDistance, 0f)); play(a, getAnimator(mFolder.mContent, SCALE_PROPERTY, initialScale, finalScale)); play(a, getAnimator(mFolder.mFooter, SCALE_PROPERTY, initialScale, finalScale)); - play(a, getAnimator(mFolderBackground, "color", initialColor, finalColor)); play(a, mFolderIcon.mFolderName.createTextAlphaAnimator(!mIsOpening)); play(a, getShape().createRevealAnimator( mFolder, startRect, endRect, finalRadius, !mIsOpening)); @@ -409,7 +421,7 @@ public class FolderAnimationManager { : ObjectAnimator.ofFloat(view, property, v2, v1); } - private Animator getAnimator(GradientDrawable drawable, String property, int v1, int v2) { + private ObjectAnimator getAnimator(GradientDrawable drawable, String property, int v1, int v2) { return mIsOpening ? ObjectAnimator.ofArgb(drawable, property, v1, v2) : ObjectAnimator.ofArgb(drawable, property, v2, v1); diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java index c63d69dfd3..33d8be034b 100644 --- a/src/com/android/launcher3/popup/ArrowPopup.java +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -79,9 +79,6 @@ public abstract class ArrowPopup> // +1 for system shortcut view private static final int MAX_NUM_CHILDREN = MAX_SHORTCUTS + 1; - // Index used to get background color when using local wallpaper color extraction, - private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50; - private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_800; private final Rect mTempRect = new Rect(); @@ -119,7 +116,6 @@ public abstract class ArrowPopup> private final int[] mColors; private final HashMap mViewForRect = new HashMap<>(); - private final int mColorExtractionIndex; @Nullable private LocalColorExtractor mColorExtractor; public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) { @@ -128,9 +124,6 @@ public abstract class ArrowPopup> mOutlineRadius = Themes.getDialogCornerRadius(context); mLauncher = BaseDraggingActivity.fromContext(context); mIsRtl = Utilities.isRtl(getResources()); - mColorExtractionIndex = Utilities.isDarkTheme(context) - ? DARK_COLOR_EXTRACTION_INDEX - : LIGHT_COLOR_EXTRACTION_INDEX; setClipToOutline(true); setOutlineProvider(new ViewOutlineProvider() { @Override diff --git a/src/com/android/launcher3/widget/LocalColorExtractor.java b/src/com/android/launcher3/widget/LocalColorExtractor.java index 8ae6b2e435..a66024aafc 100644 --- a/src/com/android/launcher3/widget/LocalColorExtractor.java +++ b/src/com/android/launcher3/widget/LocalColorExtractor.java @@ -28,6 +28,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.Launcher; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.util.ResourceBasedOverride; import java.util.List; @@ -35,6 +36,10 @@ import java.util.List; /** Extracts the colors we need from the wallpaper at given locations. */ public class LocalColorExtractor implements ResourceBasedOverride { + // Index used to get background color when using local wallpaper color extraction, + private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50; + private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_800; + /** Listener for color changes on a screen location. */ public interface Listener { /** @@ -103,4 +108,13 @@ public class LocalColorExtractor implements ResourceBasedOverride { RectF colorExtractionRectOut) { // no-op } + + /** + * Returns an index used to query the color of interest from the list of extracted colors. + */ + public static int getColorIndex(Context context) { + return Utilities.isDarkTheme(context) + ? DARK_COLOR_EXTRACTION_INDEX + : LIGHT_COLOR_EXTRACTION_INDEX; + } }