Merge changes Ib2bc8419,Ia41cb0e3,I43bfbc41 into sc-v2-dev am: 2d5a8dc847
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/15677716 Change-Id: I259caf452690d772c413824d99742a23ad95949b
This commit is contained in:
committed by
Automerger Merge Worker
commit
f08ff393fb
@@ -166,5 +166,6 @@
|
||||
<dimen name="taskbar_stashed_size">24dp</dimen>
|
||||
<dimen name="taskbar_stashed_handle_width">220dp</dimen>
|
||||
<dimen name="taskbar_stashed_handle_height">6dp</dimen>
|
||||
<dimen name="taskbar_edu_bg_corner_radius">28dp</dimen>
|
||||
<dimen name="taskbar_edu_wave_anim_trans_y">25dp</dimen>
|
||||
<dimen name="taskbar_edu_wave_anim_trans_y_return_overshoot">4dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -200,6 +200,7 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
}
|
||||
|
||||
int predictionIndex = 0;
|
||||
int numViewsAnimated = 0;
|
||||
ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
|
||||
// make sure predicted icon removal and filling predictions don't step on each other
|
||||
if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
|
||||
@@ -233,7 +234,11 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
|
||||
if (isPredictedIcon(child) && child.isEnabled()) {
|
||||
PredictedAppIcon icon = (PredictedAppIcon) child;
|
||||
icon.applyFromWorkspaceItem(predictedItem);
|
||||
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
|
||||
icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
|
||||
if (animateIconChange) {
|
||||
numViewsAnimated++;
|
||||
}
|
||||
icon.finishBinding(mPredictionLongClickListener);
|
||||
} else {
|
||||
newItems.add(predictedItem);
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
|
||||
import com.android.launcher3.util.OnboardingPrefs;
|
||||
@@ -45,6 +46,9 @@ import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A data source which integrates with a Launcher instance
|
||||
*/
|
||||
@@ -268,6 +272,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
mTaskbarOverrideBackgroundAlpha.updateValue(forceHide ? 0 : 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
|
||||
return Arrays.stream(mLauncher.getAppsView().getAppsStore().getApps());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the taskbar education flow, if the user hasn't seen it yet.
|
||||
*/
|
||||
|
||||
@@ -78,6 +78,7 @@ public class TaskbarControllers {
|
||||
taskbarKeyguardController.init(navbarButtonsViewController);
|
||||
stashedHandleViewController.init(this);
|
||||
taskbarStashController.init(this);
|
||||
taskbarEduController.init(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,16 +15,72 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.DEACCEL;
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.TimeInterpolator;
|
||||
import android.content.res.Resources;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Handles the Taskbar Education flow. */
|
||||
public class TaskbarEduController {
|
||||
|
||||
private static final long WAVE_ANIM_DELAY = 250;
|
||||
private static final long WAVE_ANIM_STAGGER = 50;
|
||||
private static final long WAVE_ANIM_EACH_ICON_DURATION = 633;
|
||||
private static final long WAVE_ANIM_SLOT_MACHINE_DURATION = 1085;
|
||||
// The fraction of each icon's animation at which we reach the top point of the wave.
|
||||
private static final float WAVE_ANIM_FRACTION_TOP = 0.4f;
|
||||
// The fraction of each icon's animation at which we reach the bottom, before overshooting.
|
||||
private static final float WAVE_ANIM_FRACTION_BOTTOM = 0.9f;
|
||||
private static final TimeInterpolator WAVE_ANIM_TO_TOP_INTERPOLATOR = FAST_OUT_SLOW_IN;
|
||||
private static final TimeInterpolator WAVE_ANIM_TO_BOTTOM_INTERPOLATOR = ACCEL_2;
|
||||
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_INTERPOLATOR = DEACCEL;
|
||||
private static final TimeInterpolator WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR = ACCEL_DEACCEL;
|
||||
private static final float WAVE_ANIM_ICON_SCALE = 1.2f;
|
||||
// How many icons to cycle through in the slot machine (+ the original icon at each end).
|
||||
private static final int WAVE_ANIM_SLOT_MACHINE_NUM_ICONS = 3;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final float mWaveAnimTranslationY;
|
||||
private final float mWaveAnimTranslationYReturnOvershoot;
|
||||
|
||||
// Initialized in init.
|
||||
TaskbarControllers mControllers;
|
||||
|
||||
private TaskbarEduView mTaskbarEduView;
|
||||
private Animator mAnim;
|
||||
|
||||
public TaskbarEduController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
|
||||
final Resources resources = activity.getResources();
|
||||
mWaveAnimTranslationY = resources.getDimension(R.dimen.taskbar_edu_wave_anim_trans_y);
|
||||
mWaveAnimTranslationYReturnOvershoot = resources.getDimension(
|
||||
R.dimen.taskbar_edu_wave_anim_trans_y_return_overshoot);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
}
|
||||
|
||||
void showEdu() {
|
||||
@@ -35,6 +91,7 @@ public class TaskbarEduController {
|
||||
mTaskbarEduView.init(new TaskbarEduCallbacks());
|
||||
mTaskbarEduView.addOnCloseListener(() -> mTaskbarEduView = null);
|
||||
mTaskbarEduView.show();
|
||||
startAnim(createWaveAnim());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,6 +101,90 @@ public class TaskbarEduController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the given animation, ending the previous animation first if it's still playing.
|
||||
*/
|
||||
private void startAnim(Animator anim) {
|
||||
if (mAnim != null) {
|
||||
mAnim.end();
|
||||
}
|
||||
mAnim = anim;
|
||||
mAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mAnim = null;
|
||||
}
|
||||
});
|
||||
mAnim.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a staggered "wave" animation where each icon translates and scales up in succession.
|
||||
*/
|
||||
private Animator createWaveAnim() {
|
||||
AnimatorSet waveAnim = new AnimatorSet();
|
||||
View[] icons = mControllers.taskbarViewController.getIconViews();
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
View icon = icons[i];
|
||||
AnimatorSet iconAnim = new AnimatorSet();
|
||||
|
||||
Keyframe[] scaleKeyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0, 1f),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, WAVE_ANIM_ICON_SCALE),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 1f),
|
||||
Keyframe.ofFloat(1f, 1f)
|
||||
};
|
||||
scaleKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
|
||||
scaleKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
|
||||
|
||||
Keyframe[] translationYKeyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0, 0f),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_TOP, -mWaveAnimTranslationY),
|
||||
Keyframe.ofFloat(WAVE_ANIM_FRACTION_BOTTOM, 0f),
|
||||
// Half of the remaining fraction overshoots, then the other half returns to 0.
|
||||
Keyframe.ofFloat(
|
||||
WAVE_ANIM_FRACTION_BOTTOM + (1 - WAVE_ANIM_FRACTION_BOTTOM) / 2f,
|
||||
mWaveAnimTranslationYReturnOvershoot),
|
||||
Keyframe.ofFloat(1f, 0f)
|
||||
};
|
||||
translationYKeyframes[1].setInterpolator(WAVE_ANIM_TO_TOP_INTERPOLATOR);
|
||||
translationYKeyframes[2].setInterpolator(WAVE_ANIM_TO_BOTTOM_INTERPOLATOR);
|
||||
translationYKeyframes[3].setInterpolator(WAVE_ANIM_OVERSHOOT_INTERPOLATOR);
|
||||
translationYKeyframes[4].setInterpolator(WAVE_ANIM_OVERSHOOT_RETURN_INTERPOLATOR);
|
||||
|
||||
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
|
||||
PropertyValuesHolder.ofKeyframe(SCALE_PROPERTY, scaleKeyframes))
|
||||
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
|
||||
iconAnim.play(ObjectAnimator.ofPropertyValuesHolder(icon,
|
||||
PropertyValuesHolder.ofKeyframe(View.TRANSLATION_Y, translationYKeyframes))
|
||||
.setDuration(WAVE_ANIM_EACH_ICON_DURATION));
|
||||
|
||||
if (icon instanceof PredictedAppIcon) {
|
||||
// Play slot machine animation through random icons from AllAppsList.
|
||||
PredictedAppIcon predictedAppIcon = (PredictedAppIcon) icon;
|
||||
ItemInfo itemInfo = (ItemInfo) icon.getTag();
|
||||
List<BitmapInfo> iconsToAnimate = mControllers.uiController.getAppIconsForEdu()
|
||||
.filter(appInfo -> !TextUtils.equals(appInfo.title, itemInfo.title))
|
||||
.map(appInfo -> appInfo.bitmap)
|
||||
.filter(bitmap -> !bitmap.isNullOrLowRes())
|
||||
.collect(Collectors.toList());
|
||||
// Pick n icons at random.
|
||||
Collections.shuffle(iconsToAnimate);
|
||||
if (iconsToAnimate.size() > WAVE_ANIM_SLOT_MACHINE_NUM_ICONS) {
|
||||
iconsToAnimate = iconsToAnimate.subList(0, WAVE_ANIM_SLOT_MACHINE_NUM_ICONS);
|
||||
}
|
||||
Animator slotMachineAnim = predictedAppIcon.createSlotMachineAnim(iconsToAnimate);
|
||||
if (slotMachineAnim != null) {
|
||||
iconAnim.play(slotMachineAnim.setDuration(WAVE_ANIM_SLOT_MACHINE_DURATION));
|
||||
}
|
||||
}
|
||||
|
||||
iconAnim.setStartDelay(WAVE_ANIM_STAGGER * i);
|
||||
waveAnim.play(iconAnim);
|
||||
}
|
||||
waveAnim.setStartDelay(WAVE_ANIM_DELAY);
|
||||
return waveAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarEduView} to interact with its controller.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
|
||||
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
|
||||
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.content.Context;
|
||||
@@ -33,6 +33,7 @@ import com.android.launcher3.views.AbstractSlideInView;
|
||||
public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
|
||||
implements Insettable {
|
||||
|
||||
private static final int DEFAULT_OPEN_DURATION = 500;
|
||||
private static final int DEFAULT_CLOSE_DURATION = 200;
|
||||
|
||||
private final Rect mInsets = new Rect();
|
||||
@@ -129,8 +130,8 @@ public class TaskbarEduView extends AbstractSlideInView<TaskbarActivityContext>
|
||||
mIsOpen = true;
|
||||
mOpenCloseAnimator.setValues(
|
||||
PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
|
||||
mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
|
||||
mOpenCloseAnimator.start();
|
||||
mOpenCloseAnimator.setInterpolator(AGGRESSIVE_EASE);
|
||||
mOpenCloseAnimator.setDuration(DEFAULT_OPEN_DURATION).start();
|
||||
}
|
||||
|
||||
void snapToPage(int page) {
|
||||
|
||||
@@ -17,6 +17,10 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Base class for providing different taskbar UI
|
||||
*/
|
||||
@@ -35,4 +39,8 @@ public class TaskbarUIController {
|
||||
protected void updateContentInsets(Rect outContentInsets) { }
|
||||
|
||||
protected void onStashedInAppChanged() { }
|
||||
|
||||
public Stream<ItemInfoWithIcon> getAppIconsForEdu() {
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
*/
|
||||
protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
|
||||
int nextViewIndex = 0;
|
||||
int numViewsAnimated = 0;
|
||||
|
||||
for (int i = 0; i < hotseatItemInfos.length; i++) {
|
||||
ItemInfo hotseatItemInfo = hotseatItemInfos[i];
|
||||
@@ -173,8 +174,14 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
// Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
|
||||
if (hotseatView instanceof BubbleTextView
|
||||
&& hotseatItemInfo instanceof WorkspaceItemInfo) {
|
||||
((BubbleTextView) hotseatView).applyFromWorkspaceItem(
|
||||
(WorkspaceItemInfo) hotseatItemInfo);
|
||||
BubbleTextView btv = (BubbleTextView) hotseatView;
|
||||
WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
|
||||
|
||||
boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
|
||||
btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
|
||||
if (animate) {
|
||||
numViewsAnimated++;
|
||||
}
|
||||
}
|
||||
setClickAndLongClickListenersForIcon(hotseatView);
|
||||
nextViewIndex++;
|
||||
@@ -259,6 +266,18 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
|
||||
return mIconLayoutBounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app icons currently shown in the taskbar.
|
||||
*/
|
||||
public View[] getIconViews() {
|
||||
final int count = getChildCount();
|
||||
View[] icons = new View[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
icons[i] = getChildAt(i);
|
||||
}
|
||||
return icons;
|
||||
}
|
||||
|
||||
// FolderIconParent implemented methods.
|
||||
|
||||
@Override
|
||||
|
||||
@@ -121,6 +121,10 @@ public class TaskbarViewController {
|
||||
return mTaskbarView.getIconLayoutBounds();
|
||||
}
|
||||
|
||||
public View[] getIconViews() {
|
||||
return mTaskbarView.getIconViews();
|
||||
}
|
||||
|
||||
public AnimatedFloat getTaskbarIconScaleForStash() {
|
||||
return mTaskbarIconScaleForStash;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
*/
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.BlurMaskFilter;
|
||||
import android.graphics.Canvas;
|
||||
@@ -23,8 +33,10 @@ import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@@ -35,6 +47,8 @@ import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
@@ -45,6 +59,10 @@ import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.DoubleShadowBubbleTextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A BubbleTextView with a ring around it's drawable
|
||||
*/
|
||||
@@ -53,6 +71,9 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private static final int RING_SHADOW_COLOR = 0x99000000;
|
||||
private static final float RING_EFFECT_RATIO = 0.095f;
|
||||
|
||||
private static final long ICON_CHANGE_ANIM_DURATION = 360;
|
||||
private static final long ICON_CHANGE_ANIM_STAGGER = 50;
|
||||
|
||||
boolean mIsDrawingDot = false;
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -67,6 +88,25 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private int mPlateColor;
|
||||
boolean mDrawForDrag = false;
|
||||
|
||||
// Used for the "slot-machine" education animation.
|
||||
private List<Drawable> mSlotMachineIcons;
|
||||
private Animator mSlotMachineAnim;
|
||||
private float mSlotMachineIconTranslationY;
|
||||
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
|
||||
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
|
||||
@Override
|
||||
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
|
||||
predictedAppIcon.mSlotMachineIconTranslationY = transY;
|
||||
predictedAppIcon.invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float get(PredictedAppIcon predictedAppIcon) {
|
||||
return predictedAppIcon.mSlotMachineIconTranslationY;
|
||||
}
|
||||
};
|
||||
|
||||
public PredictedAppIcon(Context context) {
|
||||
this(context, null, 0);
|
||||
}
|
||||
@@ -88,15 +128,38 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
int count = canvas.save();
|
||||
boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
|
||||
if (!mIsPinned) {
|
||||
drawEffect(canvas);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
// Clip to to outside of the ring during the slot machine animation.
|
||||
canvas.clipPath(mRingPath);
|
||||
}
|
||||
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
|
||||
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
|
||||
}
|
||||
super.onDraw(canvas);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
drawSlotMachineIcons(canvas);
|
||||
} else {
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
private void drawSlotMachineIcons(Canvas canvas) {
|
||||
canvas.translate((getWidth() - getIconSize()) / 2f,
|
||||
(getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
|
||||
for (Drawable icon : mSlotMachineIcons) {
|
||||
icon.setBounds(0, 0, getIconSize(), getIconSize());
|
||||
icon.draw(canvas);
|
||||
canvas.translate(0, getSlotMachineIconPlusSpacingSize());
|
||||
}
|
||||
}
|
||||
|
||||
private float getSlotMachineIconPlusSpacingSize() {
|
||||
return getIconSize() + getOutlineOffsetY();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawDotIfNecessary(Canvas canvas) {
|
||||
mIsDrawingDot = true;
|
||||
@@ -109,9 +172,17 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
|
||||
super.applyFromWorkspaceItem(info);
|
||||
mPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
|
||||
// Create the slot machine animation first, since it uses the current icon to start.
|
||||
Animator slotMachineAnim = animate
|
||||
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
|
||||
: null;
|
||||
super.applyFromWorkspaceItem(info, animate, staggerIndex);
|
||||
int oldPlateColor = mPlateColor;
|
||||
int newPlateColor = ColorUtils.setAlphaComponent(mDotParams.color, 200);
|
||||
if (!animate) {
|
||||
mPlateColor = newPlateColor;
|
||||
}
|
||||
if (mIsPinned) {
|
||||
setContentDescription(info.contentDescription);
|
||||
} else {
|
||||
@@ -119,6 +190,76 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
getContext().getString(R.string.hotseat_prediction_content_description,
|
||||
info.contentDescription));
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
|
||||
oldPlateColor, newPlateColor);
|
||||
plateColorAnim.addUpdateListener(valueAnimator -> {
|
||||
mPlateColor = (int) valueAnimator.getAnimatedValue();
|
||||
invalidate();
|
||||
});
|
||||
AnimatorSet changeIconAnim = new AnimatorSet();
|
||||
if (slotMachineAnim != null) {
|
||||
changeIconAnim.play(slotMachineAnim);
|
||||
}
|
||||
changeIconAnim.play(plateColorAnim);
|
||||
changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
|
||||
changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
|
||||
* and ending with the original icon.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
|
||||
return createSlotMachineAnim(iconsToAnimate, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
|
||||
* with the original icon, then cycling through the given icons, optionally ending back with
|
||||
* the original icon.
|
||||
* @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
|
||||
* than the last item in iconsToAnimate.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
|
||||
boolean endWithOriginalIcon) {
|
||||
if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (mSlotMachineAnim != null) {
|
||||
mSlotMachineAnim.end();
|
||||
}
|
||||
|
||||
// Bookend the other animating icons with the original icon on both ends.
|
||||
mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
iconsToAnimate.stream()
|
||||
.map(iconInfo -> iconInfo.newThemedIcon(mContext))
|
||||
.forEach(mSlotMachineIcons::add);
|
||||
if (endWithOriginalIcon) {
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
}
|
||||
|
||||
float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
|
||||
Keyframe[] keyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0f, 0f),
|
||||
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
|
||||
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
|
||||
};
|
||||
keyframes[1].setInterpolator(ACCEL_DEACCEL);
|
||||
keyframes[2].setInterpolator(ACCEL_DEACCEL);
|
||||
|
||||
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
|
||||
mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
mSlotMachineIcons = null;
|
||||
mSlotMachineAnim = null;
|
||||
mSlotMachineIconTranslationY = 0;
|
||||
invalidate();
|
||||
}));
|
||||
return mSlotMachineAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -256,9 +256,27 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
|
||||
|
||||
@UiThread
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
|
||||
applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
|
||||
applyFromWorkspaceItem(info, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the newInfo differs from the current getTag().
|
||||
*/
|
||||
public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
|
||||
WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
|
||||
? (WorkspaceItemInfo) getTag()
|
||||
: null;
|
||||
boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
|
||||
&& newInfo.getTargetComponent() != null
|
||||
&& !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
|
||||
return changedIcons && isShown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
|
||||
if (delegate instanceof LauncherAccessibilityDelegate) {
|
||||
|
||||
Reference in New Issue
Block a user