Merge "Add marquee for AppChip and simplify text view handling in IconAppChipView" into main
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user