问题
I'm trying to add red wavy line below errors in texts, such as:

Unfortunately I can't find a proper *Span class to wrap the error text with.
How should I implement such a feature in Android?
回答1:
I've solved the problem by implementing a custom Span:
Add error_underline.png
to your resources:

Then use this class to create spans:
static class ErrorSpan extends DynamicDrawableSpan {
private BitmapDrawable mRedWavy;
private int mWidth;
private int mBmpHeight;
ErrorSpan(Resources resources) {
super(DynamicDrawableSpan.ALIGN_BASELINE);
mRedWavy = new BitmapDrawable(resources, BitmapFactory.decodeResource(resources, R.drawable.error_underline));
mBmpHeight = mRedWavy.getIntrinsicHeight();
mRedWavy.setTileModeX(TileMode.REPEAT);
}
@Override
public Drawable getDrawable() {
return mRedWavy;
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
mWidth = (int) paint.measureText(text, start, end);
return mWidth;
}
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
mRedWavy.setBounds(0, 0, mWidth, mBmpHeight);
canvas.save();
canvas.translate(x, bottom-mBmpHeight);
mRedWavy.draw(canvas);
canvas.restore();
canvas.drawText(text.subSequence(start, end).toString(), x, y, paint);
}
}
回答2:
Here is another solution without touching resources:
public class WavyUnderlineSpan implements LineBackgroundSpan {
private int color;
private int lineWidth;
private int waveSize;
public WavyUnderlineSpan() {
this(Color.RED, 1, 3);
}
public WavyUnderlineSpan(int color, int lineWidth, int waveSize) {
this.color = color;
this.lineWidth = lineWidth;
this.waveSize = waveSize;
}
@Override
public void drawBackground(Canvas canvas, Paint paint, int left, int right, int top, int baseline, int bottom,
CharSequence text, int start, int end, int lnum) {
Paint p = new Paint(paint);
p.setColor(color);
p.setStrokeWidth(lineWidth);
int width = (int) paint.measureText(text, start, end);
int doubleWaveSize = waveSize * 2;
for (int i = left; i < left + width; i += doubleWaveSize) {
canvas.drawLine(i, bottom, i + waveSize, bottom - waveSize, p);
canvas.drawLine(i + waveSize, bottom - waveSize, i + doubleWaveSize, bottom, p);
}
}
}
回答3:
@bpronin's code did not work for me - the span was too small on high resolution screen and it spanned the entire text, not only the error spanned.
But following his idea I updated my answer to remove the need for added resource:
public class ErrorSpan extends DynamicDrawableSpan {
private int width;
int lineWidth;
int waveSize;
int color;
public ErrorSpan(Resources resources) {
this(resources, Color.RED, 1, 3);
}
public ErrorSpan(Resources resources, int color, int lineWidth, int waveSize) {
super(DynamicDrawableSpan.ALIGN_BASELINE);
// Get the screen's density scale
final float scale = resources.getDisplayMetrics().density;
// Convert the dps to pixels, based on density scale
this.lineWidth = (int) (lineWidth * scale + 0.5f);
this.waveSize = (int) (waveSize * scale + 0.5f);
this.color = color;
}
@Override
public Drawable getDrawable() {
return null;
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
width = (int) paint.measureText(text, start, end);
return width;
}
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Paint p = new Paint(paint);
p.setColor(color);
p.setStrokeWidth(lineWidth);
int doubleWaveSize = waveSize * 2;
for (int i = (int)x; i < x + width; i += doubleWaveSize) {
canvas.drawLine(i, bottom, i + waveSize, bottom - waveSize, p);
canvas.drawLine(i + waveSize, bottom - waveSize, i + doubleWaveSize, bottom, p);
}
canvas.drawText(text.subSequence(start, end).toString(), x, y, paint);
}
}
来源:https://stackoverflow.com/questions/27267254/how-to-add-red-wavy-line-below-text-in-androids-textview