Android使用ImageSpan,图片不能与文字对齐问题
实现如下产品需求
- 1. 图片与文字中间对齐
- 2. 文字不能超过三行,多余使用…,但是保持价格可见。
实现功能一:图片与文字中间对齐
看到设计图就想到使用TextView.drawableLeft
属性,加上Html
,发现图片居中于全部文字,而不是居中与第一行文字。后面采用Html.img
,代码如下:
String text = String.format(Locale.CHINA, getResources().getString(R.string.qa_channel_ques_str), imageHtml, this.quesStr, price);
setText(Html.fromHtml(text, getQuestionImageGetter(), null));
private Html.ImageGetter getQuestionImageGetter(){
final int textSize = (int) getTextSize();
return new Html.ImageGetter() {
public Drawable getDrawable(String source) {
InsetDrawable insetDrawable = new InsetDrawable(mIconDrawable, 0, 0, (int) dipRight, (int) dipBottom);
insetDrawable.setBounds(0, 0, textSize, textSize);
return insetDrawable;
}
};
}
<string name="qa_channel_ques_str"><![CDATA[<src src="%1$s"> %2$s<font color="#999999">(¥%3$s)</font>]]></string>
如果不设置android:lineSpacingExtra
属性时,能很好的显示,但是如果设置了该属性后,图片会下移,占用行间距:如图
这一点很烦人,没办法只好先究其原因。发现,Html.fromHtml
会一步步解析给定的在String
里面包含的标签,如标签会最终生成一个ImageSpan
,传递一个TextView
,最红会调用ImageSpan
的draw()
方法,这个方法在DynamicDrawableSpan
实现
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
//计算画笔移动距离
int transY = bottom - b.getBounds().bottom;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
//移动画笔
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
可以看到就是应为移动了画笔,所以图片才会不与上面的文字对齐,想办法自己重新一个就可以了。但是如果使用Html.fromHtml
这个ImageSpan
是在`Html
自己生成的,如法传入自定义的ImageSpan
,但是发现方法public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler)
可以传入自定义的TagHandler
private class ImageHandler implements Html.TagHandler {
private boolean isFirst = false;
private int textSize;
ImageHandler(float textSize) {
this.textSize = (int) textSize;
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (!"drawable".equalsIgnoreCase(tag) || tag == null || isFirst) {
return;
}
InsetDrawable insetDrawable = new InsetDrawable(mIconDrawable, 0, 0, (int) dipRight, (int) dipBottom);
insetDrawable.setBounds(0, 0, textSize, textSize);
isFirst = true;
int len = output.length();
output.append("\uFFFC");
//设置自定义ImageSpan
output.setSpan(new MyImageSpan(insetDrawable), len, output.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
MyImageSpan
这个直接拷贝ImageSpan
代码,主要修改器父类DynamicDrawableSpan
的draw
@Override
public void draw(Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
//与descent线对齐
canvas.translate(x, paint.getFontMetricsInt().descent);
b.draw(canvas);
canvas.restore();
}
最终调用
public void setTextStr(String quesStr, String price) {
if (quesStr == null || price == null) {
return;
}
priceWordCount = price.length() + PRICE_EXTRA_WORD_COUNT;
this.quesStr = quesStr;
this.quesPriceStr = price;
String text = String.format(Locale.CHINA, getResources()
.getString(R.string.qa_channel_ques_str), "", this.quesStr, price);
setText(Html.fromHtml(text, null, new ImageHandler(getTextSize())));
}
效果如下:
实现功能二:文字不能超过三行,多余使用…,但是保持价格可见
上图可以看到超过三行之后,在问题的后面是显示了...
,但是价格依然在那里。这又是如何做到的的呢?
基本思路是:先让TextView
自己测量,在绘制之前,判断是否超过三行,如果超过则获取三行最后一个字符的位置,然后减去价格字符窜的字数,然后重新调用setText()
方法。
@Override
protected void onDraw(Canvas canvas) {
if (getLineCount() > MAX_LINE) {
//获取指定行数的最后一个字的位置
int endLineNum = getLayout().getLineEnd(MAX_LINE - 1) - priceWordCount;
if (endLineNum > 0) {
this.quesStr = this.quesStr.substring(0, endLineNum) + "...";
setTextStr(quesStr, quesPriceStr);
}
//下次如果还是超过指定行数,增加价格字数,知道不超过三行
PRICE_EXTRA_WORD_COUNT++;
return;
}
super.onDraw(canvas);
}
完整TextView
:代码:
public class QAQuestionView extends AppCompatTextView {
protected final int MAX_LINE = 3;
protected int PRICE_EXTRA_WORD_COUNT = 3;
private int priceWordCount = 0;
private String quesStr;
private String quesPriceStr;
private Drawable mIconDrawable;
public QAQuestionView(Context context) {
this(context, null);
}
public QAQuestionView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public QAQuestionView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mIconDrawable = getResources().getDrawable(R.drawable.icon_ask_blue);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onDraw(Canvas canvas) {
if (getLineCount() > MAX_LINE) {
//获取指定行数的最后一个字的位置
int endLineNum = getLayout().getLineEnd(MAX_LINE - 1) - priceWordCount;
if (endLineNum > 0) {
this.quesStr = this.quesStr.substring(0, endLineNum) + "...";
setTextStr(quesStr, quesPriceStr);
}
//下次如果还是超过指定行数,增加价格字数,知道不超过三行
PRICE_EXTRA_WORD_COUNT++;
return;
}
super.onDraw(canvas);
}
public void setTextStr(String quesStr, String price) {
if (quesStr == null || price == null) {
return;
}
priceWordCount = price.length() + PRICE_EXTRA_WORD_COUNT;
this.quesStr = quesStr;
this.quesPriceStr = price;
String text = String.format(Locale.CHINA, getResources()
.getString(R.string.qa_channel_ques_str), "", this.quesStr, price);
setText(Html.fromHtml(text, null, new ImageHandler(getTextSize())));
}
private class ImageHandler implements Html.TagHandler {
private boolean isFirst = false;
private int textSize;
ImageHandler(float textSize) {
this.textSize = (int) textSize;
}
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
if (!"drawable".equalsIgnoreCase(tag) || tag == null || isFirst) {
return;
}
InsetDrawable insetDrawable = new InsetDrawable(mIconDrawable, 0, 0, 0, 0);
insetDrawable.setBounds(0, 0, textSize, textSize);
isFirst = true;
int len = output.length();
output.append("\uFFFC");
//设置自定义ImageSpan
output.setSpan(new MyImageSpan(insetDrawable), len, output.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}