TextShaper
public
class
TextShaper
extends Object
java.lang.Object | |
↳ | android.text.TextShaper |
Provides text shaping for multi-styled text. Here is an example of animating text size and letter spacing for simple text.
// 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)
}
See also:
Summary
Nested classes | |
---|---|
interface |
TextShaper.GlyphsConsumer
A consumer interface for accepting text shape result. |
Public methods | |
---|---|
static
void
|
shapeText(CharSequence text, int start, int count, TextDirectionHeuristic dir, TextPaint paint, TextShaper.GlyphsConsumer consumer)
Shape multi-styled text. |
Inherited methods | |
---|---|
Public methods
shapeText
public static void shapeText (CharSequence text, int start, int count, TextDirectionHeuristic dir, TextPaint paint, TextShaper.GlyphsConsumer consumer)
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 . |
Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.
Last updated 2024-04-11 UTC.