Add outline to the bubble bar background view.

Added outline to the bubble bar background. Updated pointer
appearing/disappearing animation - now pointer moves into the bubble
bar background.
Demo: http://recall/-/gx8ASgewUeUS3QYohfrd1J/e1wZMrFZTILq73ik8wwrwx

Fixes: 345489039
Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT
Test: Manual. Expand and collapse bubble bar with the light and the
dark theme.

Change-Id: I095fdc12337955aa21ee0eb622a924ad424ef186
This commit is contained in:
mpodolian
2024-06-11 01:15:20 +01:00
parent 0d8b2206af
commit 84f9cfabb9
6 changed files with 135 additions and 64 deletions

View File

@@ -364,6 +364,7 @@
<dimen name="transient_taskbar_min_width">150dp</dimen>
<dimen name="transient_taskbar_bottom_margin">24dp</dimen>
<dimen name="transient_taskbar_shadow_blur">40dp</dimen>
<dimen name="transient_taskbar_stroke_width">1dp</dimen>
<dimen name="transient_taskbar_key_shadow_distance">10dp</dimen>
<dimen name="transient_taskbar_stashed_height">32dp</dimen>
<dimen name="transient_taskbar_all_apps_button_translation_x_offset">8dp</dimen>

View File

@@ -19,7 +19,9 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import com.android.app.animation.Interpolators
@@ -28,28 +30,28 @@ import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.popup.RoundedArrowDrawable
import kotlin.math.max
import kotlin.math.min
/** Drawable for the background of the bubble bar. */
class BubbleBarBackground(context: Context, private var backgroundHeight: Float) : Drawable() {
private val DARK_THEME_SHADOW_ALPHA = 51f
private val LIGHT_THEME_SHADOW_ALPHA = 25f
private val paint: Paint = Paint()
private val pointerWidth: Float
private val pointerHeight: Float
private val pointerTipRadius: Float
private val pointerVisibleHeight: Float
private val fillPaint: Paint = Paint()
private val strokePaint: Paint = Paint()
private val arrowWidth: Float
private val arrowHeight: Float
private val arrowTipRadius: Float
private val arrowVisibleHeight: Float
private val shadowAlpha: Float
private var shadowBlur = 0f
private var keyShadowDistance = 0f
private var arrowHeightFraction = 1f
var arrowPositionX: Float = 0f
private set
private var showingArrow: Boolean = false
private var arrowDrawable: RoundedArrowDrawable
var width: Float = 0f
@@ -70,34 +72,31 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
}
init {
paint.color = context.getColor(R.color.taskbar_background)
paint.flags = Paint.ANTI_ALIAS_FLAG
paint.style = Paint.Style.FILL
val res = context.resources
// configure fill paint
fillPaint.color = context.getColor(R.color.taskbar_background)
fillPaint.flags = Paint.ANTI_ALIAS_FLAG
fillPaint.style = Paint.Style.FILL
// configure stroke paint
strokePaint.color = context.getColor(R.color.taskbar_stroke)
strokePaint.flags = Paint.ANTI_ALIAS_FLAG
strokePaint.style = Paint.Style.STROKE
strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
// apply theme alpha attributes
if (Utilities.isDarkTheme(context)) {
strokePaint.alpha = DARK_THEME_STROKE_ALPHA
shadowAlpha = DARK_THEME_SHADOW_ALPHA
} else {
strokePaint.alpha = LIGHT_THEME_STROKE_ALPHA
shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
}
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
pointerWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
pointerHeight = res.getDimension(R.dimen.bubblebar_pointer_height)
pointerVisibleHeight = res.getDimension(R.dimen.bubblebar_pointer_visible_size)
pointerTipRadius = res.getDimension(R.dimen.bubblebar_pointer_radius)
shadowAlpha =
if (Utilities.isDarkTheme(context)) {
DARK_THEME_SHADOW_ALPHA
} else {
LIGHT_THEME_SHADOW_ALPHA
}
arrowDrawable =
RoundedArrowDrawable.createVerticalRoundedArrow(
pointerWidth,
pointerHeight,
pointerTipRadius,
/* isPointingUp= */ true,
context.getColor(R.color.taskbar_background)
)
arrowDrawable.setBounds(0, 0, pointerWidth.toInt(), pointerHeight.toInt())
arrowWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
arrowHeight = res.getDimension(R.dimen.bubblebar_pointer_height)
arrowVisibleHeight = res.getDimension(R.dimen.bubblebar_pointer_visible_size)
arrowTipRadius = res.getDimension(R.dimen.bubblebar_pointer_radius)
}
fun showArrow(show: Boolean) {
@@ -115,40 +114,53 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
// TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
// Draw shadows.
val newShadowAlpha =
mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
paint.setShadowLayer(
mapToRange(fillPaint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
fillPaint.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
arrowDrawable.setShadowLayer(
shadowBlur,
0f,
keyShadowDistance,
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
)
// Draw background.
// Create background path
val backgroundPath = Path()
val radius = backgroundHeight / 2f
val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
val top = bounds.top + pointerVisibleHeight
val top = bounds.top + arrowVisibleHeight
val bottom = bounds.top + bounds.height().toFloat()
canvas.drawRoundRect(left, top, right, bottom, radius, radius, paint)
backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
addArrowPathIfNeeded(backgroundPath)
if (showingArrow) {
// Draw arrow.
val transX = bounds.left + arrowPositionX - pointerWidth / 2f
canvas.translate(transX, 0f)
arrowDrawable.draw(canvas)
}
// Draw background.
canvas.drawPath(backgroundPath, fillPaint)
canvas.drawPath(backgroundPath, strokePaint)
canvas.restore()
}
private fun addArrowPathIfNeeded(sourcePath: Path) {
if (!showingArrow || arrowHeightFraction <= 0) return
val arrowPath = Path()
RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
arrowWidth,
arrowHeight,
arrowTipRadius,
arrowPath
)
// flip it horizontally
val pathTransform = Matrix()
pathTransform.setRotate(180f, arrowWidth * 0.5f, arrowHeight * 0.5f)
arrowPath.transform(pathTransform)
// shift to arrow position
val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight
arrowPath.offset(arrowStart, arrowTop)
// union with rectangle
sourcePath.op(arrowPath, Path.Op.UNION)
}
override fun getOpacity(): Int {
return when (paint.alpha) {
return when (fillPaint.alpha) {
255 -> PixelFormat.OPAQUE
0 -> PixelFormat.TRANSPARENT
else -> PixelFormat.TRANSLUCENT
@@ -156,24 +168,40 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
arrowDrawable.alpha = alpha
fillPaint.alpha = alpha
invalidateSelf()
}
override fun getAlpha(): Int {
return paint.alpha
return fillPaint.alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
fun setArrowAlpha(alpha: Int) {
arrowDrawable.alpha = alpha
fillPaint.colorFilter = colorFilter
}
fun setHeight(newHeight: Float) {
backgroundHeight = newHeight
}
/**
* Set fraction of the arrow height that should be displayed. Allowed values range are [0..1].
* If value passed is out of range it will be converted to the closest value in tha allowed
* range.
*/
fun setArrowHeightFraction(arrowHeightFraction: Float) {
var newHeightFraction = arrowHeightFraction
if (newHeightFraction !in 0f..1f) {
newHeightFraction = min(max(newHeightFraction, 0f), 1f)
}
this.arrowHeightFraction = newHeightFraction
invalidateSelf()
}
companion object {
private const val DARK_THEME_STROKE_ALPHA = 51
private const val LIGHT_THEME_STROKE_ALPHA = 41
private const val DARK_THEME_SHADOW_ALPHA = 51f
private const val LIGHT_THEME_SHADOW_ALPHA = 25f
}
}

View File

@@ -729,7 +729,7 @@ public class BubbleBarView extends FrameLayout {
}
}
mBubbleBarBackground.setArrowPosition(arrowPosition);
mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
mBubbleBarBackground.setArrowHeightFraction(widthState);
mBubbleBarBackground.setWidth(interpolatedWidth);
}
@@ -882,7 +882,8 @@ public class BubbleBarView extends FrameLayout {
// bubbles than the current bubble and overflow.
bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0;
} else {
bubblePosition = index;
bubblePosition = index >= MAX_VISIBLE_BUBBLES_COLLAPSED
? MAX_VISIBLE_BUBBLES_COLLAPSED - 1 : index;
}
return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#9AA0A6" />
</selector>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#BDC1C6" />
</selector>

View File

@@ -175,7 +175,10 @@ public class RoundedArrowDrawable extends Drawable {
mPaint.setShadowLayer(shadowBlur, dx, dy, shadowColor);
}
private static void addDownPointingRoundedTriangleToPath(float width, float height,
/**
* Adds rounded triangle pointing down to the provided {@link Path path} argument
*/
public static void addDownPointingRoundedTriangleToPath(float width, float height,
float radius, Path path) {
// Calculated for the arrow pointing down, will be flipped later if needed.