Merge "Wire up flyout to new bubble animation" into main
This commit is contained in:
committed by
Android (Google) Code Review
commit
2c908e2c10
@@ -377,8 +377,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
// Updates mean the dot state may have changed; any other changes were updated in
|
||||
// the populateBubble step.
|
||||
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
|
||||
// If we're not stashed, we're visible so animate
|
||||
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
|
||||
mBubbleBarViewController.animateBubbleNotification(
|
||||
bb, /* isExpanding= */ false, /* isUpdate= */ true);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,8 @@ public class BubbleBarViewController {
|
||||
mBubbleBarFlyoutController = new BubbleBarFlyoutController(
|
||||
mBubbleBarContainer, createFlyoutPositioner(), createFlyoutTopBoundaryListener());
|
||||
mBubbleBarViewAnimator = new BubbleBarViewAnimator(
|
||||
mBarView, mBubbleStashController, mBubbleBarController::showExpandedView);
|
||||
mBarView, mBubbleStashController, mBubbleBarFlyoutController,
|
||||
mBubbleBarController::showExpandedView);
|
||||
mTaskbarViewPropertiesProvider = taskbarViewPropertiesProvider;
|
||||
onBubbleBarConfigurationChanged(/* animate= */ false);
|
||||
mActivity.addOnDeviceProfileChangeListener(
|
||||
@@ -781,6 +782,11 @@ public class BubbleBarViewController {
|
||||
/** Animates the bubble bar to notify the user about a bubble change. */
|
||||
public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding,
|
||||
boolean isUpdate) {
|
||||
// if we're expanded, don't animate the bubble bar. just show the notification dot.
|
||||
if (isExpanded()) {
|
||||
bubble.getView().updateDotVisibility(/* animate= */ true);
|
||||
return;
|
||||
}
|
||||
boolean isInApp = mTaskbarStashController.isInApp();
|
||||
// if this is the first bubble, animate to the initial state.
|
||||
if (mBarView.getBubbleChildCount() == 1 && !isUpdate) {
|
||||
@@ -789,13 +795,12 @@ public class BubbleBarViewController {
|
||||
}
|
||||
boolean persistentTaskbarOrOnHome = mBubbleStashController.isBubblesShowingOnHome()
|
||||
|| !mBubbleStashController.isTransientTaskBar();
|
||||
if (persistentTaskbarOrOnHome && !isExpanded()) {
|
||||
if (persistentTaskbarOrOnHome) {
|
||||
mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble, isExpanding);
|
||||
return;
|
||||
}
|
||||
|
||||
// only animate the new bubble if we're in an app, have handle view and not auto expanding
|
||||
if (isInApp && mBubbleStashController.getHasHandleView() && !isExpanded()) {
|
||||
if (isInApp && mBubbleStashController.getHasHandleView()) {
|
||||
mBubbleBarViewAnimator.animateBubbleInForStashed(bubble, isExpanding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ public class BubbleView extends ConstraintLayout {
|
||||
|
||||
void updateDotVisibility(boolean animate) {
|
||||
if (mDotSuppressedForBubbleUpdate) {
|
||||
// if the dot is suppressed for
|
||||
// if the dot is suppressed for an update, there's nothing to do
|
||||
return;
|
||||
}
|
||||
final float targetScale = hasUnseenContent() ? 1f : 0f;
|
||||
|
||||
+63
-16
@@ -27,6 +27,8 @@ import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleView
|
||||
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController
|
||||
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
|
||||
import com.android.wm.shell.shared.animation.PhysicsAnimator
|
||||
|
||||
@@ -36,8 +38,9 @@ class BubbleBarViewAnimator
|
||||
constructor(
|
||||
private val bubbleBarView: BubbleBarView,
|
||||
private val bubbleStashController: BubbleStashController,
|
||||
private val bubbleBarFlyoutController: BubbleBarFlyoutController,
|
||||
private val onExpanded: Runnable,
|
||||
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
|
||||
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView),
|
||||
) {
|
||||
|
||||
private var animatingBubble: AnimatingBubble? = null
|
||||
@@ -54,7 +57,7 @@ constructor(
|
||||
|
||||
private companion object {
|
||||
/** The time to show the flyout. */
|
||||
const val FLYOUT_DELAY_MS: Long = 2500
|
||||
const val FLYOUT_DELAY_MS: Long = 3000
|
||||
/** The initial scale Y value that the new bubble is set to before the animation starts. */
|
||||
const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
|
||||
/** The minimum alpha value to make the bubble bar touchable. */
|
||||
@@ -69,7 +72,7 @@ constructor(
|
||||
val showAnimation: Runnable,
|
||||
val hideAnimation: Runnable,
|
||||
val expand: Boolean,
|
||||
val state: State = State.CREATED
|
||||
val state: State = State.CREATED,
|
||||
) {
|
||||
|
||||
/**
|
||||
@@ -91,7 +94,7 @@ constructor(
|
||||
/** The bubble notification is now fully showing and waiting to be hidden. */
|
||||
IN,
|
||||
/** The bubble notification is animating out. */
|
||||
ANIMATING_OUT
|
||||
ANIMATING_OUT,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +130,7 @@ constructor(
|
||||
private val springConfig =
|
||||
PhysicsAnimator.SpringConfig(
|
||||
stiffness = SpringForce.STIFFNESS_LOW,
|
||||
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
|
||||
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
|
||||
)
|
||||
|
||||
/** Animates a bubble for the state where the bubble bar is stashed. */
|
||||
@@ -137,8 +140,9 @@ constructor(
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
|
||||
// and the second part hides it after a delay.
|
||||
// the animation of a new bubble is divided into 2 parts. The first part transforms the
|
||||
// handle to the bubble bar and then shows the flyout. The second part hides the flyout and
|
||||
// transforms the bubble bar back to the handle.
|
||||
val showAnimation = buildHandleToBubbleBarAnimation()
|
||||
val hideAnimation = if (isExpanding) Runnable {} else buildBubbleBarToHandleAnimation()
|
||||
animatingBubble =
|
||||
@@ -243,7 +247,8 @@ constructor(
|
||||
cancelHideAnimation()
|
||||
return@addEndListener
|
||||
}
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
setupAndShowFlyout()
|
||||
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
@@ -316,7 +321,17 @@ constructor(
|
||||
bubbleBarView.scaleY = 1f
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
animator.start()
|
||||
|
||||
val bubble = animatingBubble?.bubbleView?.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
bubbleBarFlyoutController.collapseFlyout {
|
||||
onFlyoutRemoved(bubble.view)
|
||||
animator.start()
|
||||
}
|
||||
} else {
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
|
||||
@@ -326,16 +341,16 @@ constructor(
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
|
||||
// and the second part hides it after a delay if we are in an app.
|
||||
// the animation of a new bubble is divided into 2 parts. The first part slides in the
|
||||
// bubble bar and shows the flyout. The second part hides the flyout and transforms the
|
||||
// bubble bar to the handle if we're in an app.
|
||||
val showAnimation = buildBubbleBarSpringInAnimation()
|
||||
val hideAnimation =
|
||||
if (isInApp && !isExpanding) {
|
||||
buildBubbleBarToHandleAnimation()
|
||||
} else {
|
||||
// in this case the bubble bar remains visible so not much to do. once we implement
|
||||
// the flyout we'll update this runnable to hide it.
|
||||
Runnable {
|
||||
bubbleBarFlyoutController.collapseFlyout { onFlyoutRemoved(bubbleView) }
|
||||
animatingBubble = null
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
@@ -370,7 +385,7 @@ constructor(
|
||||
if (animatingBubble?.expand == true) {
|
||||
cancelHideAnimation()
|
||||
} else {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
setupAndShowFlyout()
|
||||
}
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
@@ -384,8 +399,10 @@ constructor(
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// first bounce the bubble bar and show the flyout. Then hide the flyout.
|
||||
val showAnimation = buildBubbleBarBounceAnimation()
|
||||
val hideAnimation = Runnable {
|
||||
bubbleBarFlyoutController.collapseFlyout { onFlyoutRemoved(bubbleView) }
|
||||
animatingBubble = null
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
@@ -413,7 +430,7 @@ constructor(
|
||||
expandBubbleBar()
|
||||
cancelHideAnimation()
|
||||
} else {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
setupAndShowFlyout()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,10 +444,38 @@ constructor(
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun setupAndShowFlyout() {
|
||||
val bubbleView = animatingBubble?.bubbleView
|
||||
val bubble = bubbleView?.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
bubbleView.suppressDotForBubbleUpdate(true)
|
||||
bubbleBarFlyoutController.setUpAndShowFlyout(
|
||||
BubbleBarFlyoutMessage(flyout.icon, flyout.title, flyout.message)
|
||||
) {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
} else {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelFlyout() {
|
||||
val bubbleView = animatingBubble?.bubbleView
|
||||
bubbleBarFlyoutController.cancelFlyout { onFlyoutRemoved(bubbleView) }
|
||||
}
|
||||
|
||||
private fun onFlyoutRemoved(bubbleView: BubbleView?) {
|
||||
bubbleView?.suppressDotForBubbleUpdate(false)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
|
||||
/** Handles touching the animating bubble bar. */
|
||||
fun onBubbleBarTouchedWhileAnimating() {
|
||||
PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
|
||||
bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
|
||||
cancelFlyout()
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
@@ -439,6 +484,7 @@ constructor(
|
||||
|
||||
/** Notifies the animator that the taskbar area was touched during an animation. */
|
||||
fun onStashStateChangingWhileAnimating() {
|
||||
cancelFlyout()
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
animatingBubble = null
|
||||
@@ -446,7 +492,7 @@ constructor(
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleStashController.onNewBubbleAnimationInterrupted(
|
||||
/* isStashed= */ bubbleBarView.alpha == 0f,
|
||||
bubbleBarView.translationY
|
||||
bubbleBarView.translationY,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -455,6 +501,7 @@ constructor(
|
||||
this.animatingBubble = animatingBubble.copy(expand = true)
|
||||
// if we're fully in and waiting to hide, cancel the hide animation and clean up
|
||||
if (animatingBubble.state == AnimatingBubble.State.IN) {
|
||||
cancelFlyout()
|
||||
expandBubbleBar()
|
||||
cancelHideAnimation()
|
||||
}
|
||||
|
||||
+36
-16
@@ -21,8 +21,8 @@ import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.animation.ValueAnimator
|
||||
import com.android.launcher3.R
|
||||
import com.android.systemui.util.addListener
|
||||
import com.android.systemui.util.doOnEnd
|
||||
import com.android.systemui.util.doOnStart
|
||||
|
||||
/** Creates and manages the visibility of the [BubbleBarFlyoutView]. */
|
||||
class BubbleBarFlyoutController
|
||||
@@ -35,14 +35,19 @@ constructor(
|
||||
) {
|
||||
|
||||
private companion object {
|
||||
const val EXPAND_COLLAPSE_ANIMATION_DURATION_MS = 250L
|
||||
const val ANIMATION_DURATION_MS = 250L
|
||||
}
|
||||
|
||||
private var flyout: BubbleBarFlyoutView? = null
|
||||
private val horizontalMargin =
|
||||
container.context.resources.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin)
|
||||
|
||||
fun setUpFlyout(message: BubbleBarFlyoutMessage) {
|
||||
private enum class AnimationType {
|
||||
COLLAPSE,
|
||||
FADE,
|
||||
}
|
||||
|
||||
fun setUpAndShowFlyout(message: BubbleBarFlyoutMessage, onEnd: () -> Unit) {
|
||||
flyout?.let(container::removeView)
|
||||
val flyout = BubbleBarFlyoutView(container.context, positioner, flyoutScheduler)
|
||||
|
||||
@@ -58,27 +63,42 @@ constructor(
|
||||
lp.marginEnd = horizontalMargin
|
||||
container.addView(flyout, lp)
|
||||
|
||||
val animator =
|
||||
ValueAnimator.ofFloat(0f, 1f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS)
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
|
||||
animator.addUpdateListener { _ ->
|
||||
flyout.updateExpansionProgress(animator.animatedValue as Float)
|
||||
}
|
||||
animator.doOnStart {
|
||||
val flyoutTop = flyout.top + flyout.translationY
|
||||
// If the top position of the flyout is negative, then it's bleeding over the
|
||||
// top boundary of its parent view
|
||||
if (flyoutTop < 0) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt())
|
||||
}
|
||||
animator.addListener(
|
||||
onStart = {
|
||||
val flyoutTop = flyout.top + flyout.translationY
|
||||
// If the top position of the flyout is negative, then it's bleeding over the
|
||||
// top boundary of its parent view
|
||||
if (flyoutTop < 0) topBoundaryListener.extendTopBoundary(space = -flyoutTop.toInt())
|
||||
},
|
||||
onEnd = { onEnd() },
|
||||
)
|
||||
flyout.showFromCollapsed(message) { animator.start() }
|
||||
this.flyout = flyout
|
||||
}
|
||||
|
||||
fun hideFlyout(endAction: () -> Unit) {
|
||||
fun cancelFlyout(endAction: () -> Unit) {
|
||||
hideFlyout(AnimationType.FADE, endAction)
|
||||
}
|
||||
|
||||
fun collapseFlyout(endAction: () -> Unit) {
|
||||
hideFlyout(AnimationType.COLLAPSE, endAction)
|
||||
}
|
||||
|
||||
private fun hideFlyout(animationType: AnimationType, endAction: () -> Unit) {
|
||||
// TODO: b/277815200 - stop the current animation if it's running
|
||||
val flyout = this.flyout ?: return
|
||||
val animator =
|
||||
ValueAnimator.ofFloat(1f, 0f).setDuration(EXPAND_COLLAPSE_ANIMATION_DURATION_MS)
|
||||
animator.addUpdateListener { _ ->
|
||||
flyout.updateExpansionProgress(animator.animatedValue as Float)
|
||||
val animator = ValueAnimator.ofFloat(1f, 0f).setDuration(ANIMATION_DURATION_MS)
|
||||
when (animationType) {
|
||||
AnimationType.FADE ->
|
||||
animator.addUpdateListener { _ -> flyout.alpha = animator.animatedValue as Float }
|
||||
AnimationType.COLLAPSE ->
|
||||
animator.addUpdateListener { _ ->
|
||||
flyout.updateExpansionProgress(animator.animatedValue as Float)
|
||||
}
|
||||
}
|
||||
animator.doOnEnd {
|
||||
container.removeView(flyout)
|
||||
|
||||
@@ -140,6 +140,7 @@ class BubbleBarFlyoutView(
|
||||
|
||||
init {
|
||||
LayoutInflater.from(context).inflate(R.layout.bubblebar_flyout, this, true)
|
||||
id = R.id.bubble_bar_flyout_view
|
||||
|
||||
val ta = context.obtainStyledAttributes(intArrayOf(android.R.attr.dialogCornerRadius))
|
||||
cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
|
||||
|
||||
Reference in New Issue
Block a user