Support change of several axes in textAnimator

Test: atest TextAnimatorTest, AnimatableClockViewTest, FontVariationUtilsTest
Bug: 268067751
Change-Id: I3253e3b6ce58b38527ee8a47c0cbc77cac33e890
This commit is contained in:
Sherry Zhou
2023-03-06 22:20:27 +00:00
parent e07cc86b6c
commit 523c1ce500
4 changed files with 186 additions and 117 deletions

View File

@@ -0,0 +1,59 @@
package com.android.systemui.animation
private const val TAG_WGHT = "wght"
private const val TAG_WDTH = "wdth"
private const val TAG_OPSZ = "opsz"
private const val TAG_ROND = "ROND"
class FontVariationUtils {
private var mWeight = -1
private var mWidth = -1
private var mOpticalSize = -1
private var mRoundness = -1
private var isUpdated = false
/*
* generate fontVariationSettings string, used for key in typefaceCache in TextAnimator
* the order of axes should align to the order of parameters
* if every axis remains unchanged, return ""
*/
fun updateFontVariation(
weight: Int = -1,
width: Int = -1,
opticalSize: Int = -1,
roundness: Int = -1
): String {
isUpdated = false
if (weight >= 0 && mWeight != weight) {
isUpdated = true
mWeight = weight
}
if (width >= 0 && mWidth != width) {
isUpdated = true
mWidth = width
}
if (opticalSize >= 0 && mOpticalSize != opticalSize) {
isUpdated = true
mOpticalSize = opticalSize
}
if (roundness >= 0 && mRoundness != roundness) {
isUpdated = true
mRoundness = roundness
}
var resultString = ""
if (mWeight >= 0) {
resultString += "'$TAG_WGHT' $mWeight"
}
if (mWidth >= 0) {
resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_WDTH' $mWidth"
}
if (mOpticalSize >= 0) {
resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_OPSZ' $mOpticalSize"
}
if (mRoundness >= 0) {
resultString += (if (resultString.isBlank()) "" else ", ") + "'$TAG_ROND' $mRoundness"
}
return if (isUpdated) resultString else ""
}
}

View File

@@ -23,11 +23,8 @@ import android.animation.ValueAnimator
import android.graphics.Canvas
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.SparseArray
private const val TAG_WGHT = "wght"
private const val DEFAULT_ANIMATION_DURATION: Long = 300
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -51,7 +48,7 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
*
* // Change the text size with animation.
* fun setTextSize(sizePx: Float, animate: Boolean) {
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
* animator.setTextStyle("" /* unchanged fvar... */, sizePx, animate)
* }
* }
* ```
@@ -115,7 +112,9 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
protected set
}
private val typefaceCache = SparseArray<Typeface?>()
private val fontVariationUtils = FontVariationUtils()
private val typefaceCache = HashMap<String, Typeface?>()
fun updateLayout(layout: Layout) {
textInterpolator.layout = layout
@@ -186,7 +185,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
* Bu passing -1 to duration, the default text animation, 1000ms, is used.
* By passing false to animate, the text will be updated without animation.
*
* @param weight an optional text weight.
* @param fvar an optional text fontVariationSettings.
* @param textSize an optional font size.
* @param colors an optional colors array that must be the same size as numLines passed to
* the TextInterpolator
@@ -199,7 +198,7 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
* will be used. This is ignored if animate is false.
*/
fun setTextStyle(
weight: Int = -1,
fvar: String? = "",
textSize: Float = -1f,
color: Int? = null,
strokeWidth: Float = -1f,
@@ -217,42 +216,16 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
if (textSize >= 0) {
textInterpolator.targetPaint.textSize = textSize
}
if (weight >= 0) {
val fontVariationArray =
FontVariationAxis.fromFontVariationSettings(
textInterpolator.targetPaint.fontVariationSettings
)
if (fontVariationArray.isNullOrEmpty()) {
textInterpolator.targetPaint.typeface =
typefaceCache.getOrElse(weight) {
textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
textInterpolator.targetPaint.typeface
}
} else {
val idx = fontVariationArray.indexOfFirst { it.tag == "$TAG_WGHT" }
if (idx == -1) {
val updatedFontVariation =
textInterpolator.targetPaint.fontVariationSettings + ",'$TAG_WGHT' $weight"
textInterpolator.targetPaint.typeface =
typefaceCache.getOrElse(weight) {
textInterpolator.targetPaint.fontVariationSettings =
updatedFontVariation
textInterpolator.targetPaint.typeface
}
} else {
fontVariationArray[idx] = FontVariationAxis(
"$TAG_WGHT", weight.toFloat())
val updatedFontVariation =
FontVariationAxis.toFontVariationSettings(fontVariationArray)
textInterpolator.targetPaint.typeface =
typefaceCache.getOrElse(weight) {
textInterpolator.targetPaint.fontVariationSettings =
updatedFontVariation
textInterpolator.targetPaint.typeface
}
if (!fvar.isNullOrBlank()) {
textInterpolator.targetPaint.typeface =
typefaceCache.getOrElse(fvar) {
textInterpolator.targetPaint.fontVariationSettings = fvar
typefaceCache.put(fvar, textInterpolator.targetPaint.typeface)
textInterpolator.targetPaint.typeface
}
}
}
if (color != null) {
textInterpolator.targetPaint.color = color
}
@@ -291,13 +264,56 @@ class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
invalidateCallback()
}
}
/**
* Set text style with animation. Similar as
* fun setTextStyle(
* fvar: String? = "",
* textSize: Float = -1f,
* color: Int? = null,
* strokeWidth: Float = -1f,
* animate: Boolean = true,
* duration: Long = -1L,
* interpolator: TimeInterpolator? = null,
* delay: Long = 0,
* onAnimationEnd: Runnable? = null
* )
*
* @param weight an optional style value for `wght` in fontVariationSettings.
* @param width an optional style value for `wdth` in fontVariationSettings.
* @param opticalSize an optional style value for `opsz` in fontVariationSettings.
* @param roundness an optional style value for `ROND` in fontVariationSettings.
*/
fun setTextStyle(
weight: Int = -1,
width: Int = -1,
opticalSize: Int = -1,
roundness: Int = -1,
textSize: Float = -1f,
color: Int? = null,
strokeWidth: Float = -1f,
animate: Boolean = true,
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
) {
val fvar = fontVariationUtils.updateFontVariation(
weight = weight,
width = width,
opticalSize = opticalSize,
roundness = roundness,)
setTextStyle(
fvar = fvar,
textSize = textSize,
color = color,
strokeWidth = strokeWidth,
animate = animate,
duration = duration,
interpolator = interpolator,
delay = delay,
onAnimationEnd = onAnimationEnd,
)
}
}
private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
var v = get(key)
if (v == null) {
v = defaultValue()
put(key, v)
}
return v
}

View File

@@ -0,0 +1,62 @@
package com.android.systemui.animation
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert
import org.junit.Test
import org.junit.runner.RunWith
private const val TAG_WGHT = "wght"
private const val TAG_WDTH = "wdth"
private const val TAG_OPSZ = "opsz"
private const val TAG_ROND = "ROND"
@RunWith(AndroidTestingRunner::class)
@SmallTest
class FontVariationUtilsTest : SysuiTestCase() {
@Test
fun testUpdateFontVariation_getCorrectFvarStr() {
val fontVariationUtils = FontVariationUtils()
val initFvar =
fontVariationUtils.updateFontVariation(
weight = 100,
width = 100,
opticalSize = -1,
roundness = 100
)
Assert.assertEquals("'$TAG_WGHT' 100, '$TAG_WDTH' 100, '$TAG_ROND' 100", initFvar)
val updatedFvar =
fontVariationUtils.updateFontVariation(
weight = 200,
width = 100,
opticalSize = 0,
roundness = 100
)
Assert.assertEquals(
"'$TAG_WGHT' 200, '$TAG_WDTH' 100, '$TAG_OPSZ' 0, '$TAG_ROND' 100",
updatedFvar
)
}
@Test
fun testStyleValueUnchange_getBlankStr() {
val fontVariationUtils = FontVariationUtils()
fontVariationUtils.updateFontVariation(
weight = 100,
width = 100,
opticalSize = 0,
roundness = 100
)
val updatedFvar1 =
fontVariationUtils.updateFontVariation(
weight = 100,
width = 100,
opticalSize = 0,
roundness = 100
)
Assert.assertEquals("", updatedFvar1)
val updatedFvar2 = fontVariationUtils.updateFontVariation()
Assert.assertEquals("", updatedFvar2)
}
}

View File

@@ -19,7 +19,6 @@ package com.android.systemui.animation
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.graphics.Typeface
import android.graphics.fonts.FontVariationAxis
import android.testing.AndroidTestingRunner
import android.text.Layout
import android.text.StaticLayout
@@ -179,71 +178,4 @@ class TextAnimatorTest : SysuiTestCase() {
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
@Test
fun testSetTextStyle_addWeight() {
testWeightChange("", 100, FontVariationAxis.fromFontVariationSettings("'wght' 100")!!)
}
@Test
fun testSetTextStyle_changeWeight() {
testWeightChange(
"'wght' 500",
100,
FontVariationAxis.fromFontVariationSettings("'wght' 100")!!
)
}
@Test
fun testSetTextStyle_addWeightWithOtherAxis() {
testWeightChange(
"'wdth' 100",
100,
FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
)
}
@Test
fun testSetTextStyle_changeWeightWithOtherAxis() {
testWeightChange(
"'wght' 500, 'wdth' 100",
100,
FontVariationAxis.fromFontVariationSettings("'wght' 100, 'wdth' 100")!!
)
}
private fun testWeightChange(
initialFontVariationSettings: String,
weight: Int,
expectedFontVariationSettings: Array<FontVariationAxis>
) {
val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
val paint =
TextPaint().apply {
typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
fontVariationSettings = initialFontVariationSettings
}
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator =
TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(weight = weight, animate = false)
val resultFontVariationList =
FontVariationAxis.fromFontVariationSettings(
textInterpolator.targetPaint.fontVariationSettings
)
expectedFontVariationSettings.forEach { expectedAxis ->
val resultAxis = resultFontVariationList?.filter { it.tag == expectedAxis.tag }?.get(0)
assertThat(resultAxis).isNotNull()
if (resultAxis != null) {
assertThat(resultAxis.styleValue).isEqualTo(expectedAxis.styleValue)
}
}
}
}