Support change of several axes in textAnimator
Test: atest TextAnimatorTest, AnimatableClockViewTest, FontVariationUtilsTest Bug: 268067751 Change-Id: I3253e3b6ce58b38527ee8a47c0cbc77cac33e890
This commit is contained in:
@@ -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 ""
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user