Implement spring animations for running state changes.

Flag: com.android.window.flags.enable_taskbar_recents_layout_transition
Fix: 402683858
Test: go/testedequals
Change-Id: Icc4a3a61b6c0997564a4dcdd8eb7768459d5f2ef
This commit is contained in:
Brian Isganitis
2025-03-11 13:30:37 -04:00
parent ddc72547d4
commit c0a8f7ea99
8 changed files with 517 additions and 126 deletions
+3
View File
@@ -372,6 +372,9 @@
<dimen name="taskbar_running_app_indicator_height">2dp</dimen>
<dimen name="taskbar_running_app_indicator_width">12dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">4dp</dimen>
<dimen name="taskbar_minimized_app_indicator_width">6dp</dimen>
<dimen name="taskbar_line_indicator_width_spring">20dp</dimen>
<dimen name="taskbar_app_translate_y_spring">4dp</dimen>
<!-- Transient taskbar -->
<dimen name="transient_taskbar_padding">12dp</dimen>
@@ -0,0 +1,276 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar
import android.animation.AnimatorSet
import android.animation.ArgbEvaluator
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Color.TRANSPARENT
import android.util.FloatProperty
import android.util.IntProperty
import androidx.annotation.ColorInt
import androidx.annotation.Px
import androidx.annotation.UiThread
import androidx.core.animation.doOnEnd
import com.android.app.animation.Interpolators.EMPHASIZED
import com.android.app.animation.Interpolators.LINEAR
import com.android.internal.dynamicanimation.animation.FloatValueHolder
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.internal.dynamicanimation.animation.SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
import com.android.internal.dynamicanimation.animation.SpringForce.DAMPING_RATIO_NO_BOUNCY
import com.android.launcher3.BubbleTextView
import com.android.launcher3.BubbleTextView.RunningAppState
import com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED
import com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING
import com.android.launcher3.BubbleTextView.RunningAppState.RUNNING
import com.android.launcher3.R
import com.android.launcher3.Utilities as LauncherUtilities
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.taskbar.TaskbarViewController.TRANSITION_DEFAULT_DURATION
import com.android.launcher3.util.MultiPropertyFactory
import com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM
private const val SPRING_START = 0f
private const val SPRING_END = 100f
private val SPRING_NO_BOUNCY =
SpringForce(SPRING_END).apply {
dampingRatio = DAMPING_RATIO_NO_BOUNCY
stiffness = 3800f
}
private val SPRING_MEDIUM_BOUNCY =
SpringForce(SPRING_END).apply {
dampingRatio = DAMPING_RATIO_MEDIUM_BOUNCY
stiffness = 400f
}
private val TRANSLATE_Y_PROPERTY =
object : FloatProperty<BubbleTextView>("runningStateTranslateY") {
private val BubbleTextView.runningStateTranslateYProp: MultiPropertyFactory<*>.MultiProperty
get() = translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM)
override fun get(btv: BubbleTextView): Float = btv.runningStateTranslateYProp.value
override fun setValue(btv: BubbleTextView, translateY: Float) {
btv.runningStateTranslateYProp.value = translateY
btv.invalidate()
}
}
private val LINE_COLOR_PROPERTY =
object : IntProperty<BubbleTextView>("lineIndicatorColor") {
override fun get(btv: BubbleTextView): Int = btv.lineIndicatorColor
override fun setValue(btv: BubbleTextView, color: Int) {
btv.lineIndicatorColor = color
}
}
private val LINE_WIDTH_PROPERTY =
object : FloatProperty<BubbleTextView>("lineIndicatorWidth") {
override fun get(btv: BubbleTextView): Float = btv.lineIndicatorWidth
override fun setValue(btv: BubbleTextView, width: Float) {
btv.lineIndicatorWidth = width
}
}
/** Manages Taskbar [BubbleTextView] animations from changes in [RunningAppState]. */
class TaskbarRunningAppStateAnimationController(context: Context) {
/** Animating [BubbleTextView] instances, where invoking each value cancels the animation. */
private val runningAnimations = mutableMapOf<BubbleTextView, () -> Unit>()
private val argbEvaluator = ArgbEvaluator()
private val runningLineColor =
context.resources.getColor(R.color.taskbar_running_app_indicator_color, context.theme)
private val minimizedLineColor =
context.resources.getColor(R.color.taskbar_minimized_app_indicator_color, context.theme)
private val runningLineWidth =
context.resources
.getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width)
.toFloat()
private val minimizedLineWidth =
context.resources
.getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width)
.toFloat()
private val appTranslateYSpring =
context.resources.getDimensionPixelSize(R.dimen.taskbar_app_translate_y_spring).toFloat()
private val lineWidthSpring =
context.resources
.getDimensionPixelSize(R.dimen.taskbar_line_indicator_width_spring)
.toFloat()
// Copies the keys to avoid concurrent modification due to value callbacks modifying the map.
fun onDestroy() = runningAnimations.keys.toList().forEach { cancelAnimation(it) }
@UiThread
fun updateRunningState(btv: BubbleTextView, runningState: RunningAppState, animate: Boolean) {
val prevRunningState = btv.runningAppState ?: NOT_RUNNING
if (runningState == prevRunningState) return
cancelAnimation(btv)
btv.runningAppState = runningState
if (!animate) return applyRunningState(btv)
val isPinnedApp = btv.tag is TaskItemInfo
if (
(prevRunningState == RUNNING && runningState == MINIMIZED) ||
(prevRunningState == MINIMIZED && runningState == RUNNING) ||
(isPinnedApp && runningState == RUNNING)
) {
return startAppBounceAnimation(btv)
}
// Otherwise just animate line width and color.
AnimatorSet().run {
if (runningState == RUNNING) {
// New unpinned app - delay animation until icon is mostly scaled in.
startDelay = UNPINNED_APP_LINE_ANIM_DELAY
}
duration = LINE_ANIM_DURATION
interpolator = EMPHASIZED
playTogether(
ObjectAnimator.ofFloat(btv, LINE_WIDTH_PROPERTY, runningState.lineWidth),
ObjectAnimator.ofArgb(btv, LINE_COLOR_PROPERTY, runningState.lineColor),
)
doOnEnd {
runningAnimations.remove(btv)
applyRunningState(btv)
}
runningAnimations[btv] = this::cancel
start()
}
}
fun isAnimationRunning(btv: BubbleTextView): Boolean = runningAnimations.containsKey(btv)
private fun cancelAnimation(btv: BubbleTextView) = runningAnimations[btv]?.invoke()
private fun startAppBounceAnimation(btv: BubbleTextView) {
val isMinimized = btv.runningAppState == MINIMIZED
val translateYSpring = if (isMinimized) appTranslateYSpring else -appTranslateYSpring
val prevLineWidth = btv.lineIndicatorWidth
val prevLineColor = btv.lineIndicatorColor
val translateYProp =
btv.translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM)
fun updateTranslateY(value: Float) {
translateYProp.value = value
btv.invalidate()
}
SpringAnimation(FloatValueHolder()).run {
spring = SPRING_NO_BOUNCY
addUpdateListener { _, v, _ ->
updateTranslateY(mapValue(v, 0f, translateYSpring))
if (isMinimized) {
btv.lineIndicatorWidth = mapValue(v, prevLineWidth, lineWidthSpring)
}
}
addEndListener { _, canceled, _, _ ->
runningAnimations.remove(btv)
if (canceled) return@addEndListener applyRunningState(btv)
val startLineWidth = if (isMinimized) lineWidthSpring else prevLineWidth
val endLineWidth = btv.runningAppState.lineWidth
val endLineColor = btv.runningAppState.lineColor
val springs =
listOf(
SpringAnimation(FloatValueHolder()).apply {
spring = SPRING_MEDIUM_BOUNCY
addUpdateListener { _, v, _ ->
updateTranslateY(mapValue(v, translateYSpring, 0f))
btv.lineIndicatorWidth = mapValue(v, startLineWidth, endLineWidth)
}
},
SpringAnimation(FloatValueHolder()).apply {
spring = SPRING_NO_BOUNCY
addUpdateListener { _, v, _ ->
btv.lineIndicatorColor =
argbEvaluator.evaluate(
v / SPRING_END,
prevLineColor,
endLineColor,
) as Int
}
},
)
runningAnimations[btv] = { for (s in springs) s.cancel() }
var runningSprings = springs.size
for (s in springs) {
s.addEndListener { _, canceled2, _, _ ->
if (--runningSprings == 0) {
runningAnimations.remove(btv)
if (canceled2) applyRunningState(btv)
}
}
s.start()
}
}
runningAnimations[btv] = this::cancel
start()
}
}
private fun applyRunningState(btv: BubbleTextView) {
btv.lineIndicatorWidth = btv.runningAppState.lineWidth
btv.lineIndicatorColor = btv.runningAppState.lineColor
TRANSLATE_Y_PROPERTY[btv] = 0f
}
private fun mapValue(value: Float, min: Float, max: Float): Float {
return LauncherUtilities.mapToRange(value, SPRING_START, SPRING_END, min, max, LINEAR)
}
@get:ColorInt
val RunningAppState.lineColor: Int
get() {
return when (this) {
NOT_RUNNING -> TRANSPARENT
RUNNING -> runningLineColor
MINIMIZED -> minimizedLineColor
}
}
@get:Px
val RunningAppState.lineWidth: Float
get() {
return when (this) {
NOT_RUNNING -> 0f
RUNNING -> runningLineWidth
MINIMIZED -> minimizedLineWidth
}
}
companion object {
const val LINE_ANIM_DURATION = 100L
const val UNPINNED_APP_LINE_ANIM_DELAY = TRANSITION_DEFAULT_DURATION - LINE_ANIM_DURATION
}
}
@@ -25,7 +25,6 @@ import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRAN
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.app.animation.Interpolators.FINAL_FRAME;
import static com.android.app.animation.Interpolators.LINEAR;
import static com.android.launcher3.BubbleTextView.LINE_INDICATOR_ANIM_DURATION;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.Flags.taskbarOverflow;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -136,11 +135,9 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100;
private static final int TRANSITION_DELAY = 50;
private static final int TRANSITION_DEFAULT_DURATION = 500;
static final int TRANSITION_DEFAULT_DURATION = 500;
private static final int TRANSITION_FADE_IN_DURATION = 167;
private static final int TRANSITION_FADE_OUT_DURATION = 83;
private static final int APPEARING_LINE_INDICATOR_ANIM_DELAY =
TRANSITION_DEFAULT_DURATION - LINE_INDICATOR_ANIM_DURATION;
private final TaskbarActivityContext mActivity;
private @Nullable TaskbarDragLayerController mDragLayerController;
@@ -224,6 +221,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
private final float mTaskbarLeftRightMargin;
private final TaskbarRunningAppStateAnimationController mRunningStateController;
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
@@ -242,6 +241,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
R.dimen.transient_taskbar_padding);
mRunningStateController = new TaskbarRunningAppStateAnimationController(mActivity);
}
/**
@@ -395,6 +395,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
mRunningStateController.onDestroy();
}
/**
@@ -773,7 +774,10 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
}
private void updateRunningState(BubbleTextView btv) {
btv.updateRunningState(getRunningAppState(btv), mTaskbarView.getLayoutTransition() != null);
mRunningStateController.updateRunningState(
btv,
getRunningAppState(btv),
/* animate = */ mTaskbarView.getLayoutTransition() != null);
}
private BubbleTextView.RunningAppState getRunningAppState(BubbleTextView btv) {
@@ -1233,10 +1237,6 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
if (view instanceof BubbleTextView btv) {
// Defer so that app is mostly scaled in before showing indicator.
btv.setLineIndicatorAnimStartDelay(APPEARING_LINE_INDICATOR_ANIM_DELAY);
}
} else if (type == DISAPPEARING && view instanceof BubbleTextView btv) {
// Running state updates happen after removing this view, so update it here.
updateRunningState(btv);
@@ -1246,9 +1246,7 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar
@Override
public void endTransition(
LayoutTransition transition, ViewGroup container, View view, int type) {
if (type == APPEARING && view instanceof BubbleTextView btv) {
btv.setLineIndicatorAnimStartDelay(0);
}
// Do nothing.
}
});
@@ -0,0 +1,175 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.taskbar
import android.animation.AnimatorTestRule
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.launcher3.BubbleTextView
import com.android.launcher3.BubbleTextView.RunningAppState
import com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED
import com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING
import com.android.launcher3.BubbleTextView.RunningAppState.RUNNING
import com.android.launcher3.model.data.TaskItemInfo
import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
import com.android.launcher3.taskbar.TaskbarRunningAppStateAnimationController.Companion.LINE_ANIM_DURATION
import com.android.launcher3.taskbar.TaskbarRunningAppStateAnimationController.Companion.UNPINNED_APP_LINE_ANIM_DELAY
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val FRAME_TIME_MS = 16L // Simulates 60 Hz.
private val PINNED_APP = TaskItemInfo(0, TaskbarViewTestUtil.createHotseatWorkspaceItem(0))
private val UNPINNED_APP = TaskbarViewTestUtil.createRecentTask(1)
@RunWith(LauncherMultivalentJUnit::class)
class TaskbarRunningAppStateAnimationControllerTest {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
private val context = ActivityContextWrapper(getInstrumentation().targetContext)
private val btv = BubbleTextView(context)
private val controller = TaskbarRunningAppStateAnimationController(context)
@Test
fun updateRunningState_minimizeApp_verifySpringEndState() {
startStateChange(start = RUNNING, end = MINIMIZED)
verifySpringAnimationEnd(MINIMIZED)
}
@Test
fun updateRunningState_minimizeApp_verifyCancelEndState() {
startStateChange(start = RUNNING, end = MINIMIZED)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = MINIMIZED)
}
@Test
fun updateRunningState_restoreApp_verifySpringEndState() {
startStateChange(start = MINIMIZED, end = RUNNING)
verifySpringAnimationEnd(RUNNING)
}
@Test
fun updateRunningState_openPinnedApp_verifySpringEndState() {
btv.tag = PINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
verifySpringAnimationEnd(RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyStartDelay() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { animatorTestRule.advanceTimeBy(UNPINNED_APP_LINE_ANIM_DELAY) }
verifyLineIndicator(state = NOT_RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyEndState() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync {
animatorTestRule.advanceTimeBy(UNPINNED_APP_LINE_ANIM_DELAY + LINE_ANIM_DURATION)
}
verifyStateSettled(state = RUNNING)
}
@Test
fun updateRunningState_openUnpinnedApp_verifyCancelEndState() {
btv.tag = UNPINNED_APP
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = RUNNING)
}
@Test
fun updateRunningState_closeApp_verifyEndState() {
startStateChange(start = RUNNING, end = NOT_RUNNING)
runOnMainSync { animatorTestRule.advanceTimeBy(LINE_ANIM_DURATION) }
verifyStateSettled(state = NOT_RUNNING)
}
@Test
fun updateRunningState_repeatUpdateDuringAnimation_animationNotCanceled() {
startStateChange(start = MINIMIZED, end = RUNNING)
runOnMainSync {
animatorTestRule.advanceTimeBy(FRAME_TIME_MS)
controller.updateRunningState(btv, RUNNING, animate = false)
}
assertThat(controller.isAnimationRunning(btv)).isTrue()
}
@Test
fun updateRunningState_minimizedDuringOpen_verifyMinimizedEndState() {
startStateChange(start = NOT_RUNNING, end = RUNNING)
runOnMainSync { controller.updateRunningState(btv, MINIMIZED, animate = true) }
verifySpringAnimationEnd(MINIMIZED)
}
@Test
fun onDestroy_multipleAnimations_cancelsAll() {
startStateChange(start = RUNNING, end = MINIMIZED)
val btv2 = BubbleTextView(context)
startStateChange(btv = btv2, start = RUNNING, end = MINIMIZED)
runOnMainSync { controller.onDestroy() }
verifyStateSettled(state = MINIMIZED)
verifyStateSettled(btv = btv2, state = MINIMIZED)
}
private fun startStateChange(
btv: BubbleTextView = this.btv,
start: RunningAppState,
end: RunningAppState,
) {
runOnMainSync {
controller.updateRunningState(btv, start, animate = false)
controller.updateRunningState(btv, end, animate = true)
}
verifyLineIndicator(btv, start)
assertThat(controller.isAnimationRunning(btv)).isTrue()
}
/** Verifies [btv] spring animation ends at [state]. */
private fun verifySpringAnimationEnd(state: RunningAppState) {
while (controller.isAnimationRunning(btv)) {
runOnMainSync { animatorTestRule.advanceTimeBy(FRAME_TIME_MS) }
}
verifyStateSettled(state = state)
}
private fun verifyStateSettled(btv: BubbleTextView = this.btv, state: RunningAppState) {
assertThat(controller.isAnimationRunning(btv)).isFalse()
verifyLineIndicator(btv, state)
val translateYProp =
btv.translateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM)
assertThat(translateYProp.value).isZero()
}
private fun verifyLineIndicator(btv: BubbleTextView = this.btv, state: RunningAppState) {
controller.run {
assertThat(btv.lineIndicatorWidth).isEqualTo(state.lineWidth)
assertThat(btv.lineIndicatorColor).isEqualTo(state.lineColor)
}
}
}
@@ -83,21 +83,23 @@ object TaskbarViewTestUtil {
/** Creates a list of fake recent tasks. */
fun createRecents(size: Int): List<GroupTask> {
return List(size) {
SingleTask(
Task().apply {
key =
TaskKey(
it,
5,
TEST_INTENT,
TEST_COMPONENT,
Process.myUserHandle().identifier,
System.currentTimeMillis(),
)
}
)
}
return List(size) { createRecentTask(it) }
}
fun createRecentTask(id: Int = 0): GroupTask {
return SingleTask(
Task().apply {
key =
TaskKey(
id,
5,
TEST_INTENT,
TEST_COMPONENT,
Process.myUserHandle().identifier,
System.currentTimeMillis(),
)
}
)
}
}
+3
View File
@@ -445,6 +445,9 @@
<dimen name="taskbar_running_app_indicator_height">0dp</dimen>
<dimen name="taskbar_running_app_indicator_width">0dp</dimen>
<dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
<dimen name="taskbar_minimized_app_indicator_width">0dp</dimen>
<dimen name="taskbar_line_indicator_width_spring">0dp</dimen>
<dimen name="taskbar_app_translate_y_spring">0dp</dimen>
<!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="transient_taskbar_padding">0dp</dimen>
+31 -98
View File
@@ -20,9 +20,7 @@ import static android.graphics.fonts.FontStyle.FONT_WEIGHT_BOLD;
import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
import static android.text.Layout.Alignment.ALIGN_NORMAL;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING;
import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED;
import static com.android.launcher3.Flags.enableContrastTiles;
import static com.android.launcher3.Flags.enableCursorHoverStates;
@@ -36,10 +34,10 @@ import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTO
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_APP_RUNNING_STATE_ANIM;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -134,9 +132,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
StringMatcherUtility.StringMatcher.getInstance();
private static final int BOLD_TEXT_ADJUSTMENT = FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL;
public static final int LINE_INDICATOR_ANIM_DURATION = 150;
private static final float MINIMIZED_APP_INDICATOR_SCALE = 0.5f;
private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
private float mScaleForReorderBounce = 1f;
@@ -172,36 +167,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
}
};
private static final Property<BubbleTextView, Integer> LINE_INDICATOR_COLOR_PROPERTY =
new Property<>(Integer.class, "lineIndicatorColor") {
@Override
public Integer get(BubbleTextView bubbleTextView) {
return bubbleTextView.mLineIndicatorColor;
}
@Override
public void set(BubbleTextView bubbleTextView, Integer color) {
bubbleTextView.mLineIndicatorColor = color;
bubbleTextView.invalidate();
}
};
private static final Property<BubbleTextView, Float> LINE_INDICATOR_SCALE_PROPERTY =
new Property<>(Float.TYPE, "lineIndicatorScale") {
@Override
public Float get(BubbleTextView bubbleTextView) {
return bubbleTextView.mLineIndicatorScale;
}
@Override
public void set(BubbleTextView bubbleTextView, Float scale) {
bubbleTextView.mLineIndicatorScale = scale;
bubbleTextView.invalidate();
}
};
private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
protected final ActivityContext mActivity;
private FastBitmapDrawable mIcon;
@@ -238,20 +203,16 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
private boolean mForceHideDot;
// These fields, related to showing running apps, are only used for Taskbar.
private final int mRunningAppIndicatorWidth;
private final int mRunningAppIndicatorHeight;
private final int mRunningAppIndicatorTopMargin;
private final Paint mRunningAppIndicatorPaint;
private final Rect mRunningAppIconBounds = new Rect();
private RunningAppState mRunningAppState;
private final int mRunningAppIndicatorColor;
private final int mMinimizedAppIndicatorColor;
@ViewDebug.ExportedProperty(category = "launcher")
private int mLineIndicatorColor;
@ViewDebug.ExportedProperty(category = "launcher")
private float mLineIndicatorScale;
private int mLineIndicatorAnimStartDelay;
private Animator mLineIndicatorAnim;
private float mLineIndicatorWidth;
private final String mMinimizedStateDescription;
private final String mRunningStateDescription;
@@ -334,19 +295,12 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
defaultIconSize);
a.recycle();
mRunningAppIndicatorWidth =
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width);
mRunningAppIndicatorHeight =
getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height);
mRunningAppIndicatorTopMargin =
getResources().getDimensionPixelSize(
R.dimen.taskbar_running_app_indicator_top_margin);
mRunningAppIndicatorPaint = new Paint();
mRunningAppIndicatorColor = getResources().getColor(
R.color.taskbar_running_app_indicator_color, context.getTheme());
mMinimizedAppIndicatorColor = getResources().getColor(
R.color.taskbar_minimized_app_indicator_color, context.getTheme());
mLongPressHelper = new CheckLongPressHelper(this);
@@ -385,9 +339,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
setBackground(null);
mLineIndicatorColor = Color.TRANSPARENT;
mLineIndicatorScale = 0;
mLineIndicatorAnimStartDelay = 0;
cancelLineIndicatorAnim();
mLineIndicatorWidth = 0;
setTag(null);
if (mIconLoadRequest != null) {
@@ -479,52 +431,30 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
setContentDescription(label);
}
/** Updates whether the app this view represents is currently running. */
@UiThread
public void updateRunningState(RunningAppState runningAppState, boolean animate) {
if (runningAppState.equals(mRunningAppState)) {
return;
}
public void setRunningAppState(RunningAppState runningAppState) {
mRunningAppState = runningAppState;
cancelLineIndicatorAnim();
int color = switch (mRunningAppState) {
case NOT_RUNNING -> Color.TRANSPARENT;
case RUNNING -> mRunningAppIndicatorColor;
case MINIMIZED -> mMinimizedAppIndicatorColor;
};
float scale = switch (mRunningAppState) {
case NOT_RUNNING -> 0;
case RUNNING -> 1;
case MINIMIZED -> MINIMIZED_APP_INDICATOR_SCALE;
};
if (!animate) {
mLineIndicatorColor = color;
mLineIndicatorScale = scale;
invalidate();
return;
}
AnimatorSet lineIndicatorAnim = new AnimatorSet();
mLineIndicatorAnim = lineIndicatorAnim;
Animator colorAnimator = ObjectAnimator.ofArgb(this, LINE_INDICATOR_COLOR_PROPERTY, color);
Animator scaleAnimator = ObjectAnimator.ofFloat(this, LINE_INDICATOR_SCALE_PROPERTY, scale);
lineIndicatorAnim.playTogether(colorAnimator, scaleAnimator);
lineIndicatorAnim.setInterpolator(EMPHASIZED);
lineIndicatorAnim.setStartDelay(mLineIndicatorAnimStartDelay);
lineIndicatorAnim.setDuration(LINE_INDICATOR_ANIM_DURATION).start();
}
public void setLineIndicatorAnimStartDelay(int lineIndicatorAnimStartDelay) {
mLineIndicatorAnimStartDelay = lineIndicatorAnimStartDelay;
public RunningAppState getRunningAppState() {
return mRunningAppState;
}
private void cancelLineIndicatorAnim() {
if (mLineIndicatorAnim != null) {
mLineIndicatorAnim.cancel();
}
public int getLineIndicatorColor() {
return mLineIndicatorColor;
}
public void setLineIndicatorColor(int lineIndicatorColor) {
mLineIndicatorColor = lineIndicatorColor;
invalidate();
}
public float getLineIndicatorWidth() {
return mLineIndicatorWidth;
}
public void setLineIndicatorWidth(float lineIndicatorWidth) {
mLineIndicatorWidth = lineIndicatorWidth;
invalidate();
}
/**
@@ -879,22 +809,25 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver,
/** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
if (mDisplay != DISPLAY_TASKBAR
|| mLineIndicatorScale == 0
|| Float.compare(mLineIndicatorWidth, 0) == 0
|| mLineIndicatorColor == Color.TRANSPARENT) {
return;
}
getIconBounds(mRunningAppIconBounds);
Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
final float indicatorWidth = mRunningAppIndicatorWidth * mLineIndicatorScale;
float taskbarAppRunningStateAnimOffset =
mTranslateDelegate.getTranslationY(INDEX_TASKBAR_APP_RUNNING_STATE_ANIM).getValue();
final float indicatorTop = mRunningAppIconBounds.bottom
+ mRunningAppIndicatorTopMargin
- taskbarAppRunningStateAnimOffset;
final float cornerRadius = mRunningAppIndicatorHeight / 2f;
mRunningAppIndicatorPaint.setColor(mLineIndicatorColor);
canvas.drawRoundRect(
mRunningAppIconBounds.centerX() - indicatorWidth / 2f,
mRunningAppIconBounds.centerX() - mLineIndicatorWidth / 2f,
indicatorTop,
mRunningAppIconBounds.centerX() + indicatorWidth / 2f,
mRunningAppIconBounds.centerX() + mLineIndicatorWidth / 2f,
indicatorTop + mRunningAppIndicatorHeight,
cornerRadius,
cornerRadius,
@@ -39,6 +39,7 @@ public class MultiTranslateDelegate {
public static final int INDEX_TASKBAR_PINNING_ANIM = 5;
public static final int INDEX_NAV_BAR_ANIM = 6;
public static final int INDEX_BUBBLE_BAR_ANIM = 7;
public static final int INDEX_TASKBAR_APP_RUNNING_STATE_ANIM = 8;
// Affect all items inside of a MultipageCellLayout
public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3;
@@ -49,7 +50,7 @@ public class MultiTranslateDelegate {
// Specific for hotseat items when adjusting for bubbles
public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
public static final int COUNT = 8;
public static final int COUNT = 9;
private final MultiPropertyFactory<View> mTranslationX;
private final MultiPropertyFactory<View> mTranslationY;