Android ReplacementSpan Example

android.text.style.ReplacementSpan class is a supper span class that you can extend to create your customized span class. You can draw anything as you want in your custom span class, and then use it to replace the specified characters in your textview string.

If you are not familiar with android SpannableString, please read the article Android SpannableString Example for a basic introduction.

1. Example Overview.

  1. This example implements the below effect with IconReplacementSpan which extends android.text.style.ReplacementSpan.
  2. Generally your customize span class just needs to override below two ReplacemengSpan methods.
  3. getSize(): Return the span area width.
  4. draw(): Draw the span content in this method.
  5. In our example, we draw both span background and text in it.
    android-replacementspan-example

2. IconReplacementSpan.java

  1. To understand more accurately for below java code, please refer to this picture which shows you the android text font diagram.
    android-text-font-diagram
  2. IconReplacementSpan.java
    package com.dev2qa.example.view;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.RectF;
    import android.support.annotation.IntRange;
    import android.support.annotation.NonNull;
    import android.support.annotation.Nullable;
    import android.text.TextPaint;
    import android.text.TextUtils;
    import android.text.style.ReplacementSpan;
    import android.util.DisplayMetrics;
    import android.util.TypedValue;
    
    public class IconReplacementSpan extends ReplacementSpan {
    
        private Context context;
    
        private int backgroundColorId;
        private float backgroundHeight;
        private float backgroundWidth;
        private float backgroundRadius;
        private Paint backgroundPaint;
    
        private String text;
        private int textColorId;
        private float textSize;
        private float textMarginRight;
        private Paint textPaint;
    
        public IconReplacementSpan(Context context, int backgroundColorId, int textColorId, String text) {
            // Do not allow empty text.
            if(TextUtils.isEmpty(text))
            {
                return;
            }
    
            // Init instance variable value.
            this.context = context.getApplicationContext();
    
            DisplayMetrics displayMetrics = this.context.getResources().getDisplayMetrics();
    
            // Init text related variable value.
            this.text = text;
            this.textColorId = textColorId;
            this.textMarginRight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, displayMetrics);
            this.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 25, displayMetrics);
    
            // Init background related variable value.
            this.backgroundColorId = backgroundColorId;
            this.backgroundHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25f, displayMetrics);
            this.backgroundWidth = this.caculateBackgroundWidth(text, displayMetrics, this.textSize);
            this.backgroundRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, displayMetrics);
    
            // Init background paint.
            this.backgroundPaint = new Paint();
            this.backgroundPaint.setColor(this.backgroundColorId);
            this.backgroundPaint.setStyle(Paint.Style.FILL);
            this.backgroundPaint.setAntiAlias(true);
    
            // Init text paint.
            this.textPaint = new TextPaint();
            this.textPaint.setColor(this.textColorId);
            this.textPaint.setTextSize(this.textSize);
            this.textPaint.setAntiAlias(true);
            this.textPaint.setTextAlign(Paint.Align.CENTER);
        }
    
        /* Return the background width after replace text. */
        @Override
        public int getSize(@NonNull Paint paint, CharSequence charSequence, @IntRange(from = 0) int i, @IntRange(from = 0) int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) {
            return (int)(this.backgroundWidth + this.textMarginRight);
        }
    
        /*Draw the span component in the canvas.*/
        @Override
        public void draw(@NonNull Canvas canvas, CharSequence text, @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
    
            // Draw background.
            Paint.FontMetrics backgroundFontMetrics = this.backgroundPaint.getFontMetrics();
            float textHeight = backgroundFontMetrics.descent - backgroundFontMetrics.ascent;
            //Calculate draw background y coordinate.
            float backgroundStartY = y + (textHeight - this.backgroundHeight) / 2 + backgroundFontMetrics.ascent;
    
            RectF backgroundRect = new RectF(x, backgroundStartY, x + this.backgroundWidth, backgroundStartY + this.backgroundHeight);
            canvas.drawRoundRect(backgroundRect, this.backgroundRadius, this.backgroundRadius, this.backgroundPaint);
    
            // Draw text.
            Paint.FontMetrics textFontMetrics = textPaint.getFontMetrics();
            float textRectHeight = textFontMetrics.bottom - textFontMetrics.top;
            canvas.drawText(this.text, x + this.backgroundWidth / 2, backgroundStartY + (this.backgroundHeight - textRectHeight) / 2 - textFontMetrics.top, textPaint);
        }
    
        /* Calculate and return background width.*/
        private float caculateBackgroundWidth(String text, DisplayMetrics displayMetrics, float textSize) {
            if (text.length() > 1) {
                Paint textPaint = new Paint();
                textPaint.setTextSize(textSize);
                Rect textRect = new Rect();
                // calculate textRect
                textPaint.getTextBounds(text, 0, text.length(), textRect);
                // Get context resource padding value.
                float padding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, displayMetrics);
                int textWidth = textRect.width();
                return textWidth + padding * 2;
            } else {
                // If has only one character, then return background width is equal to height.
                return this.backgroundHeight;
            }
        }
    }

3. ReplacementSpanActivity.java

  1. ReplacementSpanActivity.java
    package com.dev2qa.example;
    
    import android.graphics.Color;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.text.SpannableString;
    import android.text.Spanned;
    import android.text.style.ReplacementSpan;
    import android.widget.TextView;
    
    import com.dev2qa.example.view.IconReplacementSpan;
    
    import java.util.ArrayList;
    import java.util.List;
    
    
    public class ReplacementSpanActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_replacement_span);
    
            TextView replacementSpanTextView = (TextView) findViewById(R.id.textViewReplacementSpan);
            replacementSpanTextView.setTextSize(20);
    
            List<ReplacementSpan> spanList = new ArrayList<>();
            String content = "  This article will show you an example about how to use android ReplacementSpan to draw a text with customized background color at any place in your text.";
            // This span will add a Like text icon.
            IconReplacementSpan likeSpan = new IconReplacementSpan(getApplicationContext(), Color.BLUE, Color.WHITE, "Like");
            spanList.add(likeSpan);
    
            // This span will add a H text icon.
            IconReplacementSpan hSpan = new IconReplacementSpan(getApplicationContext(), Color.GREEN, Color.RED, "H");
            spanList.add(hSpan);
    
            SpannableString spannableString = new SpannableString(content);
            // Loop in the spanList.
            for (int i = 0; i < spanList.size(); i++) {
                spannableString.setSpan(spanList.get(i), i, i + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
            replacementSpanTextView.setText(spannableString);
        }
    }

4. activity_replacement_span.xml

  1. There is only one TextView component in this layout file.
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.dev2qa.example.ReplacementSpanActivity">
    
        <TextView
            android:id="@+id/textViewReplacementSpan"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="TextView"
            tools:layout_editor_absoluteY="0dp"
            tools:layout_editor_absoluteX="8dp" />
    </android.support.constraint.ConstraintLayout>

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.