Add "wave" animation when entering taskbar edu

Each icon scales and translates up, then back down. If the icon is predicted, it also plays a "slot machine" animation through random icons from AllAppsList.

Test: Visual
Bug: 180605356
Change-Id: Ia41cb0e340347eea6b580d23c8a2386837e9c399
This commit is contained in:
Tony Wickham
2021-08-24 10:56:31 -07:00
parent 8e0484bf69
commit 31f787d352
8 changed files with 181 additions and 4 deletions

View File

@@ -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.