Implement bubble bar flyout background animation
Update the bubble bar flyout drawing behavior to allow to animate from a collapsed position into an expanded position. This change only handles the rounded rect and the triangle. The text is still left unchanged. When wired up this looks like this: left: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/gIsckRmFKj8CceafiJnPTa right: http://recall/-/bJtug1HhvXkkeA4MQvIaiP/dpn51yXFCCkT6ViUegf351 Flag: com.android.wm.shell.enable_bubble_bar Bug: 277815200 Test: atest BubbleBarFlyoutViewScreenshotTest Test: atest BubbleBarFlyoutControllerTest Change-Id: I85ae3bf908c04e5473655c9e536495f56d80f466
This commit is contained in:
+7
-2
@@ -19,6 +19,7 @@ package com.android.launcher3.taskbar.bubbles.flyout
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.animation.ValueAnimator
|
||||
import com.android.launcher3.R
|
||||
|
||||
/** Creates and manages the visibility of the [BubbleBarFlyoutView]. */
|
||||
@@ -33,7 +34,7 @@ class BubbleBarFlyoutController(
|
||||
|
||||
fun setUpFlyout(message: BubbleBarFlyoutMessage) {
|
||||
flyout?.let(container::removeView)
|
||||
val flyout = BubbleBarFlyoutView(container.context, onLeft = positioner.isOnLeft)
|
||||
val flyout = BubbleBarFlyoutView(container.context, positioner)
|
||||
|
||||
flyout.translationY = positioner.targetTy
|
||||
|
||||
@@ -47,7 +48,11 @@ class BubbleBarFlyoutController(
|
||||
lp.marginEnd = horizontalMargin
|
||||
container.addView(flyout, lp)
|
||||
|
||||
flyout.setData(message)
|
||||
val animator = ValueAnimator.ofFloat(0f, 1f)
|
||||
animator.addUpdateListener { _ ->
|
||||
flyout.updateExpansionProgress(animator.animatedValue as Float)
|
||||
}
|
||||
flyout.showFromCollapsed(message) { animator.start() }
|
||||
this.flyout = flyout
|
||||
}
|
||||
|
||||
|
||||
+12
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles.flyout
|
||||
|
||||
import android.graphics.PointF
|
||||
|
||||
/** Provides positioning data to the flyout view. */
|
||||
interface BubbleBarFlyoutPositioner {
|
||||
|
||||
@@ -24,4 +26,14 @@ interface BubbleBarFlyoutPositioner {
|
||||
|
||||
/** The target translation Y that the flyout view should have when displayed. */
|
||||
val targetTy: Float
|
||||
|
||||
/**
|
||||
* The distance between the expanded position of the flyout and the collapsed position.
|
||||
*
|
||||
* The distance is calculated between the bottom corner which is aligned with the bubble bar.
|
||||
*/
|
||||
val distanceToCollapsedPosition: PointF
|
||||
|
||||
/** The size of the flyout when collapsed. */
|
||||
val collapsedSize: Float
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Path
|
||||
import android.graphics.PointF
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
@@ -30,9 +31,14 @@ import com.android.launcher3.R
|
||||
import com.android.launcher3.popup.RoundedArrowDrawable
|
||||
|
||||
/** The flyout view used to notify the user of a new bubble notification. */
|
||||
class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
|
||||
class BubbleBarFlyoutView(context: Context, private val positioner: BubbleBarFlyoutPositioner) :
|
||||
ConstraintLayout(context) {
|
||||
|
||||
private companion object {
|
||||
// the minimum progress of the expansion animation before the triangle is made visible.
|
||||
const val MIN_EXPANSION_PROGRESS_FOR_TRIANGLE = 0.1f
|
||||
}
|
||||
|
||||
private val sender: TextView by
|
||||
lazy(LazyThreadSafetyMode.NONE) { findViewById(R.id.bubble_flyout_name) }
|
||||
|
||||
@@ -82,6 +88,14 @@ class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
|
||||
private val cornerRadius: Float
|
||||
private val triangle: Path = Path()
|
||||
private var backgroundColor = Color.BLACK
|
||||
/** Represents the progress of the expansion animation. 0 when collapsed. 1 when expanded. */
|
||||
private var expansionProgress = 0f
|
||||
/** Translation x-y values to move the flyout to its collapsed position. */
|
||||
private var translationToCollapsedPosition = PointF(0f, 0f)
|
||||
/** The size of the flyout when it's collapsed. */
|
||||
private var collapsedSize = 0f
|
||||
/** The corner radius of the flyout when it's collapsed. */
|
||||
private var collapsedCornerRadius = 0f
|
||||
|
||||
/**
|
||||
* The paint used to draw the background, whose color changes as the flyout transitions to the
|
||||
@@ -116,7 +130,28 @@ class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
|
||||
applyConfigurationColors(resources.configuration)
|
||||
}
|
||||
|
||||
fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
|
||||
/** Sets the data for the flyout and starts playing the expand animation. */
|
||||
fun showFromCollapsed(flyoutMessage: BubbleBarFlyoutMessage, expandAnimation: () -> Unit) {
|
||||
setData(flyoutMessage)
|
||||
val txToCollapsedPosition =
|
||||
if (positioner.isOnLeft) {
|
||||
positioner.distanceToCollapsedPosition.x
|
||||
} else {
|
||||
-positioner.distanceToCollapsedPosition.x
|
||||
}
|
||||
val tyToCollapsedPosition =
|
||||
positioner.distanceToCollapsedPosition.y + triangleHeight - triangleOverlap
|
||||
translationToCollapsedPosition = PointF(txToCollapsedPosition, tyToCollapsedPosition)
|
||||
|
||||
collapsedSize = positioner.collapsedSize
|
||||
collapsedCornerRadius = collapsedSize / 2
|
||||
|
||||
// post the request to start the expand animation to the looper so the view can measure
|
||||
// itself
|
||||
post(expandAnimation)
|
||||
}
|
||||
|
||||
private fun setData(flyoutMessage: BubbleBarFlyoutMessage) {
|
||||
// the avatar is only displayed in group chat messages
|
||||
if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) {
|
||||
avatar.visibility = VISIBLE
|
||||
@@ -151,24 +186,56 @@ class BubbleBarFlyoutView(context: Context, private val onLeft: Boolean) :
|
||||
message.text = flyoutMessage.message
|
||||
}
|
||||
|
||||
/** Updates the flyout view with the progress of the animation. */
|
||||
fun updateExpansionProgress(fraction: Float) {
|
||||
expansionProgress = fraction
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
// interpolate the width, height, corner radius and translation based on the progress of the
|
||||
// animation
|
||||
|
||||
val currentWidth = collapsedSize + (width - collapsedSize) * expansionProgress
|
||||
val rectBottom = height - triangleHeight + triangleOverlap
|
||||
val currentHeight = collapsedSize + (rectBottom - collapsedSize) * expansionProgress
|
||||
val currentCornerRadius =
|
||||
collapsedCornerRadius + (cornerRadius - collapsedCornerRadius) * expansionProgress
|
||||
val tx = translationToCollapsedPosition.x * (1 - expansionProgress)
|
||||
val ty = translationToCollapsedPosition.y * (1 - expansionProgress)
|
||||
|
||||
canvas.save()
|
||||
canvas.translate(tx, ty)
|
||||
// draw the background starting from the bottom left if we're positioned left, or the bottom
|
||||
// right if we're positioned right.
|
||||
canvas.drawRoundRect(
|
||||
0f,
|
||||
0f,
|
||||
width.toFloat(),
|
||||
if (positioner.isOnLeft) 0f else width.toFloat() - currentWidth,
|
||||
height.toFloat() - triangleHeight + triangleOverlap - currentHeight,
|
||||
if (positioner.isOnLeft) currentWidth else width.toFloat(),
|
||||
height.toFloat() - triangleHeight + triangleOverlap,
|
||||
cornerRadius,
|
||||
cornerRadius,
|
||||
currentCornerRadius,
|
||||
currentCornerRadius,
|
||||
backgroundPaint,
|
||||
)
|
||||
drawTriangle(canvas)
|
||||
if (expansionProgress >= MIN_EXPANSION_PROGRESS_FOR_TRIANGLE) {
|
||||
drawTriangle(canvas, currentCornerRadius)
|
||||
}
|
||||
canvas.restore()
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
|
||||
private fun drawTriangle(canvas: Canvas) {
|
||||
private fun drawTriangle(canvas: Canvas, currentCornerRadius: Float) {
|
||||
canvas.save()
|
||||
val triangleX = if (onLeft) cornerRadius else width - cornerRadius - triangleWidth
|
||||
canvas.translate(triangleX, (height - triangleHeight).toFloat())
|
||||
val triangleX =
|
||||
if (positioner.isOnLeft) {
|
||||
currentCornerRadius
|
||||
} else {
|
||||
width - currentCornerRadius - triangleWidth
|
||||
}
|
||||
// instead of scaling the triangle, increasingly reveal it from the background, starting
|
||||
// with half the size. this has the effect of the triangle scaling.
|
||||
val triangleY = height - triangleHeight - 0.5f * triangleHeight * (1 - expansionProgress)
|
||||
canvas.translate(triangleX, triangleY)
|
||||
canvas.drawPath(triangle, backgroundPaint)
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user