Skip to content

Instantly share code, notes, and snippets.

@ok3141
Last active March 15, 2023 05:43
Show Gist options
  • Save ok3141/3a3d142adc2e30a203a8211adfc65bd8 to your computer and use it in GitHub Desktop.
Save ok3141/3a3d142adc2e30a203a8211adfc65bd8 to your computer and use it in GitHub Desktop.
MonospaceSpan detects the widest char of given text and draws others with the same width
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.style.ReplacementSpan;
public class MonospaceSpan extends ReplacementSpan {
private boolean ignoreFullText;
public void setIgnoreFullText(boolean ignoreFullText) {
this.ignoreFullText = ignoreFullText;
}
private int getMaxCharWidth(@NonNull Paint paint, @NonNull CharSequence text, int start, int end, float[] widths) {
if (widths == null) {
widths = new float[end - start];
}
paint.getTextWidths(text, start, end, widths);
float max = 0;
for (float w : widths) {
if (max < w) {
max = w;
}
}
return Math.round(max);
}
@Override
public int getSize(@NonNull Paint paint, @NonNull CharSequence text, int start, int end, @Nullable Paint.FontMetricsInt fm) {
if (fm != null) {
paint.getFontMetricsInt(fm);
}
int count = end - start;
if (text.charAt(start) == '\n') {
count -= 1;
}
if (text.charAt(end - 1) == '\n') {
count -= 1;
}
if (count < 0) {
count = 0;
}
if (ignoreFullText) {
return getMaxCharWidth(paint, text, start, end, null) * count;
} else {
return getMaxCharWidth(paint, text, 0, text.length(), null) * count;
}
}
@Override
public void draw(@NonNull Canvas canvas, @NonNull CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
float[] widths = new float[end - start];
int max = getMaxCharWidth(paint, text, start, end, widths);
if (!ignoreFullText) {
max = getMaxCharWidth(paint, text, 0, text.length(), null);
}
for (int i = 0, n = end - start; i < n; ++i) {
float p = (max - widths[i]) / 2;
canvas.drawText(text, start + i, start + i + 1, x + max * i + p, y, paint);
}
}
}
@ok3141
Copy link
Author

ok3141 commented Nov 23, 2018

Example

Example

MainActivity.java

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.SpannableString;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        String text = "Lorem ipsum\ndolor sit amet\n0123456789";
        SpannableString textMono = new SpannableString(text);
        textMono.setSpan(new MonospaceSpan(), 0, textMono.length(), 0);

        TextView textView1 = findViewById(android.R.id.text1);
        TextView textView2 = findViewById(android.R.id.text2);

        textView1.setText(text);
        textView2.setText(textMono);
    }
}

res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@android:id/text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:background="#fa0"
        android:fontFamily="@font/fredoka_one" />

    <TextView
        android:id="@android:id/text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="4dp"
        android:background="#0af"
        android:fontFamily="@font/fredoka_one" />
</LinearLayout>

@Walther-MD
Copy link

WARNING: This class breaks when handling CharSequences which include unicode Emojis, because those are represented using TWO CharSequence elements together. Displaying them one by one breaks the display (so instead of the emoji, you'll get two question marks).

To display the emoji correctly, line 70 needs to be "canvas.drawText(text, start + i, start + i + 2, x + max * i + p, y, paint);", but, only where it's needed. I don't have the solution, but I wanted to report this bug :')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment