A Story of Creating Drawable From Text

Featured image

Introduction

Today I’m going to release the initial version of Android DText Library. It converts string text to a drawable image. You can find the GitHub repository here .

I created DText as a part of my other personal (Yet unfinished) project. What I needed was to create an icon (or you can say avatar) with the first character of the title. Like Contacts app from Google. As I’m a lazy human being, I was looking for a simple solution. After a few searches on Google, I found a library that does create drawables from text. Here is that library in case you are interested.

But the problem was that it was not maintained for a long time. The maintainer seems to be inactive and does not accept pull requests. Also It does not let me customize as much as I need. So, I thought why not create a new library for my present and future use.

In this article I’m going to explain how I created the DText class that converts strings to drawable.

Initial Thoughts

What I need is a class/library that takes a string as input and gives me a nice drawable image as output. The drawable image can be a round shaped, a rounded-rectangle shape or a rectangle shape. Initially I can work with these three basic shapes, but I may implement more shapes in the future as well. Also, I need full control over the font or typeface that is going to be drawn on the canvas. I need regular, bold, italic and bold italic font weight. I may implement light font weight in the future as well.

Getting Started

Thinking of a solution, I found out about the ShapeDrawable class. Android Developer Reference says,

A Drawable object that draws primitive shapes. A ShapeDrawable takes a Shape object and manages its presence on the screen. If no Shape is given, then the ShapeDrawable will default to a RectShape. more

In other words, it says With ShapeDrawable, we can draw shapes as a drawable. So, let’s create a DText class extending the ShapeDrawable class and override a few methods.

public class DText extends ShapeDrawable {

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        // Here we will draw the actual canvas.
    }

    @Override
    public void setAlpha(int alpha) {
        // Here we will set alpha to our paint class instance.
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // Here we will set a color filter to our paint class instance.
    }

    @Override
    public int getOpacity() {
        // Here we will return PixelFormat.TRANSLUCENT as opacity.
        // So that it can be partially transparent and should have
        // alpha blending applied.
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth() {
        // Here we will return our canvas width.
        return 0;
    }

    @Override
    public int getIntrinsicHeight() {
        // Here we will return our canvas height.
        return 0;
    }
}

Now, we have a class called DText that extends ShapeDrawable class. We also override a few methods that we are going to use later on.

After that, we need a builder class to initialize variables and configurations. So, let’s create a Builder class inside of the DText class.

public class DText extends ShapeDrawable {

    private DText() {
        // Here we will initialize variables based on builder configurations.
        // We made this constructor private, because we do not want an
        // DText class instance directly.
        // Which will be useless without a proper drawable.
    }

	...

    public static class Builder {

        public Builder() {
            // Here we will initialize default variables.
        }
    }
}

We have builder class. Now it’s time to create some variables for configuration.

public static class Builder {

    // We need a shape to draw
    private Shape shape;

    // Original text that is going to be drawn
    private String text;

    // We need a typeface for fonts
    private Typeface typeface;

    // Text and background color on the canvas
    private int textColor;
    private int backgroundColor;

    // Text size on the canvas
    private float textSize;

    // Width and height of the canvas
    private float width;
    private float height;

    public Builder() {
        // Let's initialize defaults
        text = "";
        textSize = -1;
        width = -1;
        height = -1;
        backgroundColor = Color.GRAY;
        textColor = Color.WHITE;
        shape = new RectShape(); // We will draw a rectangle shaped drawable
        typeface = Typeface.DEFAULT;
    }
    
    // We need setter methods for those variables
    public Builder setText(String text) {
        this.text = text;
        return this;
    }

    public Builder setHeight(float height) {
        this.height = height;
        return this;
    }

    public Builder setWidth(float width) {
        this.width = width;
        return this;
    }

    public Builder setTypeface(Typeface typeface) {
        this.typeface = typeface;
        return this;
    }

    public Builder setTextSize(float textSize) {
        this.textSize = textSize;
        return this;
    }

    public Builder setTextColor(int color) {
        this.textColor = color;
        return this;
    }

    public Builder setTextColor(String color) {
        this.textColor = Color.parseColor(color);
        return this;
    }

    public Builder setBackgroundColor(int color) {
        this.backgroundColor = color;
        return this;
    }

    public Builder setBackgroundColor(String color) {
        this.backgroundColor = Color.parseColor(color);
        return this;
    }

    public Builder boldText() {
        typeface = Typeface.create(typeface, Typeface.BOLD);
        return this;
    }

    public Builder italicText() {
        typeface = Typeface.create(typeface, Typeface.ITALIC);
        return this;
    }

    public Builder boldItalicText() {
        typeface = Typeface.create(typeface, Typeface.BOLD_ITALIC);
        return this;
    }

    public Builder drawAsRectangle() {
        this.shape = new RectShape();
        return this;
    }

    public Builder drawAsRound() {
        shape = new OvalShape();
        return this;
    }

    public DText build() {
        return new DText(this);
    }
}

Up to this, we have few customization variables in our hand. Now it’s time to actually draw on the canvas…

public class DText extends ShapeDrawable {

    private final Builder builder;
    private final Paint textPaint;

    private DText(Builder builder) {
        super(builder.shape);
        this.builder = builder;

        // Initialize paint class for text
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setColor(builder.textColor);
        textPaint.setTypeface(builder.typeface);

        // Initialize paint class for background
        Paint paint = getPaint();
        paint.setColor(builder.backgroundColor);
    }

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);

        // We need drawable bounds rect
        Rect bounds = getBounds();

        // We need to save current canvas matrix
        // So that we can restore it later
        int savedCanvasCount = canvas.save();

        // Now translate our canvas from bounds rect
        canvas.translate(bounds.left, bounds.top);

        // If builder class does not provide a valid width and height,
        // use bounds rect's width and height as canvas's width and height.
        // We want to fill the whole drawable by default.
        float canvasWidth = builder.width < 0 ? bounds.width() : builder.width;
        float canvasHeight = builder.height < 0 ? bounds.height() : builder.height;

        // If builder class does not provide a valid text size,
        // calculate text size from canvas width and height.
        // Use half of the lowest dimension as text size by default.
        // It can be width or height.
        float textSize = builder.textSize < 0 ?
                (Math.min(canvasWidth, canvasHeight) / 2) : builder.textSize;
        textPaint.setTextSize(textSize);

        // Now it's time to draw the text on the canvas.
        canvas.drawText(builder.text, canvasWidth / 2, canvasHeight / 2 -
                ((textPaint.descent() + textPaint.ascent()) / 2), textPaint);

        // Restore previous matrix to the canvas
        canvas.restoreToCount(savedCanvasCount);
    }

    @Override
    public void setAlpha(int alpha) {
        // Set alpha to our paint class instance.
        textPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // Set color filter to our paint class instance.
        textPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        // Here we will return PixelFormat.TRANSLUCENT as opacity.
        // So that it can be partially transparent and should have
        // alpha blending applied.
        return PixelFormat.TRANSLUCENT;
    }

    @Override
    public int getIntrinsicWidth() {
        // Return our canvas width.
        return (int) builder.width;
    }

    @Override
    public int getIntrinsicHeight() {
        // Return our canvas height.
        return (int) builder.height;
    }

    public static class Builder {

        // We need a shape to draw
        private Shape shape;

        // Original text that is going to be drawn
        private String text;

        // We need a typeface for fonts
        private Typeface typeface;

        // Text and background color on the canvas
        private int textColor;
        private int backgroundColor;

        // Text size on the canvas
        private float textSize;

        // Width and height of the canvas
        private float width;
        private float height;

        public Builder() {
            // Let's initialize defaults
            text = "";
            textSize = -1;
            width = -1;
            height = -1;
            backgroundColor = Color.GRAY;
            textColor = Color.WHITE;
            shape = new RectShape(); // We will draw a rectangle shaped drawable
            typeface = Typeface.DEFAULT;
        }

        public Builder setText(String text) {
            this.text = text;
            return this;
        }

        public Builder setHeight(float height) {
            this.height = height;
            return this;
        }

        public Builder setWidth(float width) {
            this.width = width;
            return this;
        }

        public Builder setTypeface(Typeface typeface) {
            this.typeface = typeface;
            return this;
        }

        public Builder setTextSize(float textSize) {
            this.textSize = textSize;
            return this;
        }

        public Builder setTextColor(int color) {
            this.textColor = color;
            return this;
        }

        public Builder setTextColor(String color) {
            this.textColor = Color.parseColor(color);
            return this;
        }

        public Builder setBackgroundColor(int color) {
            this.backgroundColor = color;
            return this;
        }

        public Builder setBackgroundColor(String color) {
            this.backgroundColor = Color.parseColor(color);
            return this;
        }

        public Builder boldText() {
            typeface = Typeface.create(typeface, Typeface.BOLD);
            return this;
        }

        public Builder italicText() {
            typeface = Typeface.create(typeface, Typeface.ITALIC);
            return this;
        }

        public Builder boldItalicText() {
            typeface = Typeface.create(typeface, Typeface.BOLD_ITALIC);
            return this;
        }

        public Builder drawAsRectangle() {
            this.shape = new RectShape();
            return this;
        }

        public Builder drawAsRound() {
            shape = new OvalShape();
            return this;
        }

        public DText build() {
            return new DText(this);
        }
    }
}

Testing

This is the full DText class up to now. Let’s test it in our activity class. We have an ImageView as demoImageView. Now, let’s create a drawable text using DText class and set it as an image drawable to the ImageView.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    demoImageView = findViewById(R.id.demo_image_view);
    Drawable drawableText = new DText.Builder()
        .setText("First")
        .setTextColor(Color.RED)
        .setBackgroundColor(Color.BLUE)
        .build();
    demoImageView.setImageDrawable(drawableText);
}

And voila… We have a drawable which is created from a text.

First drawable from a text

First drawable from a text

Adding Customization

Up to this, we have a fully working DText class that creates drawables from text. Now we can add customizations like,

// Create an instance of Builder class.
DText.Builder builder = new DText.Builder();

// Set text that we are going to draw.
builder.setText("Android DText Library");

// Draw only the first character.
// If the text is "android",
// then the builder will draw "a" on the canvas.
builder.firstCharOnly();

// Draw only the alphanumeric character.
// If the text is "<Unknown>",
// then the builder will draw first alphanumeric character
// from the text. In this case, it is "U".
// NOTE: alphaNumOnly() will not work without firstCharOnly().
builder.alphaNumOnly();

// Draw only the first digit from the text.
// If the text is "You have 5 notifications",
// then the builder will draw "5" on the canvas.
// NOTE: digitOnly() will not work without firstCharOnly().
builder.digitOnly();

// Use random background color from a nice preset background color list.
builder.randomBackgroundColor();

// You can pass your own color list to the builder as well.
List<String> colorList = new ArrayList<>();
colorList.add("#9C27B0");
colorList.add("#EF6C00");
builder.setRandomColorList(colorList);

// You can set a background color as well.
// By default, the background color is gray.
builder.setBackgroundColor(Color.BLUE);
// or
builder.setBackgroundColor("#0000FF");

// You can set text color with integer or string (HEX) value.
// By default, the text color is white.
builder.setTextColor(Color.RED);
// or
builder.setTextColor("#FF0000");

// Set height and width of the canvas.
builder.setHeight(150);
builder.setWidth(150);

// Set text size.
builder.setTextSize(24);

// By default, DText uses pixels to calculate height, width and text size.
// But you can use DP for height/width and SP for text size as well
// by passing a context to the builder.
builder.useSpAndDp(context);

// Use bold text.
builder.boldText();

// Use italic text.
builder.italicText();

// Use bold italic text.
builder.boldItalicText();

// By default, DText uses Typeface.DEFAULT.
// But you can pass your own typeface as well.
builder.setTypeface(Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL));

// Transform to upper case letter.
builder.toUpperCase();

// Draw as a round on the canvas.
builder.drawAsRound();

// Draw as a rectangle on the canvas.
builder.drawAsRectangle();

// Draw as a rectangle with border radius on the canvas.
builder.drawAsRectangle(16);

After creating our final library we can use it like these..

First letter avatar with round shape

First letter avatar with round shape

Final Words

Thanks for reading this article. If you have any question or confusion regarding the tutorial, feel free to ask your questions on Telegram or Twitter . You can send me an email as well.