使用ImageSpan,图片不能与文字对齐问题

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">&nbsp;%2$s<font color="#999999">(¥%3$s)</font>]]></string>

如果不设置android:lineSpacingExtra属性时,能很好的显示,但是如果设置了该属性后,图片会下移,占用行间距:如图

这一点很烦人,没办法只好先究其原因。发现,Html.fromHtml会一步步解析给定的在String里面包含的标签,如标签会最终生成一个ImageSpan,传递一个TextView,最红会调用ImageSpandraw()方法,这个方法在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代码,主要修改器父类DynamicDrawableSpandraw

@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);
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值