preface
We now see ImageSpan almost every day in Android apps, such as App custom emojis and small ICONS in text.
What's the difference between an emoji in an input method and an emoji in an ImageSpan?
base
TextView
ImageSpan
ImageSpan
ImageSpan
1. ImageSpan comes from ReplacementSpan
ReplacementSpan
updateMeasureState
updateDrawState
/** * This method does nothing, since ReplacementSpans are measured * explicitly instead of affecting Paint properties. */
public void updateMeasureState(TextPaint p) {}/** * This method does nothing, since ReplacementSpans are drawn * explicitly instead of affecting Paint properties. */
public void updateDrawState(TextPaint ds) {}Copy the code
GetSize and Draw are added to determine the position of the picture and draw the picture content
/**
* Returns the width of the span. Extending classes can set the height of the span by updating
* attributes of {@link android.graphics.Paint.FontMetricsInt}. If the span covers the whole
* text, and the height is not set,
* {@link #draw(Canvas, CharSequence, int, int, float, int, int, int, Paint)} will not be
* called for the span.
*
* @param paint Paint instance.
* @param text Current text.
* @param start Start character index for span.
* @param end End character index for span.
* @param fm Font metrics, can be null.
* @return Width of the span.
*/
public abstract int getSize(@NonNull Paint paint, CharSequence text,
@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@Nullable Paint.FontMetricsInt fm);
/**
* Draws the span into the canvas.
*
* @param canvas Canvas into which the span should be rendered.
* @param text Current text.
* @param start Start character index for span.
* @param end End character index for span.
* @param x Edge of the replacement closest to the leading margin.
* @param top Top of the line.
* @param y Baseline.
* @param bottom Bottom of the line.
* @param paint Paint instance.
*/
public abstract void draw(@NonNull Canvas canvas, CharSequence text,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, float x,
int top, int y, int bottom, @NonNull Paint paint);
Copy the code
The arguments in getSize are explained clearly in the comments, but the FontMetricsInt is used to change the height. It will also change the layout of TextView and the baseline of drawing images. The layout of TextView will be discussed later. In addition to top, Ascent, Descent, Bottom and leading, you can refer to the following figure. For details, you need to search online. HenCoder tutorial: Custom draw View 1-3 drawText() text
draw
HenCoder
y
baseline
draw
ellipsize
DynamicDrawableSpan
ALIGN_BOTTOM
ALIGN_BASELINE
public abstract Drawable getDrawable();
DynamicDrawableSpan
ImageSpan
drawable
resouce id
uri
DynamicDrawableSpan
ImageSpan
ImageSpan
getSize
draw
What ImageSpan getSize and Draw do
FontMetricsInt, fm.top = fm.ascent = -drawable height; fm.bottom = fm.descent = 0; And returns the width of drawable. At first glance, this looks fine, but there are pits in this area, more on that later. The draw method handles the alignment, and the bottom-aligned transY = bottom-b.getbounds ().bottom; “And then move the canvas over to paint. TransY -= paint.getFontMetricsint ().descent; The reason is that the baseline is above bottom and below descent, so it is aligned with the baseline after subtracting descent. Note: “bottom alignment” refers to the bottom of the entire line of text, not the bottom of the text, so let’s try different alignment.
ALIGN_BOTTOM
ALIGN_BASELINE
baseline
draw
ImageSpan
draw
canvas.translate(x, top);
3. What about the center-aligned ImageSpan
Of course, what we really want to see is that the text is centered so it looks more comfortable. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Because that’s how you line up your text, and then figure out how much the image needs to be offset relative to it. So the question is, where is baseline? This is done using the FontMetricsInt in getSize, which sets the ascent and Descent of the image. The ascent and Descent are both referenced to the baseline, Start with baseline = 0, ascent < 0 at top, descent > 0 at bottom. From getSize implementation of DynamicDrawableSpan, it can be known that the baseline is coincided with descent, and to center the text is to center the ascent and descent of the text, so we know the baseline. You can determine the position of the image when it is centered with the text.
ascent
baseline
descent
ascent
top
transY
y
baseline
fm
FontMetrics
textHeight = fm.descent - fm.ascent
drawableHeight
baseline
descent
transY = y - fm.descent
offsetHeight = textHeight - drawableHeight
transY -= offsetHeight / 2
trasnY -= drawableHeight
transY = y + (fm.descent + fm.ascent) / 2 - drawableHeight / 2
y + (fm.descent + fm.ascent) / 2
drawableHeight / 2
Align center
While we were still basking in the beauty of "center alignment", the QA student suddenly raised a Bug: "Why is the bottom part of your image truncated?" "... What?"
harmonious
free
TextView
padding
padding
0
Align center
ImageSpan
TextView
baseline
getSize
TextView
TextView
onMeasure
Layout
StaticLayout
Span
TextView
Layout
Span
LineHeightSpan
bottom
Align center
getSize
TextView
draw
Align center
@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
// return super.getSize(paint, text, start, end, fm);
Drawable d = getDrawable();
Rect rect = d.getBounds();
float drawableHeight = rect.height();
Paint.FontMetrics paintFm = paint.getFontMetrics();
float textHeight = paintFm.descent - paintFm.ascent;
if(fm ! =null) {
float textCenter = (paintFm.descent + paintFm.ascent) / 2;
fm.ascent = fm.top = (int) (textCenter - drawableHeight / 2);
fm.descent = fm.bottom = (int) (drawableHeight + fm.ascent);
}
return rect.right;
}
Copy the code
Align center
ImageSpan
getSize
TextView
TexView
EditText
getSize
Bitmap
getDrawable
4, Ellipsis of Ellipsize is “eaten”
flat
ImageSpan
flat
Etc.
,
ImageSpan
ImageSpan
eat
TextView
Layout
getEllipsisStart
getEllipsisCount
draw
TextView
text
text
'. '
'... '
'\uFEFF'
BOM (Byte Order Mark)
Big-Endian
'\uFEFF'
Little-Endian
'\uFFFE'
draw
text = text.toString().replace("\uFEFF"."").replace("\uFFFE"."");
if (start >= text.length()) {
return;
}
Copy the code
To be safe, ‘\uFFFE’ is also replaced, so that anything beyond the ellipsis is not drawn again.
text
'... '
'... '
if (end > text.length()) {
end = text.length();
}
String subText = text.subSequence(start, end).toString();
if ("...".equals(subText)) {
canvas.drawText(text, start, end, x, y, paint);
return;
}
Copy the code
That’s OK!
The end of the
Long winded so much, but also the use of ImageSpan encountered problems clear, because there is no detailed study of TextView and its Layout source, there is a problem, please also criticize you. Attached below do experiment with the source, github.com/funnywolfda… . The next article will cover the ClickableSpan pit and my solution.