Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 523c1ce5 authored by Sherry Zhou's avatar Sherry Zhou
Browse files

Support change of several axes in textAnimator

Test: atest TextAnimatorTest, AnimatableClockViewTest, FontVariationUtilsTest
Bug: 268067751
Change-Id: I3253e3b6ce58b38527ee8a47c0cbc77cac33e890
parent e07cc86b
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
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 ""
    }
}
+64 −48
Original line number Diff line number Diff line
@@ -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)

        if (!fvar.isNullOrBlank()) {
            textInterpolator.targetPaint.typeface =
                        typefaceCache.getOrElse(weight) {
                            textInterpolator.targetPaint.fontVariationSettings =
                                    updatedFontVariation
                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()
        }
    }
}

private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
    var v = get(key)
    if (v == null) {
        v = defaultValue()
        put(key, v)
    /**
     * 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,
        )
    }
    return v
}
+62 −0
Original line number Diff line number Diff line
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)
    }
}
+0 −68
Original line number Diff line number Diff line
@@ -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)
            }
        }
    }
}