44

I'm trying to create a drawable on the fly to use as a background for a custom linearlayout. It needs to have hash marks and such (no big deal), but also have numbers labeling what the hash marks are (like a ruler). I know I can just create text elements and put them inside the linearlayout and just have the hash marks in the drawable, but I'm hoping to have them inside the drawable as well, so I don't have to do measurement calculations twice.

atraudes
  • 2,368
  • 2
  • 21
  • 31
  • 5
    10 years later, arriving at Android 11, this question and the most voted answers are still relevant, crazy! Can't believe Google hasn't added a TextDrawable by now. – 0101100101 Jul 31 '20 at 01:41

5 Answers5

103

Here is a brief example of a TextDrawable which works like a normal drawable but lets you specify text as the only constructor variable:

public class TextDrawable extends Drawable {

    private final String text;
    private final Paint paint;

    public TextDrawable(String text) {

        this.text = text;

        this.paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(22f);
        paint.setAntiAlias(true);
        paint.setFakeBoldText(true);
        paint.setShadowLayer(6f, 0, 0, Color.BLACK);
        paint.setStyle(Paint.Style.FILL);
        paint.setTextAlign(Paint.Align.LEFT);
    }

    @Override
    public void draw(Canvas canvas) {
        canvas.drawText(text, 0, 0, paint);
    }

    @Override
    public void setAlpha(int alpha) {
        paint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        paint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }
}
plowman
  • 13,335
  • 8
  • 53
  • 53
  • it works fine the only problem is text displayed wide.i am drawing text at (x,y) according to progress. where x = progress*seekBar.getWidth()/seekBar.getMax() and y = seekBar.getHeight()/2. can you please tell me where is my mistake to calculate the x,y co-ordinates. – Hiren Dabhi Mar 12 '12 at 12:11
  • ok it works perfect. that was my mistake. i have putted padding(left and right) of seek bar.Thanks plowman. – Hiren Dabhi Mar 12 '12 at 14:22
  • Any Idea for [How to display Text in Android Canvas ShapeDrawable with RectShape?](http://stackoverflow.com/questions/25401981/how-to-display-text-in-android-canvas-shapedrawable-with-rectshape) – LOG_TAG Aug 21 '14 at 05:21
  • Am trying to use type face in the paint object, but it didn't work. Any ideas? paint.setTypeface(Typeface.createFromAsset(context.getAssets(), "custom_font.ttf")); – Sathesh Jan 24 '15 at 07:45
  • @Sathesh make sure you have a font called "custom_font.ttf" in the assets folder of your project. – plowman Jan 26 '15 at 21:14
  • @Sathesh haha, okay... without seeing your code I can't guess what's wrong. If you post a gist I can take a quick look. – plowman Jan 27 '15 at 20:03
  • How do I put the text nicely in the center of the drawable? I remember it is a very known issue. – android developer Sep 07 '15 at 11:31
  • @android-developer I believe you use `paint.setTextAlign(Paint.Align.CENTER);` – plowman Sep 08 '15 at 00:11
  • @plowman As I said, I remember this is problematic, as the text font doesn't center correctly. Maybe I'm wrong and it's a similar issue. Anyway thank you. – android developer Sep 08 '15 at 06:57
  • 4
    Sorry for foolishness but how to use this class to draw text in a drawable – Leap Hawk Oct 18 '16 at 11:28
  • 1
    @Akash Rajput ImageView.setImageDrawable(myTextDrawable); – Shane Sepac Apr 27 '18 at 00:52
  • @AkashRajput Drawable drawable=new TextDrawable("MyText"); – Ishwor Khanal Oct 24 '18 at 01:06
  • I tried this code but somehow the only method that is being called is getOpacity(). So no image is shown in the imageView. Any clue? – Leandro Ocampo Jun 25 '19 at 20:31
  • I realised there was a stack trace saying that the drawable needs to have width and height > 0. So I set those in intrinsicWidth and intrinsicHeight to make it work. – Leandro Ocampo Jun 25 '19 at 22:05
16

I've read the book "Professional Android 2 Application Development" (by Reto Meier). Amongst others, it contains an example project where you create a simple compass application where you "draw" text, markers etc.

The brief explanation is that you create a class that extends the android.view.View class and overrides the onDraw(Canvas) method.

All the source code form the book is available for download here: http://www.wrox.com/WileyCDA/WroxTitle/Professional-Android-2-Application-Development.productCd-0470565527,descCd-DOWNLOAD.html. If you download the code and look inside the project named "Chapter 4 Compass", I believe you would find what you're looking for :)

Julian
  • 20,008
  • 17
  • 77
  • 108
12

Looking at Plowman's answer and trying to adjust it to my needs I stumpled upon a Class that is used for Camera in this link

Here is the code from the TextDrawable Class. Looks pretty simillar with Plowmans but for me works better:

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;

public class TextDrawable extends Drawable {
    private static final int DEFAULT_COLOR = Color.WHITE;
    private static final int DEFAULT_TEXTSIZE = 15;
    private Paint mPaint;
    private CharSequence mText;
    private int mIntrinsicWidth;
    private int mIntrinsicHeight;

    public TextDrawable(Resources res, CharSequence text) {
        mText = text;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(DEFAULT_COLOR);
        mPaint.setTextAlign(Align.CENTER);
        float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                DEFAULT_TEXTSIZE, res.getDisplayMetrics());
        mPaint.setTextSize(textSize);
        mIntrinsicWidth = (int) (mPaint.measureText(mText, 0, mText.length()) + .5);
        mIntrinsicHeight = mPaint.getFontMetricsInt(null);
    }
    @Override
    public void draw(Canvas canvas) {
        Rect bounds = getBounds();
        canvas.drawText(mText, 0, mText.length(),
                bounds.centerX(), bounds.centerY(), mPaint);
    }
    @Override
    public int getOpacity() {
        return mPaint.getAlpha();
    }
    @Override
    public int getIntrinsicWidth() {
        return mIntrinsicWidth;
    }
    @Override
    public int getIntrinsicHeight() {
        return mIntrinsicHeight;
    }
    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }
    @Override
    public void setColorFilter(ColorFilter filter) {
        mPaint.setColorFilter(filter);
    }
}
Jimmy Kane
  • 16,223
  • 11
  • 86
  • 117
6

To answer the comments above regarding how to center the text:

mPaint.textAlign = Align.CENTER
...
// Centering for mixed case letters
canvas.drawText(mText, 0, mText.length,
        bounds.centerX().toFloat(), bounds.centerY().toFloat() - ((mPaint.descent() + mPaint.ascent()) / 2), mPaint)

// Centering for all uppercase letters
canvas.drawText(mText, 0, mText.length,
            bounds.centerX().toFloat(), bounds.centerY().toFloat() - mPaint.ascent() / 2, mPaint)
user2241362
  • 63
  • 1
  • 4
4

This allows you to put any View in a Drawable, including a TextView. You can even use styles in your XML layout.

public class ViewDrawable extends Drawable {
    final View mView;

    public ViewDrawable(final Context context, final @LayoutRes int layoutId) {
        this(LayoutInflater.from(context).inflate(layoutId, null));
    }

    public ViewDrawable(final @NonNull View view) {
        mView = view;
    }

    public View getView() {
        return mView;
    }

    @Override
    public void setBounds(int left, int top, int right, int bottom) {
        super.setBounds(left, top, right, bottom);
        final int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(right - left, View.MeasureSpec.EXACTLY);
        final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(bottom - top, View.MeasureSpec.EXACTLY);
        mView.measure(widthMeasureSpec, heightMeasureSpec);
        mView.layout(left, top, right, bottom);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        mView.draw(canvas);
    }

    @Override
    public void setAlpha(int alpha) {
        mView.setAlpha(alpha/255f);
    }

    @Override
    public void setColorFilter(@Nullable ColorFilter colorFilter) {

    }

    @Override
    public int getOpacity() {
        return PixelFormat.UNKNOWN;
    }
}
Dustin
  • 2,064
  • 1
  • 16
  • 12