Draw an image over the Android text span

I'm creating a complex text view, which means using different text styles in the same view. Some text needs a small image above it. But the text should still exist (not just replace), so a simple imagespan won't. I can't use the collection of textviews because I need to wrap the text (or I'm wrong, can I use textviews?)

I tried to combine two spans on the same character, but although it can be used to stylize text, it does not apply to imagespan

What am I going to do:

Any ideas?

read: http://flavienlaurent.com/blog/2014/01/31/spans/ Helped a lot, but I'm still not there

resolvent:

After reading the excellent articles you quoted, traversing the Android source code and coding a lot of log. D (), I finally figured out what you need. Are you ready- Subclass of replacementspan

Replacementspan is counterintuitive to your situation because you are not replacing text, but drawing something else. However, it has proved that replacementspan meets two things you need: a hook for determining the row height of a graph and a hook for drawing a graph. Therefore, you will also draw text in it, because superclasses will not do so

I've always been interested in learning more about span and text layout, so I started a demonstration project

I put forward two different ideas for you. In the first lesson, you have an icon that can be accessed as drawable. You pass drawable on the constructor. Then, use the drawable size to help adjust the line height. The advantage of this is that the drawable size has been adjusted for the display density of the device

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

The second idea is better if you want to use very simple shapes for graphics. You can define paths for shapes and then render only paths. Now, you must consider display density, and for simplicity, I only choose from constructor parameters

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

This is how I use these classes in my main activities:

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

Where? Now we have both learned something

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>