Merge "Add marquee for AppChip and simplify text view handling in IconAppChipView" into main

This commit is contained in:
Jordan Silva
2025-03-26 04:28:26 -07:00
committed by Android (Google) Code Review
2 changed files with 109 additions and 70 deletions
+7 -14
View File
@@ -51,25 +51,18 @@
android:importantForAccessibility="no" />
<TextView
android:id="@+id/icon_text_collapsed"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_collapsed_max_width"
android:id="@+id/icon_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
android:textAlignment="viewStart"
android:importantForAccessibility="no"
style="@style/IconAppChipMenuTextStyle" />
<TextView
android:id="@+id/icon_text_expanded"
android:layout_width="@dimen/task_thumbnail_icon_menu_text_expanded_max_width"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:maxLines="1"
android:ellipsize="end"
android:singleLine="true"
android:ellipsize="marquee"
android:textAlignment="viewStart"
android:importantForAccessibility="no"
android:fadingEdge="horizontal"
android:marqueeRepeatLimit="1"
android:scrollHorizontally="true"
style="@style/IconAppChipMenuTextStyle" />
<ImageView
@@ -23,6 +23,7 @@ import android.content.Context
import android.graphics.Outline
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import android.view.ViewAnimationUtils
@@ -30,6 +31,8 @@ import android.view.ViewOutlineProvider
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators
import com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
@@ -57,8 +60,7 @@ constructor(
private var iconArrowView: ImageView? = null
private var menuAnchorView: View? = null
// Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name.
private var iconTextCollapsedView: TextView? = null
private var iconTextExpandedView: TextView? = null
private var appTitle: TextView? = null
private val backgroundRelativeLtrLocation = Rect()
private val backgroundAnimationRectEvaluator = RectEvaluator(backgroundRelativeLtrLocation)
@@ -94,6 +96,8 @@ constructor(
resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_view_start_margin)
private val appIconSize =
resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size)
private val expandedMaxTextWidth =
resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_text_expanded_max_width)
private val arrowSize =
resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_size)
private val iconViewDrawableExpandedSize =
@@ -110,7 +114,10 @@ constructor(
private val viewTranslationY: MultiPropertyFactory<View> =
MultiPropertyFactory(this, VIEW_TRANSLATE_Y, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR)
var maxWidth = Int.MAX_VALUE
// Width showing only the app icon and arrow. Max width should not be set to less than
// this.
private val minWidthAllowed = iconViewMarginStart + appIconSize + arrowSize + arrowMarginEnd
var maxWidth = Integer.MAX_VALUE
/**
* Sets the maximum width of this Icon Menu. This is usually used when space is limited for
* split screen.
@@ -118,8 +125,7 @@ constructor(
set(value) {
// Width showing only the app icon and arrow. Max width should not be set to less than
// this.
val minMaxWidth = iconViewMarginStart + appIconSize + arrowSize + arrowMarginEnd
field = max(value, minMaxWidth)
field = max(value, minWidthAllowed)
}
var status: AppChipStatus = AppChipStatus.Collapsed
@@ -128,15 +134,14 @@ constructor(
override fun onFinishInflate() {
super.onFinishInflate()
iconView = findViewById(R.id.icon_view)
iconTextCollapsedView = findViewById(R.id.icon_text_collapsed)
iconTextExpandedView = findViewById(R.id.icon_text_expanded)
appTitle = findViewById(R.id.icon_title)
iconArrowView = findViewById(R.id.icon_arrow)
menuAnchorView = findViewById(R.id.icon_view_menu_anchor)
}
override fun setText(text: CharSequence?) {
iconTextCollapsedView?.text = text
iconTextExpandedView?.text = text
if (text == appTitle?.text) return
appTitle?.text = text
}
override fun getDrawable(): Drawable? = iconView?.drawable
@@ -200,30 +205,17 @@ constructor(
// Layout Params for the collapsed Icon Text View
val textMarginStart =
iconMarginStartRelativeToParent + appIconSize + appNameHorizontalMargin
val iconTextCollapsedParams = iconTextCollapsedView!!.layoutParams as LayoutParams
val iconTextCollapsedParams = appTitle!!.layoutParams as LayoutParams
orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart)
val collapsedTextWidth =
(collapsedBackgroundBounds.width() -
iconViewMarginStart -
appIconSize -
arrowSize -
appNameHorizontalMargin -
arrowMarginEnd)
iconTextCollapsedParams.width = collapsedTextWidth
iconTextCollapsedView!!.layoutParams = iconTextCollapsedParams
iconTextCollapsedView!!.alpha = 1f
// Layout Params for the expanded Icon Text View
val iconTextExpandedParams = iconTextExpandedView!!.layoutParams as LayoutParams
orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart)
iconTextExpandedView!!.layoutParams = iconTextExpandedParams
iconTextExpandedView!!.alpha = 0f
iconTextExpandedView!!.setRevealClip(
true,
0f,
appIconSize / 2f,
collapsedTextWidth.toFloat(),
)
iconTextCollapsedParams.width =
calculateCollapsedTextWidth(collapsedBackgroundBounds.width())
appTitle!!.layoutParams = iconTextCollapsedParams
// select to enable marquee and disable it when is running test harness.
if (Utilities.isRunningInTestHarness()) {
disableAppTitleMarquee()
} else {
appTitle!!.isSelected = true
}
// Layout Params for the Icon Arrow View
val iconArrowParams = iconArrowView!!.layoutParams as LayoutParams
@@ -243,6 +235,38 @@ constructor(
)
}
private fun disableAppTitleMarquee() {
appTitle?.let {
it.isHorizontalFadingEdgeEnabled = false
it.ellipsize = TextUtils.TruncateAt.END
it.horizontallyScrolling = false
it.isSelected = false
}
}
/**
* Calculates the width available for the collapsed text (app name) within the view.
*
* This function determines the maximum width that the app name can occupy when the view is in
* its collapsed state. It considers various factors such as the maximum allowed width, the
* bounds of the collapsed background, the size of the app icon, the arrow, and the margins
* around these elements.
*
* @return The calculated width available for the collapsed text (app name).
*/
private fun calculateCollapsedTextWidth(width: Int): Int {
val collapsedTextWidth =
(width -
iconViewMarginStart -
appIconSize -
arrowSize -
appNameHorizontalMargin -
arrowMarginEnd)
val spaceLeftForText = maxWidth - minWidthAllowed
return minOf(collapsedTextWidth, spaceLeftForText)
}
override fun setIconColorTint(color: Int, amount: Float) {
// RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu.
val colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, Interpolators.LINEAR)
@@ -312,11 +336,11 @@ constructor(
// Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
val expandedTextRevealAnim =
ViewAnimationUtils.createCircularReveal(
iconTextExpandedView,
appTitle,
0,
iconTextExpandedView!!.height / 2,
iconTextCollapsedView!!.width.toFloat(),
iconTextExpandedView!!.width.toFloat(),
appTitle!!.height / 2,
appTitle!!.width.toFloat(),
expandedMaxTextWidth.toFloat(),
)
// Animate background clipping
val backgroundAnimator =
@@ -343,18 +367,7 @@ constructor(
backgroundAnimator,
ObjectAnimator.ofFloat(iconView, SCALE_X, iconViewScaling),
ObjectAnimator.ofFloat(iconView, SCALE_Y, iconViewScaling),
ObjectAnimator.ofFloat(
iconTextCollapsedView,
TRANSLATION_X,
textTranslationXWithRtl,
),
ObjectAnimator.ofFloat(
iconTextExpandedView,
TRANSLATION_X,
textTranslationXWithRtl,
),
ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 0f),
ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 1f),
ObjectAnimator.ofFloat(appTitle, TRANSLATION_X, textTranslationXWithRtl),
ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, arrowTranslationWithRtl),
ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f),
)
@@ -364,11 +377,11 @@ constructor(
// Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
val expandedTextClipAnim =
ViewAnimationUtils.createCircularReveal(
iconTextExpandedView,
appTitle,
0,
iconTextExpandedView!!.height / 2,
iconTextExpandedView!!.width.toFloat(),
iconTextCollapsedView!!.width.toFloat(),
appTitle!!.height / 2,
appTitle!!.width.toFloat(),
calculateCollapsedTextWidth(collapsedBackgroundBounds.width()).toFloat(),
)
// Animate background clipping
@@ -386,10 +399,7 @@ constructor(
expandedTextClipAnim,
backgroundAnimator,
ObjectAnimator.ofFloat(iconView, SCALE_PROPERTY, 1f),
ObjectAnimator.ofFloat(iconTextCollapsedView, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(iconTextExpandedView, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 1f),
ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 0f),
ObjectAnimator.ofFloat(appTitle, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, 0f),
ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f),
)
@@ -399,9 +409,45 @@ constructor(
if (!animated) animator!!.duration = 0
animator!!.interpolator = Interpolators.EMPHASIZED
animator!!.addListener(
onStart = {
appTitle!!.isSelected = false
if (status == AppChipStatus.Expanded) updateTitleSize()
},
onEnd = {
if (status == AppChipStatus.Collapsed) updateTitleSize()
appTitle!!.isSelected = true
},
)
animator!!.start()
}
/**
* Updates the width of the app title based on the current [AppChipStatus].
*
* This function dynamically adjusts the width of the `appTitle` TextView depending on whether
* the app chip is in an expanded or collapsed state.
* - When the chip is [AppChipStatus.Expanded], the title width is set to
* [expandedMaxTextWidth], allowing the title to potentially take up more space.
* - When the chip is [AppChipStatus.Collapsed], the title width is calculated based on the
* width of the collapsed background. This ensures the title fits within the smaller,
* collapsed chip boundaries. The width is then determined by calling
* [calculateCollapsedTextWidth].
*/
private fun updateTitleSize() {
val appTitleWidth =
when (status) {
AppChipStatus.Expanded -> expandedMaxTextWidth
AppChipStatus.Collapsed -> {
val collapsedBackgroundWidth = getCollapsedBackgroundLtrBounds().width()
calculateCollapsedTextWidth(collapsedBackgroundWidth)
}
}
appTitle!!.updateLayoutParams { width = appTitleWidth }
}
private fun getCollapsedBackgroundLtrBounds(): Rect {
val bounds =
Rect(0, 0, min(maxWidth, collapsedMenuDefaultWidth), collapsedMenuDefaultHeight)