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
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 95cdbdd..32b2c9a 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -39,6 +39,7 @@
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 @@
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 cf1cb45..92d891f 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -31,6 +31,7 @@
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.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.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.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 @@
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 @@
@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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 ee6ea99..af8be8d 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,8 +35,6 @@
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 @@
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 @@
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 @@
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 @@
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 @@
: 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 c63d69d..33d8be0 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -79,9 +79,6 @@
// +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 @@
private final int[] mColors;
private final HashMap<String, View> mViewForRect = new HashMap<>();
- private final int mColorExtractionIndex;
@Nullable private LocalColorExtractor mColorExtractor;
public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -128,9 +124,6 @@
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 8ae6b2e..a66024a 100644
--- a/src/com/android/launcher3/widget/LocalColorExtractor.java
+++ b/src/com/android/launcher3/widget/LocalColorExtractor.java
@@ -28,6 +28,7 @@
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 @@
/** 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 @@
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;
+ }
}