76

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

Carl Anderson
  • 3,446
  • 1
  • 25
  • 45
Philipp Redeker
  • 3,848
  • 4
  • 26
  • 34

5 Answers5

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);
    }
}
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Philipp Redeker
  • 3,848
  • 4
  • 26
  • 34
  • 2
    @notme what should i pass to the string variable family in this constructor CustomTypefaceSpan(String family, Typeface type) {} ??? – KJEjava48 Mar 08 '17 at 11:27
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);
    }
}
Benjamin Dobell
  • 4,042
  • 1
  • 32
  • 44
  • 1
    +1 Thank you! And [here](http://stackoverflow.com/a/10741161/89818) is a proper usage example. – caw Jul 24 '14 at 04:37
  • @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? – Jayson Minard Jan 29 '16 at 10:51
  • @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. – Benjamin Dobell Jan 29 '16 at 11:07
  • 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: http://stackoverflow.com/questions/35039686/extending-metricaffectingspan-with-kotlin – Jayson Minard Jan 29 '16 at 11:11
  • @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. – Benjamin Dobell Jan 29 '16 at 11:22
  • Is it possible to perform underlines to this CustomTypefaceSpan? – VIN Oct 25 '16 at 02:15
  • @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);` – Benjamin Dobell Oct 25 '16 at 03:55
  • @BenjaminDobell could you explain what the purpose of fakeStyle is and what those bitwise operations are doing? I'm a bit confused. – starkej2 Nov 08 '16 at 22:13
  • @starkej2 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). – Benjamin Dobell Nov 08 '16 at 22:18
  • @starkej2 Yeah, although I should add it's not "technically" Android that does the font rendering. It's Skia (https://skia.org/), a native library developed by Google. If you look in `SKPaint.cpp` you can see exactly what is going on. – Benjamin Dobell Nov 08 '16 at 22:29
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.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
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);
    }
}
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://stackoverflow.com/a/17961854/1996780
    /// <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;  
Đonny
  • 910
  • 10
  • 11