在android文本跨度上方绘制图像 [英] Drawing an image above an android text span

查看:73
本文介绍了在android文本跨度上方绘制图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在创建一个复杂的文本视图,这意味着在同一视图中使用不同的文本样式.一些文本需要在其上方有一个小图像.但文本仍应存在(而不是替换),因此简单的ImageSpan不会.我不能使用TextViews的集合,因为我需要包装文本(或者我错了,可以用TextViews完成吗?).

I am creating a complex text view, meaning different text styles in the same view. some of the text needs to have a small image just above it. but the text should still be there (not just replaced) so a simple ImageSpan will not do. I can't use a collection of TextViews because I need the text to wrap(or Am I wrong and this can be done with TextViews?).

我尝试将两个跨度合并到同一字符上,但是虽然可以用于样式化文本,但不适用于ImageSpan.

I tried to combine two spans over the same characters but while that works for styling the text it does not for the ImageSpan.

我要做什么:

有什么想法吗?

阅读此博客文章: http://old.flavienlaurent.com/blog/2014/01/31/spans/帮助很大,但我仍然不在那儿.

Reading this blog post: http://old.flavienlaurent.com/blog/2014/01/31/spans/ Helped a lot but i'm still not there.

推荐答案

在阅读了您引用的出色文章之后,仔细研究了Android源代码,并对很多 Log.d() s进行了编码,我终于弄清楚了您需要的是什么-您准备好了吗?-一个 ReplacementSpan 子类.

After reading the excellent article you referenced, poring over Android source code, and coding lots of Log.d()s, I finally figured out what you need and it is -- are you ready? -- a ReplacementSpan subclass.

ReplacementSpan 对您的情况而言是违反直觉的,因为您替换了文本,这是在绘制一些其他内容.但是事实证明, ReplacementSpan 是为您提供所需的两件事:用于确定图形线条高度的钩子和用于绘制图形的钩子.因此,您也将在其中绘制文本,因为超类不会这样做.

ReplacementSpan is counter-intuitive for your case because you aren't replacing the text, you're drawing some additional stuff. But it turns out that ReplacementSpan is what gives you the two things you need: the hook to size the line height for your graphic and the hook to draw your graphic. So you'll just draw the text in there too, since the superclass isn't going to do it.

我一直有兴趣了解有关跨度和文本布局的更多信息,所以我开始了一个演示项目.

I've been interested in learning more about spans and text layout, so I started a demo project to play with.

我为您提出了两种不同的想法.在第一堂课中,您有一个图标,可以作为 Drawable 访问.您在构造函数上传递 Drawable .然后,使用 Drawable 的尺寸来帮助调整行高.这样做的好处是,已经针对设备的显示密度调整了 Drawable 的尺寸.

I came up with two different ideas for you. In the first class, you have an icon that you can access as a Drawable. You pass the Drawable in on the constructor. Then you use the Drawable's dimensions to help size your line height. A benefit here is that the Drawable's dimensions have already been adjusted for the device's display density.

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.text.style.ReplacementSpan;
import android.util.Log;

public class IconOverSpan extends ReplacementSpan {

    private static final String TAG = "IconOverSpan";

    private Drawable mIcon;

    public IconOverSpan(Drawable icon) {
        mIcon = icon;
        Log.d(TAG, "<ctor>, icon intrinsic dimensions: " + icon.getIntrinsicWidth() + " x " + icon.getIntrinsicHeight());
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {

        /*
         * This method is where we make room for the drawing.
         * We are passed in a FontMetrics that we can check to see if there is enough space.
         * If we need to, we can alter these FontMetrics to suit our needs.
         */
        if (fm != null) {  // test for null because sometimes fm isn't passed in
            /*
             * Everything is measured from the baseline, so the ascent is a negative number,
             * and the top is an even more negative number.  We are going to make sure that
             * there is enough room between the top and the ascent line for the graphic.
             */
            int h = mIcon.getIntrinsicHeight();
            if (- fm.top + fm.ascent < h) {
                // if there is not enough room, "raise" the top
                fm.top = fm.ascent - h;
            }
        }

        /*
         * the number returned is actually the width of the span.
         * you will want to make sure the span is wide enough for your graphic.
         */
        int textWidth = (int) Math.ceil(paint.measureText(text, start, end));
        int w = mIcon.getIntrinsicWidth();
        Log.d(TAG, "getSize(), returning " + textWidth + ", fm = " + fm);
        return Math.max(textWidth, w);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        Log.d(TAG, "draw(), x = " + x + ", top = " + top + ", y = " + y + ", bottom = " + bottom);

        // first thing we do is draw the text that is not drawn because it is being "replaced"
        // you may have to adjust x if the graphic is wider and you want to center-align
        canvas.drawText(text, start, end, x, y, paint);

        // Set the bounds on the drawable.  If bouinds aren't set, drawable won't render at all
        // we set the bounds relative to upper left corner of the span
        mIcon.setBounds((int) x, top, (int) x + mIcon.getIntrinsicWidth(), top + mIcon.getIntrinsicHeight());
        mIcon.draw(canvas);
    }
}

如果您要对图形使用非常简单的形状,则第二个想法更好.您可以为形状定义 Path ,然后仅渲染 Path .现在,您必须考虑显示密度,并且为了简便起见,我仅从构造函数参数中进行选择.

The second idea is better if you are going to use really simple shapes for your graphics. You can define a Path for your shape and then just render the Path. Now you have to take display density into account, and to make it easy I just take it from a constructor parameter.

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.text.style.ReplacementSpan;
import android.util.Log;

public class PathOverSpan extends ReplacementSpan {

    private static final String TAG = "PathOverSpan";

    private float mDensity;

    private Path mPath;

    private int mWidth;

    private int mHeight;

    private Paint mPaint;

    public PathOverSpan(float density) {

        mDensity = density;
        mPath = new Path();
        mWidth = (int) Math.ceil(16 * mDensity);
        mHeight = (int) Math.ceil(16 * mDensity);
        // we will make a small triangle
        mPath.moveTo(mWidth/2, 0);
        mPath.lineTo(mWidth, mHeight);
        mPath.lineTo(0, mHeight);
        mPath.close();

        /*
         * set up a paint for our shape.
         * The important things are the color and style = fill
         */
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {

        /*
         * This method is where we make room for the drawing.
         * We are passed in a FontMetrics that we can check to see if there is enough space.
         * If we need to, we can alter these FontMetrics to suit our needs.
         */
        if (fm != null) {
            /*
             * Everything is measured from the baseline, so the ascent is a negative number,
             * and the top is an even more negative number.  We are going to make sure that
             * there is enough room between the top and the ascent line for the graphic.
             */
            if (- fm.top + fm.ascent < mHeight) {
                // if there is not enough room, "raise" the top
                fm.top = fm.ascent - mHeight;
            }
        }

        /*
         * the number returned is actually the width of the span.
         * you will want to make sure the span is wide enough for your graphic.
         */
        int textWidth = (int) Math.ceil(paint.measureText(text, start, end));
        return Math.max(textWidth, mWidth);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        Log.d(TAG, "draw(), x = " + x + ", top = " + top + ", y = " + y + ", bottom = " + bottom);

        // first thing we do is draw the text that is not drawn because it is being "replaced"
        // you may have to adjust x if the graphic is wider and you want to center-align
        canvas.drawText(text, start, end, x, y, paint);

        // calculate an offset to center the shape
        int textWidth = (int) Math.ceil(paint.measureText(text, start, end));
        int offset = 0;
        if (textWidth > mWidth) {
            offset = (textWidth - mWidth) / 2;
        }

        // we set the bounds relative to upper left corner of the span
        canvas.translate(x + offset, top);
        canvas.drawPath(mPath, mPaint);
        canvas.translate(-x - offset, -top);
    }
}

这是我在主要活动中使用这些类的方式:

Here's how I used these classes in the main activity:

    SpannableString spannableString = new SpannableString("Some text and it can have an icon over it");
    UnderlineSpan underlineSpan = new UnderlineSpan();
    IconOverSpan iconOverSpan = new IconOverSpan(getResources().getDrawable(R.drawable.ic_star));
    PathOverSpan pathOverSpan = new PathOverSpan(getResources().getDisplayMetrics().density);
    spannableString.setSpan(underlineSpan, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    spannableString.setSpan(iconOverSpan, 21, 25, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    spannableString.setSpan(pathOverSpan, 29, 38, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

    TextView textView = (TextView) findViewById(R.id.textView);
    textView.setText(spannableString);

那里!现在我们都学到了一些东西.

There! Now we both learned something.

这篇关于在android文本跨度上方绘制图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆