Added in API level 31
TextShaper
open class TextShaper
kotlin.Any | |
↳ | android.text.TextShaper |
Provides text shaping for multi-styled text. Here is an example of animating text size and letter spacing for simple text.
<code> // In this example, shape the text once for start and end state, then animate between two shape // result without re-shaping in each frame. class SimpleAnimationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { private val textDir = TextDirectionHeuristics.LOCALE private val text = "Hello, World." // The text to be displayed // Class for keeping drawing parameters. data class DrawStyle(val textSize: Float, val alpha: Int) // The start and end text shaping result. This class will animate between these two. private val start = mutableListOf<Pair<PositionedGlyphs, DrawStyle>>() private val end = mutableListOf<Pair<PositionedGlyphs, DrawStyle>>() init { val startPaint = TextPaint().apply { alpha = 0 // Alpha only affect text drawing but not text shaping textSize = 36f // TextSize affect both text shaping and drawing. letterSpacing = 0f // Letter spacing only affect text shaping but not drawing. } val endPaint = TextPaint().apply { alpha = 255 textSize =128f letterSpacing = 0.1f } TextShaper.shapeText(text, 0, text.length, textDir, startPaint) { _, _, glyphs, paint -> start.add(Pair(glyphs, DrawStyle(paint.textSize, paint.alpha))) } TextShaper.shapeText(text, 0, text.length, textDir, endPaint) { _, _, glyphs, paint -> end.add(Pair(glyphs, DrawStyle(paint.textSize, paint.alpha))) } } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) // Set the baseline to the vertical center of the view. canvas.translate(0f, height / 2f) // Assume the number of PositionedGlyphs are the same. If different, you may want to // animate in a different way, e.g. cross fading. start.zip(end) { (startGlyphs, startDrawStyle), (endGlyphs, endDrawStyle) -> // Tween the style and set to paint. paint.textSize = lerp(startDrawStyle.textSize, endDrawStyle.textSize, progress) paint.alpha = lerp(startDrawStyle.alpha, endDrawStyle.alpha, progress) // Assume the number of glyphs are the same. If different, you may want to animate in // a different way, e.g. cross fading. require(startGlyphs.glyphCount() == endGlyphs.glyphCount()) if (startGlyphs.glyphCount() == 0) return@zip var curFont = startGlyphs.getFont(0) var drawStart = 0 for (i in 1 until startGlyphs.glyphCount()) { // Assume the pair of Glyph ID and font is the same. If different, you may want // to animate in a different way, e.g. cross fading. require(startGlyphs.getGlyphId(i) == endGlyphs.getGlyphId(i)) require(startGlyphs.getFont(i) === endGlyphs.getFont(i)) val font = startGlyphs.getFont(i) if (curFont != font) { drawGlyphs(canvas, startGlyphs, endGlyphs, drawStart, i, curFont, paint) curFont = font drawStart = i } } if (drawStart != startGlyphs.glyphCount() - 1) { drawGlyphs(canvas, startGlyphs, endGlyphs, drawStart, startGlyphs.glyphCount(), curFont, paint) } } } // Draws Glyphs for the same font run. private fun drawGlyphs(canvas: Canvas, startGlyph: PositionedGlyphs, endGlyph: PositionedGlyphs, start: Int, end: Int, font: Font, paint: Paint) { var cacheIndex = 0 for (i in start until end) { intArrayCache[cacheIndex] = startGlyph.getGlyphId(i) // The glyph positions are different from start to end since they are shaped // with different letter spacing. Use linear interpolation for positions // during animation. floatArrayCache[cacheIndex * 2] = lerp(startGlyph.getGlyphX(i), endGlyph.getGlyphX(i), progress) floatArrayCache[cacheIndex * 2 + 1] = lerp(startGlyph.getGlyphY(i), endGlyph.getGlyphY(i), progress) if (cacheIndex == CACHE_SIZE) { // Cached int array is full. Flashing. canvas.drawGlyphs( intArrayCache, 0, // glyphID array and its starting offset floatArrayCache, 0, // position array and its starting offset cacheIndex, // glyph count font, paint ) cacheIndex = 0 } cacheIndex++ } if (cacheIndex != 0) { canvas.drawGlyphs( intArrayCache, 0, // glyphID array and its starting offset floatArrayCache, 0, // position array and its starting offset cacheIndex, // glyph count font, paint ) } } // Linear Interpolator private fun lerp(start: Float, end: Float, t: Float) = start * (1f - t) + end * t private fun lerp(start: Int, end: Int, t: Float) = (start * (1f - t) + end * t).toInt() // The animation progress. var progress: Float = 0f set(value) { field = value invalidate() } // working copy of paint. private val paint = Paint() // Array cache for reducing allocation during drawing. private var intArrayCache = IntArray(CACHE_SIZE) private var floatArrayCache = FloatArray(CACHE_SIZE * 2) } </code>
Summary
Nested classes | |
---|---|
abstract |
A consumer interface for accepting text shape result. |
Public methods | |
---|---|
open static Unit |
shapeText(text: CharSequence, start: Int, count: Int, dir: TextDirectionHeuristic, paint: TextPaint, consumer: TextShaper.GlyphsConsumer) Shape multi-styled text. |
Public methods
shapeText
Added in API level 31
open static fun shapeText(
text: CharSequence,
start: Int,
count: Int,
dir: TextDirectionHeuristic,
paint: TextPaint,
consumer: TextShaper.GlyphsConsumer
): Unit
Shape multi-styled text. In the LTR context, the shape result will go from left to right, thus you may want to draw glyphs from left most position of the canvas. In the RTL context, the shape result will go from right to left, thus you may want to draw glyphs from right most position of the canvas.
Parameters | |
---|---|
text |
CharSequence: a styled text. This value cannot be null . |
start |
Int: a start index of shaping target in the text. Value is 0 or greater |
count |
Int: a length of shaping target in the text. Value is 0 or greater |
dir |
TextDirectionHeuristic: a text direction. This value cannot be null . |
paint |
TextPaint: a paint This value cannot be null . |
consumer |
TextShaper.GlyphsConsumer: a consumer of the shape result. This value cannot be null . |