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

AndroidTextHtml

Android Problem Overview


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

Android Solutions


Solution 1 - Android

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);
	}
}

Solution 2 - Android

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);
	}
}

Solution 3 - Android

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.

Solution 4 - Android

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);
    }
}

Solution 5 - Android

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;  

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhilipp RedekerView Question on Stackoverflow
Solution 1 - AndroidPhilipp RedekerView Answer on Stackoverflow
Solution 2 - AndroidBenjamin DobellView Answer on Stackoverflow
Solution 3 - Androidandroid developerView Answer on Stackoverflow
Solution 4 - AndroidAbdelaziz DaoudView Answer on Stackoverflow
Solution 5 - AndroidĐonnyView Answer on Stackoverflow