Merge "Format Font/Text Animation files" into tm-qpr-dev am: f3a6b54477
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20948431 Change-Id: Ic3c748a737931b851792e392a0cd06d89c7fe28a Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -33,9 +33,7 @@ private const val FONT_ITALIC_MIN = 0f
|
|||||||
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
|
private const val FONT_ITALIC_ANIMATION_STEP = 0.1f
|
||||||
private const val FONT_ITALIC_DEFAULT_VALUE = 0f
|
private const val FONT_ITALIC_DEFAULT_VALUE = 0f
|
||||||
|
|
||||||
/**
|
/** Provide interpolation of two fonts by adjusting font variation settings. */
|
||||||
* Provide interpolation of two fonts by adjusting font variation settings.
|
|
||||||
*/
|
|
||||||
class FontInterpolator {
|
class FontInterpolator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,11 +59,14 @@ class FontInterpolator {
|
|||||||
var index: Int,
|
var index: Int,
|
||||||
val sortedAxes: MutableList<FontVariationAxis>
|
val sortedAxes: MutableList<FontVariationAxis>
|
||||||
) {
|
) {
|
||||||
constructor(font: Font, axes: List<FontVariationAxis>) :
|
constructor(
|
||||||
this(font.sourceIdentifier,
|
font: Font,
|
||||||
font.ttcIndex,
|
axes: List<FontVariationAxis>
|
||||||
axes.toMutableList().apply { sortBy { it.tag } }
|
) : this(
|
||||||
)
|
font.sourceIdentifier,
|
||||||
|
font.ttcIndex,
|
||||||
|
axes.toMutableList().apply { sortBy { it.tag } }
|
||||||
|
)
|
||||||
|
|
||||||
fun set(font: Font, axes: List<FontVariationAxis>) {
|
fun set(font: Font, axes: List<FontVariationAxis>) {
|
||||||
sourceId = font.sourceIdentifier
|
sourceId = font.sourceIdentifier
|
||||||
@@ -86,9 +87,7 @@ class FontInterpolator {
|
|||||||
private val tmpInterpKey = InterpKey(null, null, 0f)
|
private val tmpInterpKey = InterpKey(null, null, 0f)
|
||||||
private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
|
private val tmpVarFontKey = VarFontKey(0, 0, mutableListOf())
|
||||||
|
|
||||||
/**
|
/** Linear interpolate the font variation settings. */
|
||||||
* Linear interpolate the font variation settings.
|
|
||||||
*/
|
|
||||||
fun lerp(start: Font, end: Font, progress: Float): Font {
|
fun lerp(start: Font, end: Font, progress: Float): Font {
|
||||||
if (progress == 0f) {
|
if (progress == 0f) {
|
||||||
return start
|
return start
|
||||||
@@ -115,27 +114,34 @@ class FontInterpolator {
|
|||||||
// this doesn't take much time since the variation axes is usually up to 5. If we need to
|
// this doesn't take much time since the variation axes is usually up to 5. If we need to
|
||||||
// support more number of axes, we may want to preprocess the font and store the sorted axes
|
// support more number of axes, we may want to preprocess the font and store the sorted axes
|
||||||
// and also pre-fill the missing axes value with default value from 'fvar' table.
|
// and also pre-fill the missing axes value with default value from 'fvar' table.
|
||||||
val newAxes = lerp(startAxes, endAxes) { tag, startValue, endValue ->
|
val newAxes =
|
||||||
when (tag) {
|
lerp(startAxes, endAxes) { tag, startValue, endValue ->
|
||||||
// TODO: Good to parse 'fvar' table for retrieving default value.
|
when (tag) {
|
||||||
TAG_WGHT -> adjustWeight(
|
// TODO: Good to parse 'fvar' table for retrieving default value.
|
||||||
MathUtils.lerp(
|
TAG_WGHT ->
|
||||||
|
adjustWeight(
|
||||||
|
MathUtils.lerp(
|
||||||
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
|
startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
|
||||||
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
|
endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
|
||||||
progress))
|
progress
|
||||||
TAG_ITAL -> adjustItalic(
|
)
|
||||||
MathUtils.lerp(
|
)
|
||||||
|
TAG_ITAL ->
|
||||||
|
adjustItalic(
|
||||||
|
MathUtils.lerp(
|
||||||
startValue ?: FONT_ITALIC_DEFAULT_VALUE,
|
startValue ?: FONT_ITALIC_DEFAULT_VALUE,
|
||||||
endValue ?: FONT_ITALIC_DEFAULT_VALUE,
|
endValue ?: FONT_ITALIC_DEFAULT_VALUE,
|
||||||
progress))
|
progress
|
||||||
else -> {
|
)
|
||||||
require(startValue != null && endValue != null) {
|
)
|
||||||
"Unable to interpolate due to unknown default axes value : $tag"
|
else -> {
|
||||||
|
require(startValue != null && endValue != null) {
|
||||||
|
"Unable to interpolate due to unknown default axes value : $tag"
|
||||||
|
}
|
||||||
|
MathUtils.lerp(startValue, endValue, progress)
|
||||||
}
|
}
|
||||||
MathUtils.lerp(startValue, endValue, progress)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we already make font for this axes. This is typically happens if the animation
|
// Check if we already make font for this axes. This is typically happens if the animation
|
||||||
// happens backward.
|
// happens backward.
|
||||||
@@ -149,9 +155,7 @@ class FontInterpolator {
|
|||||||
// This is the first time to make the font for the axes. Build and store it to the cache.
|
// This is the first time to make the font for the axes. Build and store it to the cache.
|
||||||
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will
|
// Font.Builder#build won't throw IOException since creating fonts from existing fonts will
|
||||||
// not do any IO work.
|
// not do any IO work.
|
||||||
val newFont = Font.Builder(start)
|
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
|
||||||
.setFontVariationSettings(newAxes.toTypedArray())
|
|
||||||
.build()
|
|
||||||
interpCache[InterpKey(start, end, progress)] = newFont
|
interpCache[InterpKey(start, end, progress)] = newFont
|
||||||
verFontCache[VarFontKey(start, newAxes)] = newFont
|
verFontCache[VarFontKey(start, newAxes)] = newFont
|
||||||
return newFont
|
return newFont
|
||||||
@@ -173,26 +177,28 @@ class FontInterpolator {
|
|||||||
val tagA = if (i < start.size) start[i].tag else null
|
val tagA = if (i < start.size) start[i].tag else null
|
||||||
val tagB = if (j < end.size) end[j].tag else null
|
val tagB = if (j < end.size) end[j].tag else null
|
||||||
|
|
||||||
val comp = when {
|
val comp =
|
||||||
tagA == null -> 1
|
when {
|
||||||
tagB == null -> -1
|
tagA == null -> 1
|
||||||
else -> tagA.compareTo(tagB)
|
tagB == null -> -1
|
||||||
}
|
else -> tagA.compareTo(tagB)
|
||||||
|
}
|
||||||
|
|
||||||
val axis = when {
|
val axis =
|
||||||
comp == 0 -> {
|
when {
|
||||||
val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
|
comp == 0 -> {
|
||||||
FontVariationAxis(tagA, v)
|
val v = filter(tagA!!, start[i++].styleValue, end[j++].styleValue)
|
||||||
|
FontVariationAxis(tagA, v)
|
||||||
|
}
|
||||||
|
comp < 0 -> {
|
||||||
|
val v = filter(tagA!!, start[i++].styleValue, null)
|
||||||
|
FontVariationAxis(tagA, v)
|
||||||
|
}
|
||||||
|
else -> { // comp > 0
|
||||||
|
val v = filter(tagB!!, null, end[j++].styleValue)
|
||||||
|
FontVariationAxis(tagB, v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
comp < 0 -> {
|
|
||||||
val v = filter(tagA!!, start[i++].styleValue, null)
|
|
||||||
FontVariationAxis(tagA, v)
|
|
||||||
}
|
|
||||||
else -> { // comp > 0
|
|
||||||
val v = filter(tagB!!, null, end[j++].styleValue)
|
|
||||||
FontVariationAxis(tagB, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.add(axis)
|
result.add(axis)
|
||||||
}
|
}
|
||||||
@@ -202,21 +208,21 @@ class FontInterpolator {
|
|||||||
// For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
|
// For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
|
||||||
// Cache hit ratio in the Skia glyph cache.
|
// Cache hit ratio in the Skia glyph cache.
|
||||||
private fun adjustWeight(value: Float) =
|
private fun adjustWeight(value: Float) =
|
||||||
coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
|
coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
|
||||||
|
|
||||||
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
|
// For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
|
||||||
// Cache hit ratio in the Skia glyph cache.
|
// Cache hit ratio in the Skia glyph cache.
|
||||||
private fun adjustItalic(value: Float) =
|
private fun adjustItalic(value: Float) =
|
||||||
coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
|
coerceInWithStep(value, FONT_ITALIC_MIN, FONT_ITALIC_MAX, FONT_ITALIC_ANIMATION_STEP)
|
||||||
|
|
||||||
private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
|
private fun coerceInWithStep(v: Float, min: Float, max: Float, step: Float) =
|
||||||
(v.coerceIn(min, max) / step).toInt() * step
|
(v.coerceIn(min, max) / step).toInt() * step
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
|
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
|
||||||
|
|
||||||
// Returns true if given two font instance can be interpolated.
|
// Returns true if given two font instance can be interpolated.
|
||||||
fun canInterpolate(start: Font, end: Font) =
|
fun canInterpolate(start: Font, end: Font) =
|
||||||
start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
|
start.ttcIndex == end.ttcIndex && start.sourceIdentifier == end.sourceIdentifier
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
|
|||||||
* Currently this class can provide text style animation for text weight and text size. For example
|
* Currently this class can provide text style animation for text weight and text size. For example
|
||||||
* the simple view that draws text with animating text size is like as follows:
|
* the simple view that draws text with animating text size is like as follows:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre> <code>
|
||||||
* <code>
|
* ```
|
||||||
* class SimpleTextAnimation : View {
|
* class SimpleTextAnimation : View {
|
||||||
* @JvmOverloads constructor(...)
|
* @JvmOverloads constructor(...)
|
||||||
*
|
*
|
||||||
@@ -53,83 +53,63 @@ typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
|
|||||||
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
|
* animator.setTextStyle(-1 /* unchanged weight */, sizePx, animate)
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* </code>
|
* ```
|
||||||
* </pre>
|
* </code> </pre>
|
||||||
*/
|
*/
|
||||||
class TextAnimator(
|
class TextAnimator(layout: Layout, private val invalidateCallback: () -> Unit) {
|
||||||
layout: Layout,
|
|
||||||
private val invalidateCallback: () -> Unit
|
|
||||||
) {
|
|
||||||
// Following two members are for mutable for testing purposes.
|
// Following two members are for mutable for testing purposes.
|
||||||
public var textInterpolator: TextInterpolator = TextInterpolator(layout)
|
public var textInterpolator: TextInterpolator = TextInterpolator(layout)
|
||||||
public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
|
public var animator: ValueAnimator =
|
||||||
duration = DEFAULT_ANIMATION_DURATION
|
ValueAnimator.ofFloat(1f).apply {
|
||||||
addUpdateListener {
|
duration = DEFAULT_ANIMATION_DURATION
|
||||||
textInterpolator.progress = it.animatedValue as Float
|
addUpdateListener {
|
||||||
invalidateCallback()
|
textInterpolator.progress = it.animatedValue as Float
|
||||||
}
|
invalidateCallback()
|
||||||
addListener(object : AnimatorListenerAdapter() {
|
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
|
||||||
textInterpolator.rebase()
|
|
||||||
}
|
}
|
||||||
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
|
addListener(
|
||||||
})
|
object : AnimatorListenerAdapter() {
|
||||||
}
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
|
textInterpolator.rebase()
|
||||||
|
}
|
||||||
|
override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
sealed class PositionedGlyph {
|
sealed class PositionedGlyph {
|
||||||
|
|
||||||
/**
|
/** Mutable X coordinate of the glyph position relative from drawing offset. */
|
||||||
* Mutable X coordinate of the glyph position relative from drawing offset.
|
|
||||||
*/
|
|
||||||
var x: Float = 0f
|
var x: Float = 0f
|
||||||
|
|
||||||
/**
|
/** Mutable Y coordinate of the glyph position relative from the baseline. */
|
||||||
* Mutable Y coordinate of the glyph position relative from the baseline.
|
|
||||||
*/
|
|
||||||
var y: Float = 0f
|
var y: Float = 0f
|
||||||
|
|
||||||
/**
|
/** The current line of text being drawn, in a multi-line TextView. */
|
||||||
* The current line of text being drawn, in a multi-line TextView.
|
|
||||||
*/
|
|
||||||
var lineNo: Int = 0
|
var lineNo: Int = 0
|
||||||
|
|
||||||
/**
|
/** Mutable text size of the glyph in pixels. */
|
||||||
* Mutable text size of the glyph in pixels.
|
|
||||||
*/
|
|
||||||
var textSize: Float = 0f
|
var textSize: Float = 0f
|
||||||
|
|
||||||
/**
|
/** Mutable color of the glyph. */
|
||||||
* Mutable color of the glyph.
|
|
||||||
*/
|
|
||||||
var color: Int = 0
|
var color: Int = 0
|
||||||
|
|
||||||
/**
|
/** Immutable character offset in the text that the current font run start. */
|
||||||
* Immutable character offset in the text that the current font run start.
|
|
||||||
*/
|
|
||||||
abstract var runStart: Int
|
abstract var runStart: Int
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/**
|
/** Immutable run length of the font run. */
|
||||||
* Immutable run length of the font run.
|
|
||||||
*/
|
|
||||||
abstract var runLength: Int
|
abstract var runLength: Int
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/**
|
/** Immutable glyph index of the font run. */
|
||||||
* Immutable glyph index of the font run.
|
|
||||||
*/
|
|
||||||
abstract var glyphIndex: Int
|
abstract var glyphIndex: Int
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/**
|
/** Immutable font instance for this font run. */
|
||||||
* Immutable font instance for this font run.
|
|
||||||
*/
|
|
||||||
abstract var font: Font
|
abstract var font: Font
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/**
|
/** Immutable glyph ID for this glyph. */
|
||||||
* Immutable glyph ID for this glyph.
|
|
||||||
*/
|
|
||||||
abstract var glyphId: Int
|
abstract var glyphId: Int
|
||||||
protected set
|
protected set
|
||||||
}
|
}
|
||||||
@@ -147,20 +127,18 @@ class TextAnimator(
|
|||||||
/**
|
/**
|
||||||
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
|
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
|
||||||
*
|
*
|
||||||
* This callback is called for each glyphs just before drawing the glyphs. This function will
|
* This callback is called for each glyphs just before drawing the glyphs. This function will be
|
||||||
* be called with the intrinsic position, size, color, glyph ID and font instance. You can
|
* called with the intrinsic position, size, color, glyph ID and font instance. You can mutate
|
||||||
* mutate the position, size and color for tweaking animations.
|
* the position, size and color for tweaking animations. Do not keep the reference of passed
|
||||||
* Do not keep the reference of passed glyph object. The interpolator reuses that object for
|
* glyph object. The interpolator reuses that object for avoiding object allocations.
|
||||||
* avoiding object allocations.
|
|
||||||
*
|
*
|
||||||
* Details:
|
* Details: The text is drawn with font run units. The font run is a text segment that draws
|
||||||
* The text is drawn with font run units. The font run is a text segment that draws with the
|
* with the same font. The {@code runStart} and {@code runLimit} is a range of the font run in
|
||||||
* same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
|
* the text that current glyph is in. Once the font run is determined, the system will convert
|
||||||
* that current glyph is in. Once the font run is determined, the system will convert characters
|
* characters into glyph IDs. The {@code glyphId} is the glyph identifier in the font and {@code
|
||||||
* into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
|
* glyphIndex} is the offset of the converted glyph array. Please note that the {@code
|
||||||
* {@code glyphIndex} is the offset of the converted glyph array. Please note that the
|
* glyphIndex} is not a character index, because the character will not be converted to glyph
|
||||||
* {@code glyphIndex} is not a character index, because the character will not be converted to
|
* one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
|
||||||
* glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
|
|
||||||
* composed from multiple characters.
|
* composed from multiple characters.
|
||||||
*
|
*
|
||||||
* Here is an example of font runs: "fin. 終わり"
|
* Here is an example of font runs: "fin. 終わり"
|
||||||
@@ -193,7 +171,9 @@ class TextAnimator(
|
|||||||
*/
|
*/
|
||||||
var glyphFilter: GlyphCallback?
|
var glyphFilter: GlyphCallback?
|
||||||
get() = textInterpolator.glyphFilter
|
get() = textInterpolator.glyphFilter
|
||||||
set(value) { textInterpolator.glyphFilter = value }
|
set(value) {
|
||||||
|
textInterpolator.glyphFilter = value
|
||||||
|
}
|
||||||
|
|
||||||
fun draw(c: Canvas) = textInterpolator.draw(c)
|
fun draw(c: Canvas) = textInterpolator.draw(c)
|
||||||
|
|
||||||
@@ -208,7 +188,7 @@ class TextAnimator(
|
|||||||
* @param weight an optional text weight.
|
* @param weight an optional text weight.
|
||||||
* @param textSize an optional font size.
|
* @param textSize an optional font size.
|
||||||
* @param colors an optional colors array that must be the same size as numLines passed to
|
* @param colors an optional colors array that must be the same size as numLines passed to
|
||||||
* the TextInterpolator
|
* the TextInterpolator
|
||||||
* @param animate an optional boolean indicating true for showing style transition as animation,
|
* @param animate an optional boolean indicating true for showing style transition as animation,
|
||||||
* false for immediate style transition. True by default.
|
* false for immediate style transition. True by default.
|
||||||
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
|
* @param duration an optional animation duration in milliseconds. This is ignored if animate is
|
||||||
@@ -237,10 +217,11 @@ class TextAnimator(
|
|||||||
if (weight >= 0) {
|
if (weight >= 0) {
|
||||||
// Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
|
// Paint#setFontVariationSettings creates Typeface instance from scratch. To reduce the
|
||||||
// memory impact, cache the typeface result.
|
// memory impact, cache the typeface result.
|
||||||
textInterpolator.targetPaint.typeface = typefaceCache.getOrElse(weight) {
|
textInterpolator.targetPaint.typeface =
|
||||||
textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
|
typefaceCache.getOrElse(weight) {
|
||||||
textInterpolator.targetPaint.typeface
|
textInterpolator.targetPaint.fontVariationSettings = "'$TAG_WGHT' $weight"
|
||||||
}
|
textInterpolator.targetPaint.typeface
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (color != null) {
|
if (color != null) {
|
||||||
textInterpolator.targetPaint.color = color
|
textInterpolator.targetPaint.color = color
|
||||||
@@ -249,22 +230,24 @@ class TextAnimator(
|
|||||||
|
|
||||||
if (animate) {
|
if (animate) {
|
||||||
animator.startDelay = delay
|
animator.startDelay = delay
|
||||||
animator.duration = if (duration == -1L) {
|
animator.duration =
|
||||||
DEFAULT_ANIMATION_DURATION
|
if (duration == -1L) {
|
||||||
} else {
|
DEFAULT_ANIMATION_DURATION
|
||||||
duration
|
} else {
|
||||||
}
|
duration
|
||||||
|
}
|
||||||
interpolator?.let { animator.interpolator = it }
|
interpolator?.let { animator.interpolator = it }
|
||||||
if (onAnimationEnd != null) {
|
if (onAnimationEnd != null) {
|
||||||
val listener = object : AnimatorListenerAdapter() {
|
val listener =
|
||||||
override fun onAnimationEnd(animation: Animator?) {
|
object : AnimatorListenerAdapter() {
|
||||||
onAnimationEnd.run()
|
override fun onAnimationEnd(animation: Animator?) {
|
||||||
animator.removeListener(this)
|
onAnimationEnd.run()
|
||||||
|
animator.removeListener(this)
|
||||||
|
}
|
||||||
|
override fun onAnimationCancel(animation: Animator?) {
|
||||||
|
animator.removeListener(this)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override fun onAnimationCancel(animation: Animator?) {
|
|
||||||
animator.removeListener(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animator.addListener(listener)
|
animator.addListener(listener)
|
||||||
}
|
}
|
||||||
animator.start()
|
animator.start()
|
||||||
|
|||||||
@@ -26,12 +26,8 @@ import android.util.MathUtils
|
|||||||
import com.android.internal.graphics.ColorUtils
|
import com.android.internal.graphics.ColorUtils
|
||||||
import java.lang.Math.max
|
import java.lang.Math.max
|
||||||
|
|
||||||
/**
|
/** Provide text style linear interpolation for plain text. */
|
||||||
* Provide text style linear interpolation for plain text.
|
class TextInterpolator(layout: Layout) {
|
||||||
*/
|
|
||||||
class TextInterpolator(
|
|
||||||
layout: Layout
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns base paint used for interpolation.
|
* Returns base paint used for interpolation.
|
||||||
@@ -64,12 +60,11 @@ class TextInterpolator(
|
|||||||
var baseFont: Font,
|
var baseFont: Font,
|
||||||
var targetFont: Font
|
var targetFont: Font
|
||||||
) {
|
) {
|
||||||
val length: Int get() = end - start
|
val length: Int
|
||||||
|
get() = end - start
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A class represents text layout of a single run. */
|
||||||
* A class represents text layout of a single run.
|
|
||||||
*/
|
|
||||||
private class Run(
|
private class Run(
|
||||||
val glyphIds: IntArray,
|
val glyphIds: IntArray,
|
||||||
val baseX: FloatArray, // same length as glyphIds
|
val baseX: FloatArray, // same length as glyphIds
|
||||||
@@ -79,12 +74,8 @@ class TextInterpolator(
|
|||||||
val fontRuns: List<FontRun>
|
val fontRuns: List<FontRun>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/** A class represents text layout of a single line. */
|
||||||
* A class represents text layout of a single line.
|
private class Line(val runs: List<Run>)
|
||||||
*/
|
|
||||||
private class Line(
|
|
||||||
val runs: List<Run>
|
|
||||||
)
|
|
||||||
|
|
||||||
private var lines = listOf<Line>()
|
private var lines = listOf<Line>()
|
||||||
private val fontInterpolator = FontInterpolator()
|
private val fontInterpolator = FontInterpolator()
|
||||||
@@ -106,8 +97,8 @@ class TextInterpolator(
|
|||||||
/**
|
/**
|
||||||
* The layout used for drawing text.
|
* The layout used for drawing text.
|
||||||
*
|
*
|
||||||
* Only non-styled text is supported. Even if the given layout is created from Spanned, the
|
* Only non-styled text is supported. Even if the given layout is created from Spanned, the span
|
||||||
* span information is not used.
|
* information is not used.
|
||||||
*
|
*
|
||||||
* The paint objects used for interpolation are not changed by this method call.
|
* The paint objects used for interpolation are not changed by this method call.
|
||||||
*
|
*
|
||||||
@@ -133,8 +124,8 @@ class TextInterpolator(
|
|||||||
/**
|
/**
|
||||||
* Recalculate internal text layout for interpolation.
|
* Recalculate internal text layout for interpolation.
|
||||||
*
|
*
|
||||||
* Whenever the target paint is modified, call this method to recalculate internal
|
* Whenever the target paint is modified, call this method to recalculate internal text layout
|
||||||
* text layout used for interpolation.
|
* used for interpolation.
|
||||||
*/
|
*/
|
||||||
fun onTargetPaintModified() {
|
fun onTargetPaintModified() {
|
||||||
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
|
updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false)
|
||||||
@@ -143,8 +134,8 @@ class TextInterpolator(
|
|||||||
/**
|
/**
|
||||||
* Recalculate internal text layout for interpolation.
|
* Recalculate internal text layout for interpolation.
|
||||||
*
|
*
|
||||||
* Whenever the base paint is modified, call this method to recalculate internal
|
* Whenever the base paint is modified, call this method to recalculate internal text layout
|
||||||
* text layout used for interpolation.
|
* used for interpolation.
|
||||||
*/
|
*/
|
||||||
fun onBasePaintModified() {
|
fun onBasePaintModified() {
|
||||||
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
|
updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true)
|
||||||
@@ -155,11 +146,11 @@ class TextInterpolator(
|
|||||||
*
|
*
|
||||||
* The text interpolator does not calculate all the text position by text shaper due to
|
* The text interpolator does not calculate all the text position by text shaper due to
|
||||||
* performance reasons. Instead, the text interpolator shape the start and end state and
|
* performance reasons. Instead, the text interpolator shape the start and end state and
|
||||||
* calculate text position of the middle state by linear interpolation. Due to this trick,
|
* calculate text position of the middle state by linear interpolation. Due to this trick, the
|
||||||
* the text positions of the middle state is likely different from the text shaper result.
|
* text positions of the middle state is likely different from the text shaper result. So, if
|
||||||
* So, if you want to start animation from the middle state, you will see the glyph jumps due to
|
* you want to start animation from the middle state, you will see the glyph jumps due to this
|
||||||
* this trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different
|
* trick, i.e. the progress 0.5 of interpolation between weight 400 and 700 is different from
|
||||||
* from text shape result of weight 550.
|
* text shape result of weight 550.
|
||||||
*
|
*
|
||||||
* After calling this method, do not call onBasePaintModified() since it reshape the text and
|
* After calling this method, do not call onBasePaintModified() since it reshape the text and
|
||||||
* update the base state. As in above notice, the text shaping result at current progress is
|
* update the base state. As in above notice, the text shaping result at current progress is
|
||||||
@@ -171,8 +162,8 @@ class TextInterpolator(
|
|||||||
* animate weight from 200 to 400, then if you want to move back to 200 at the half of the
|
* animate weight from 200 to 400, then if you want to move back to 200 at the half of the
|
||||||
* animation, it will look like
|
* animation, it will look like
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre> <code>
|
||||||
* <code>
|
* ```
|
||||||
* val interp = TextInterpolator(layout)
|
* val interp = TextInterpolator(layout)
|
||||||
*
|
*
|
||||||
* // Interpolate between weight 200 to 400.
|
* // Interpolate between weight 200 to 400.
|
||||||
@@ -202,9 +193,8 @@ class TextInterpolator(
|
|||||||
* // progress is 0.5
|
* // progress is 0.5
|
||||||
* animator.start()
|
* animator.start()
|
||||||
* }
|
* }
|
||||||
* </code>
|
* ```
|
||||||
* </pre>
|
* </code> </pre>
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
fun rebase() {
|
fun rebase() {
|
||||||
if (progress == 0f) {
|
if (progress == 0f) {
|
||||||
@@ -266,69 +256,73 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxRunLength = 0
|
var maxRunLength = 0
|
||||||
lines = baseLayout.zip(targetLayout) { baseLine, targetLine ->
|
lines =
|
||||||
val runs = baseLine.zip(targetLine) { base, target ->
|
baseLayout.zip(targetLayout) { baseLine, targetLine ->
|
||||||
|
val runs =
|
||||||
require(base.glyphCount() == target.glyphCount()) {
|
baseLine.zip(targetLine) { base, target ->
|
||||||
"Inconsistent glyph count at line ${lines.size}"
|
require(base.glyphCount() == target.glyphCount()) {
|
||||||
}
|
"Inconsistent glyph count at line ${lines.size}"
|
||||||
|
|
||||||
val glyphCount = base.glyphCount()
|
|
||||||
|
|
||||||
// Good to recycle the array if the existing array can hold the new layout result.
|
|
||||||
val glyphIds = IntArray(glyphCount) {
|
|
||||||
base.getGlyphId(it).also { baseGlyphId ->
|
|
||||||
require(baseGlyphId == target.getGlyphId(it)) {
|
|
||||||
"Inconsistent glyph ID at $it in line ${lines.size}"
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
|
val glyphCount = base.glyphCount()
|
||||||
val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
|
|
||||||
val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
|
|
||||||
val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
|
|
||||||
|
|
||||||
// Calculate font runs
|
// Good to recycle the array if the existing array can hold the new layout
|
||||||
val fontRun = mutableListOf<FontRun>()
|
// result.
|
||||||
if (glyphCount != 0) {
|
val glyphIds =
|
||||||
var start = 0
|
IntArray(glyphCount) {
|
||||||
var baseFont = base.getFont(start)
|
base.getGlyphId(it).also { baseGlyphId ->
|
||||||
var targetFont = target.getFont(start)
|
require(baseGlyphId == target.getGlyphId(it)) {
|
||||||
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
|
"Inconsistent glyph ID at $it in line ${lines.size}"
|
||||||
"Cannot interpolate font at $start ($baseFont vs $targetFont)"
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 1 until glyphCount) {
|
|
||||||
val nextBaseFont = base.getFont(i)
|
|
||||||
val nextTargetFont = target.getFont(i)
|
|
||||||
|
|
||||||
if (baseFont !== nextBaseFont) {
|
|
||||||
require(targetFont !== nextTargetFont) {
|
|
||||||
"Base font has changed at $i but target font has not changed."
|
|
||||||
}
|
}
|
||||||
// Font transition point. push run and reset context.
|
|
||||||
fontRun.add(FontRun(start, i, baseFont, targetFont))
|
val baseX = FloatArray(glyphCount) { base.getGlyphX(it) }
|
||||||
maxRunLength = max(maxRunLength, i - start)
|
val baseY = FloatArray(glyphCount) { base.getGlyphY(it) }
|
||||||
baseFont = nextBaseFont
|
val targetX = FloatArray(glyphCount) { target.getGlyphX(it) }
|
||||||
targetFont = nextTargetFont
|
val targetY = FloatArray(glyphCount) { target.getGlyphY(it) }
|
||||||
start = i
|
|
||||||
|
// Calculate font runs
|
||||||
|
val fontRun = mutableListOf<FontRun>()
|
||||||
|
if (glyphCount != 0) {
|
||||||
|
var start = 0
|
||||||
|
var baseFont = base.getFont(start)
|
||||||
|
var targetFont = target.getFont(start)
|
||||||
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
|
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
|
||||||
"Cannot interpolate font at $start ($baseFont vs $targetFont)"
|
"Cannot interpolate font at $start ($baseFont vs $targetFont)"
|
||||||
}
|
}
|
||||||
} else { // baseFont === nextBaseFont
|
|
||||||
require(targetFont === nextTargetFont) {
|
for (i in 1 until glyphCount) {
|
||||||
"Base font has not changed at $i but target font has changed."
|
val nextBaseFont = base.getFont(i)
|
||||||
|
val nextTargetFont = target.getFont(i)
|
||||||
|
|
||||||
|
if (baseFont !== nextBaseFont) {
|
||||||
|
require(targetFont !== nextTargetFont) {
|
||||||
|
"Base font has changed at $i but target font is unchanged."
|
||||||
|
}
|
||||||
|
// Font transition point. push run and reset context.
|
||||||
|
fontRun.add(FontRun(start, i, baseFont, targetFont))
|
||||||
|
maxRunLength = max(maxRunLength, i - start)
|
||||||
|
baseFont = nextBaseFont
|
||||||
|
targetFont = nextTargetFont
|
||||||
|
start = i
|
||||||
|
require(FontInterpolator.canInterpolate(baseFont, targetFont)) {
|
||||||
|
"Cannot interpolate font at $start" +
|
||||||
|
" ($baseFont vs $targetFont)"
|
||||||
|
}
|
||||||
|
} else { // baseFont === nextBaseFont
|
||||||
|
require(targetFont === nextTargetFont) {
|
||||||
|
"Base font is unchanged at $i but target font has changed."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
|
||||||
|
maxRunLength = max(maxRunLength, glyphCount - start)
|
||||||
}
|
}
|
||||||
|
Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
|
||||||
}
|
}
|
||||||
fontRun.add(FontRun(start, glyphCount, baseFont, targetFont))
|
Line(runs)
|
||||||
maxRunLength = max(maxRunLength, glyphCount - start)
|
|
||||||
}
|
|
||||||
Run(glyphIds, baseX, baseY, targetX, targetY, fontRun)
|
|
||||||
}
|
}
|
||||||
Line(runs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update float array used for drawing.
|
// Update float array used for drawing.
|
||||||
if (tmpPositionArray.size < maxRunLength * 2) {
|
if (tmpPositionArray.size < maxRunLength * 2) {
|
||||||
@@ -360,9 +354,9 @@ class TextInterpolator(
|
|||||||
if (glyphFilter == null) {
|
if (glyphFilter == null) {
|
||||||
for (i in run.start until run.end) {
|
for (i in run.start until run.end) {
|
||||||
tmpPositionArray[arrayIndex++] =
|
tmpPositionArray[arrayIndex++] =
|
||||||
MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
|
MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
|
||||||
tmpPositionArray[arrayIndex++] =
|
tmpPositionArray[arrayIndex++] =
|
||||||
MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
|
MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
|
||||||
}
|
}
|
||||||
c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
|
c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
|
||||||
return
|
return
|
||||||
@@ -391,13 +385,14 @@ class TextInterpolator(
|
|||||||
tmpPaintForGlyph.color = tmpGlyph.color
|
tmpPaintForGlyph.color = tmpGlyph.color
|
||||||
|
|
||||||
c.drawGlyphs(
|
c.drawGlyphs(
|
||||||
line.glyphIds,
|
line.glyphIds,
|
||||||
prevStart,
|
prevStart,
|
||||||
tmpPositionArray,
|
tmpPositionArray,
|
||||||
0,
|
0,
|
||||||
i - prevStart,
|
i - prevStart,
|
||||||
font,
|
font,
|
||||||
tmpPaintForGlyph)
|
tmpPaintForGlyph
|
||||||
|
)
|
||||||
prevStart = i
|
prevStart = i
|
||||||
arrayIndex = 0
|
arrayIndex = 0
|
||||||
}
|
}
|
||||||
@@ -407,13 +402,14 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.drawGlyphs(
|
c.drawGlyphs(
|
||||||
line.glyphIds,
|
line.glyphIds,
|
||||||
prevStart,
|
prevStart,
|
||||||
tmpPositionArray,
|
tmpPositionArray,
|
||||||
0,
|
0,
|
||||||
run.end - prevStart,
|
run.end - prevStart,
|
||||||
font,
|
font,
|
||||||
tmpPaintForGlyph)
|
tmpPaintForGlyph
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updatePositionsAndFonts(
|
private fun updatePositionsAndFonts(
|
||||||
@@ -421,9 +417,7 @@ class TextInterpolator(
|
|||||||
updateBase: Boolean
|
updateBase: Boolean
|
||||||
) {
|
) {
|
||||||
// Update target positions with newly calculated text layout.
|
// Update target positions with newly calculated text layout.
|
||||||
check(layoutResult.size == lines.size) {
|
check(layoutResult.size == lines.size) { "The new layout result has different line count." }
|
||||||
"The new layout result has different line count."
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.zip(layoutResult) { line, runs ->
|
lines.zip(layoutResult) { line, runs ->
|
||||||
line.runs.zip(runs) { lineRun, newGlyphs ->
|
line.runs.zip(runs) { lineRun, newGlyphs ->
|
||||||
@@ -439,7 +433,7 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
require(newFont === newGlyphs.getFont(i)) {
|
require(newFont === newGlyphs.getFont(i)) {
|
||||||
"The new layout has different font run." +
|
"The new layout has different font run." +
|
||||||
" $newFont vs ${newGlyphs.getFont(i)} at $i"
|
" $newFont vs ${newGlyphs.getFont(i)} at $i"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +441,7 @@ class TextInterpolator(
|
|||||||
// check new font can be interpolatable with base font.
|
// check new font can be interpolatable with base font.
|
||||||
require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
|
require(FontInterpolator.canInterpolate(newFont, run.baseFont)) {
|
||||||
"New font cannot be interpolated with existing font. $newFont," +
|
"New font cannot be interpolated with existing font. $newFont," +
|
||||||
" ${run.baseFont}"
|
" ${run.baseFont}"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateBase) {
|
if (updateBase) {
|
||||||
@@ -483,10 +477,7 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Shape the text and stores the result to out argument.
|
// Shape the text and stores the result to out argument.
|
||||||
private fun shapeText(
|
private fun shapeText(layout: Layout, paint: TextPaint): List<List<PositionedGlyphs>> {
|
||||||
layout: Layout,
|
|
||||||
paint: TextPaint
|
|
||||||
): List<List<PositionedGlyphs>> {
|
|
||||||
var text = StringBuilder()
|
var text = StringBuilder()
|
||||||
val out = mutableListOf<List<PositionedGlyphs>>()
|
val out = mutableListOf<List<PositionedGlyphs>>()
|
||||||
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
|
for (lineNo in 0 until layout.lineCount) { // Shape all lines.
|
||||||
@@ -500,10 +491,13 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val runs = mutableListOf<PositionedGlyphs>()
|
val runs = mutableListOf<PositionedGlyphs>()
|
||||||
TextShaper.shapeText(layout.text, lineStart, count, layout.textDirectionHeuristic,
|
TextShaper.shapeText(
|
||||||
paint) { _, _, glyphs, _ ->
|
layout.text,
|
||||||
runs.add(glyphs)
|
lineStart,
|
||||||
}
|
count,
|
||||||
|
layout.textDirectionHeuristic,
|
||||||
|
paint
|
||||||
|
) { _, _, glyphs, _ -> runs.add(glyphs) }
|
||||||
out.add(runs)
|
out.add(runs)
|
||||||
|
|
||||||
if (lineNo > 0) {
|
if (lineNo > 0) {
|
||||||
@@ -517,8 +511,8 @@ class TextInterpolator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Layout.getDrawOrigin(lineNo: Int) =
|
private fun Layout.getDrawOrigin(lineNo: Int) =
|
||||||
if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
|
if (getParagraphDirection(lineNo) == Layout.DIR_LEFT_TO_RIGHT) {
|
||||||
getLineLeft(lineNo)
|
getLineLeft(lineNo)
|
||||||
} else {
|
} else {
|
||||||
getLineRight(lineNo)
|
getLineRight(lineNo)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
+packages/SystemUI
|
+packages/SystemUI
|
||||||
-packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
|
|
||||||
-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
|
-packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
|
||||||
-packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
|
|
||||||
-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
|
-packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
|
||||||
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
|
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BindServiceViaContextDetector.kt
|
||||||
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
|
-packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt
|
||||||
|
|||||||
Reference in New Issue
Block a user