How can I use TypefaceSpan or StyleSpan with a custom Typeface?
Asked Answered
G

5

76

I have not found a way to do this. Is it possible?

Gander answered 27/1, 2011 at 16:33 Comment(0)
P
108

Whilst notme has essentially the right idea, the solution given is a bit hacky as "family" becomes redundant. It is also slightly incorrect because TypefaceSpan is one of the special spans that Android knows about and expects certain behaviour with respect to the ParcelableSpan interface (which notme's subclass does not properly, nor is it possible to, implement).

A simpler and more accurate solution would be:

public class CustomTypefaceSpan extends MetricAffectingSpan
{
    private final Typeface typeface;

    public CustomTypefaceSpan(final Typeface typeface)
    {
        this.typeface = typeface;
    }

    @Override
    public void updateDrawState(final TextPaint drawState)
    {
        apply(drawState);
    }

    @Override
    public void updateMeasureState(final TextPaint paint)
    {
        apply(paint);
    }

    private void apply(final Paint paint)
    {
        final Typeface oldTypeface = paint.getTypeface();
        final int oldStyle = oldTypeface != null ? oldTypeface.getStyle() : 0;
        final int fakeStyle = oldStyle & ~typeface.getStyle();

        if ((fakeStyle & Typeface.BOLD) != 0)
        {
            paint.setFakeBoldText(true);
        }

        if ((fakeStyle & Typeface.ITALIC) != 0)
        {
            paint.setTextSkewX(-0.25f);
        }

        paint.setTypeface(typeface);
    }
}
Physicalism answered 31/7, 2013 at 4:51 Comment(10)
+1 Thank you! And here is a proper usage example.Rodriquez
@MarcoW and @Benjamin .... Benjamin says that you can't use TypefaceSpan but then Marco shows a working example using just that. Which is the right one? Benjamin did you test your example?Autopsy
@JaysonMinard I think @MarcoW commented on the wrong answer. I suppose he meant to comment on @notme's answer, as it is his class that he used. To be clear, I'm not saying you can't sub-class TypefaceSpan. I'm just very strongly suggesting you shouldn't. Doing so breaks Liscov's principal of substitution and is extremely bad practice. You've sub-classed a class that specifies the font via a family name and is parcelable; you've then totally overwritten that behaviour and made the family parameter confusing and pointless, and the Span is also not parcelable.Physicalism
I really know nothing on this topic @BenjaminDobell ... just trying to figure out what is going on in this other question that basically ported the answers here to Kotlin and failed to get it working. The Kotlin difference is not relevant because it is the same concept, and would be the same byte code as Java, something else must be wrong with: #35040186Autopsy
@JaysonMinard Well considering this answer already has 42 up-votes, asking if I tested my example is a little bit peculiar. However, yes I have tested this code. This is the exact code I've used in numerous published Android apps.Physicalism
Is it possible to perform underlines to this CustomTypefaceSpan?Apparent
@Klone Android's Typeface class doesn't provide support for underlines. However, both the official TypefaceSpan and my class CustomTypefaceSpan use a TextPaint behind the scenes to assist with the rendering, the only problem is that it's not exposed in a simple fashion. To underline an entire TextView you can do textView.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);. Otherwise, add a boolean underlined, and a new method setUnderlined(boolean) to my class. Then in apply(Paint), add paint.setUnderlineText(underlined);Physicalism
@BenjaminDobell could you explain what the purpose of fakeStyle is and what those bitwise operations are doing? I'm a bit confused.Bobwhite
@Bobwhite Usually when you make a font bold on your computer, there is a separate bold version of the font in question, and that is used in place of the regular font. However, for that to work you have to know whether the font you're using is already bold and what the corresponding fonts are. As a work around Android provides "fake" bold and italic modes, which just applies some transforms to the regular font in order to make it appear bold or italic (so that you don't need to load multiple typeface).Physicalism
@Bobwhite Yeah, although I should add it's not "technically" Android that does the font rendering. It's Skia (skia.org), a native library developed by Google. If you look in SKPaint.cpp you can see exactly what is going on.Physicalism
G
149

Well I couldn't figure out how to do it with the available classes so I extended the TypefaceSpan on my own an now it works for me. Here is what I did:

package de.myproject.text.style;

import android.graphics.Paint;
import android.graphics.Typeface;
import android.text.TextPaint;
import android.text.style.TypefaceSpan;

public class CustomTypefaceSpan extends TypefaceSpan {
    private final Typeface newType;

    public CustomTypefaceSpan(String family, Typeface type) {
        super(family);
        newType = type;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        applyCustomTypeFace(ds, newType);
    }

    @Override
    public void updateMeasureState(TextPaint paint) {
        applyCustomTypeFace(paint, newType);
    }

    private static void applyCustomTypeFace(Paint paint, Typeface tf) {
        int oldStyle;
        Typeface old = paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }

        int fake = oldStyle & ~tf.getStyle();
        if ((fake & Typeface.BOLD) != 0) {
            paint.setFakeBoldText(true);
        }

        if ((fake & Typeface.ITALIC) != 0) {
            paint.setTextSkewX(-0.25f);
        }

        paint.setTypeface(tf);
    }
}
Gander answered 28/1, 2011 at 9:58 Comment(1)
@notme what should i pass to the string variable family in this constructor CustomTypefaceSpan(String family, Typeface type) {} ???Pudendas
P
108

Whilst notme has essentially the right idea, the solution given is a bit hacky as "family" becomes redundant. It is also slightly incorrect because TypefaceSpan is one of the special spans that Android knows about and expects certain behaviour with respect to the ParcelableSpan interface (which notme's subclass does not properly, nor is it possible to, implement).

A simpler and more accurate solution would be:

public class CustomTypefaceSpan extends MetricAffectingSpan
{
    private final Typeface typeface;

    public CustomTypefaceSpan(final Typeface typeface)
    {
        this.typeface = typeface;
    }

    @Override
    public void updateDrawState(final TextPaint drawState)
    {
        apply(drawState);
    }

    @Override
    public void updateMeasureState(final TextPaint paint)
    {
        apply(paint);
    }

    private void apply(final Paint paint)
    {
        final Typeface oldTypeface = paint.getTypeface();
        final int oldStyle = oldTypeface != null ? oldTypeface.getStyle() : 0;
        final int fakeStyle = oldStyle & ~typeface.getStyle();

        if ((fakeStyle & Typeface.BOLD) != 0)
        {
            paint.setFakeBoldText(true);
        }

        if ((fakeStyle & Typeface.ITALIC) != 0)
        {
            paint.setTextSkewX(-0.25f);
        }

        paint.setTypeface(typeface);
    }
}
Physicalism answered 31/7, 2013 at 4:51 Comment(10)
+1 Thank you! And here is a proper usage example.Rodriquez
@MarcoW and @Benjamin .... Benjamin says that you can't use TypefaceSpan but then Marco shows a working example using just that. Which is the right one? Benjamin did you test your example?Autopsy
@JaysonMinard I think @MarcoW commented on the wrong answer. I suppose he meant to comment on @notme's answer, as it is his class that he used. To be clear, I'm not saying you can't sub-class TypefaceSpan. I'm just very strongly suggesting you shouldn't. Doing so breaks Liscov's principal of substitution and is extremely bad practice. You've sub-classed a class that specifies the font via a family name and is parcelable; you've then totally overwritten that behaviour and made the family parameter confusing and pointless, and the Span is also not parcelable.Physicalism
I really know nothing on this topic @BenjaminDobell ... just trying to figure out what is going on in this other question that basically ported the answers here to Kotlin and failed to get it working. The Kotlin difference is not relevant because it is the same concept, and would be the same byte code as Java, something else must be wrong with: #35040186Autopsy
@JaysonMinard Well considering this answer already has 42 up-votes, asking if I tested my example is a little bit peculiar. However, yes I have tested this code. This is the exact code I've used in numerous published Android apps.Physicalism
Is it possible to perform underlines to this CustomTypefaceSpan?Apparent
@Klone Android's Typeface class doesn't provide support for underlines. However, both the official TypefaceSpan and my class CustomTypefaceSpan use a TextPaint behind the scenes to assist with the rendering, the only problem is that it's not exposed in a simple fashion. To underline an entire TextView you can do textView.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG);. Otherwise, add a boolean underlined, and a new method setUnderlined(boolean) to my class. Then in apply(Paint), add paint.setUnderlineText(underlined);Physicalism
@BenjaminDobell could you explain what the purpose of fakeStyle is and what those bitwise operations are doing? I'm a bit confused.Bobwhite
@Bobwhite Usually when you make a font bold on your computer, there is a separate bold version of the font in question, and that is used in place of the regular font. However, for that to work you have to know whether the font you're using is already bold and what the corresponding fonts are. As a work around Android provides "fake" bold and italic modes, which just applies some transforms to the regular font in order to make it appear bold or italic (so that you don't need to load multiple typeface).Physicalism
@Bobwhite Yeah, although I should add it's not "technically" Android that does the font rendering. It's Skia (skia.org), a native library developed by Google. If you look in SKPaint.cpp you can see exactly what is going on.Physicalism
A
5

On Android P it's possible using the same TypefaceSpan class you know of, as shown here.

But on older versions, you can use what they've shown later in the video, which I've written about here.

Auvil answered 18/5, 2018 at 23:31 Comment(0)
S
2

Spannable typeface: In order to set a different font typeface to some portion of text, a custom TypefaceSpan can be used, as shown in the following example:

spannable.setSpan( new CustomTypefaceSpan("SFUIText-Bold.otf",fontBold), 0,
firstWord.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan( new CustomTypefaceSpan("SFUIText-Regular.otf",fontRegular),
firstWord.length(), firstWord.length() + lastWord.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setText( spannable );

However, in order to make the above code working, the class CustomTypefaceSpan has to be derived from the class TypefaceSpan. This can be done as follows:

public class CustomTypefaceSpan extends TypefaceSpan {
    private final Typeface newType;

    public CustomTypefaceSpan(String family, Typeface type) {
        super(family);
        newType = type;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        applyCustomTypeFace(ds, newType);
    }

    @Override
    public void updateMeasureState(TextPaint paint) {
        applyCustomTypeFace(paint, newType);
    }

    private static void applyCustomTypeFace(Paint paint, Typeface tf) {
        int oldStyle;
        Typeface old = paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }
        int fake = oldStyle & ~tf.getStyle();
        if ((fake & Typeface.BOLD) != 0) {
            paint.setFakeBoldText(true);
        }
        if ((fake & Typeface.ITALIC) != 0) {
            paint.setTextSkewX(-0.25f);
        }
        paint.setTypeface(tf);
    }
}
Sparky answered 3/6, 2020 at 5:22 Comment(0)
Z
0

If anybody would be interested here's C# Xamarin version of Benjamin's code:

using System;
using Android.Graphics;
using Android.Text;
using Android.Text.Style;

namespace Utils
{
    //https://mcmap.net/q/219141/-how-can-i-use-typefacespan-or-stylespan-with-a-custom-typeface
    /// <summary>A text span which applies <see cref="Android.Graphics.Typeface"/> on text</summary>
    internal class CustomFontSpan : MetricAffectingSpan
    {
        /// <summary>The typeface to apply</summary>
        public Typeface Typeface { get; }

        /// <summary>CTor - creates a new instance of the <see cref="CustomFontSpan"/> class</summary>
        /// <param name="typeface">Typeface to apply</param>
        /// <exception cref="ArgumentNullException"><paramref name="typeface"/> is null</exception>
        public CustomFontSpan(Typeface typeface) =>
            Typeface = typeface ?? throw new ArgumentNullException(nameof(typeface));


        public override void UpdateDrawState(TextPaint drawState) => Apply(drawState);

        public override void UpdateMeasureState(TextPaint paint) => Apply(paint);

        /// <summary>Applies <see cref="Typeface"/></summary>
        /// <param name="paint"><see cref="Paint"/> to apply <see cref="Typeface"/> on</param>
        private void Apply(Paint paint)
        {
            Typeface oldTypeface = paint.Typeface;
            var oldStyle = oldTypeface != null ? oldTypeface.Style : 0;
            var fakeStyle = oldStyle & Typeface.Style;

            if (fakeStyle.HasFlag(TypefaceStyle.Bold))
                paint.FakeBoldText = true;

            if (fakeStyle.HasFlag(TypefaceStyle.Italic))
                paint.TextSkewX = -0.25f;

            paint.SetTypeface(Typeface);
        }
    }
}

And usage: (in activity OnCreate)

var txwLogo = FindViewById<TextView>(Resource.Id.logo);
var font = Resources.GetFont(Resource.Font.myFont);

var wordtoSpan = new SpannableString(txwLogo.Text);
wordtoSpan.SetSpan(new CustomFontSpan(font), 6, 7, SpanTypes.InclusiveInclusive); //One caracter
txwLogo.TextFormatted = wordtoSpan;  
Zeller answered 17/3, 2019 at 12:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.