Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 175d152f authored by Tony Wickham's avatar Tony Wickham
Browse files

Animate PredictedAppIcon when its icon changes

Reuses the slot machine animation to slide in the new icon. Additionally, staggers based on other icons changing before it.

Test: open apps, watch predictions change
Bug: 197780290
Change-Id: Ib2bc84193a9e350c915dd3486b6c98c6c88d3f83
parent 31f787d3
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -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);
+9 −2
Original line number Diff line number Diff line
@@ -115,6 +115,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];
@@ -170,8 +171,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++;
+53 −4
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@ 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;
@@ -57,6 +60,7 @@ import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.DoubleShadowBubbleTextView;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
@@ -67,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);
@@ -165,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 {
@@ -175,6 +190,22 @@ 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();
        }
    }

    /**
@@ -182,16 +213,34 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
     * 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[] {
+18 −0
Original line number Diff line number Diff line
@@ -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) {