Diseño Android: Tipografías personalizadas

android

Existen dos motivos que pueden llevarnos a tener que definir explícitamente el tipo de letra a utilizar en nuestra aplicación:

  • Queremos utilizar un tipo específico como parte del diseño de la aplicación.
  • Evitar que en caso de que el usuario cambie el tipo de letra por omisión de su dispositivo Android el diseño de nuestra aplicación pueda quedar “afeado”. Esta funcionalidad no está incluída originalmente en Android pero es proporcionada por algunos fabricantes como Samsung y algunas apps en dispositivos rooteados.

El atributo android:typeface de TextView sólo se aplica a las familias de tipos de letras por omisión del sistema por lo que no existe una forma directa de utilizar una letra personalizada incluída en la aplicación simplemente desde XML. En este artículo veremos algunas de las opciones más habituales para utilizar tipos de letras TrueType personalizados.

Como ejemplo, utilizaremos Roboto diseñada por Google especialmente para Android y que es el tipo de letra por omisión a partir de Android 4. Nuestra app de ejemplo mostrará siempre este tipo de letra independientemente de la versión de Android y su configuración.

Nota: parece ser que hay algunos problemas en Lollipop al utilizarse tipografías personalizadas, consultar este hilo y el workaround.

Entorno de pruebas:

Requisitos: Conocimientos básicos de Android SDK. Se utilizará Eclipse como IDE.

Proyecto de ejemplo

La aplicación de ejemplo consistirá en una única Activity con un layout que mostrará cuatro TextView. Uno de ellos utilizará la tipografía del sistema y los demás serán utilizados para mostrar Roboto en negrita, cursiva y normal.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/background_light"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/textViewSystem"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/system"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" />

    <TextView
        android:id="@+id/textViewRoboto"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_normal"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" />

    <TextView
        android:id="@+id/textViewRobotoItalic"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_italic"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" />

    <TextView
        android:id="@+id/textViewRobotoBold"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_bold"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" />

</LinearLayout>

Los tipos True Type que se quieran utilizar se incorporarán en la aplicación incluyendo los ficheros .ttf en el directorio assets. En el ejemplo se usará Roboto tal y como se ha comentado y que viene incluída en la SDK de Android, yo la he tomado de /platforms/android-15/data/fonts (en Lollipop encontraremos una nueva versión de Roboto).

Forzar programáticamente el uso del tipo de letra

Esta operación es sumamente sencilla puesto que lo único que hay que hacer es cargar la tipografía a partir de su fichero .ttf y aplicarla con el método setTypeface.

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//NOTA: la obtención de la letra es una operación costosa, se debería obtener una sóla vez
        //en toda la aplicación
		Typeface typefaceRegular = Typeface.createFromAsset(getAssets(), "fonts/Roboto-Regular.ttf");
		Typeface typefaceItalic = Typeface.createFromAsset(getAssets(), "fonts/Roboto-Italic.ttf");
		Typeface typefaceBold = Typeface.createFromAsset(getAssets(), "fonts/Roboto-Bold.ttf");
		
		((TextView) findViewById(R.id.textViewRoboto)).setTypeface(typefaceRegular);
		((TextView) findViewById(R.id.textViewRobotoItalic)).setTypeface(typefaceItalic);
		((TextView) findViewById(R.id.textViewRobotoBold)).setTypeface(typefaceBold);

	}

En una HTC Desire con Froyo tendremos el siguiente resultado:

Roboto en Froyo HTC

Y en Samsung con Gingerbread

Roboto en Gingerbread Samsung HTC

Implementar una Custom View y configurar con XML

Existe una solución más potente y elegante que consiste en implementar nuestro propio TextView al que dotaremos de un atributo que nos permita indicar directamente en el XML de los layout el tipo de letra a utilizar. Ahora simplemente utilizaremos este “Custom View” de mismo modo que el TextView propio de Android en cualquier aplicación en la que queramos personalizar el tipo de letra.

Para crear nuestro propio TextView vamos a especializar el TextView de Android y redefinir todos sus posibles constructores. Para establecer el tipo de letra que queramos se implementará el método public void setTypeface(Typeface tf, int style). Se va a suponer la siguiente convención: los ficheros .ttf se nombran siguiendo la siguiente convención: -.ttf.

CustomTextView

package com.danieme.blog.android.customfonts;

import java.io.IOException;
import java.util.HashMap;

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

/**
 * TextView que utiliza el tipo de letra indicado por el atributo "font" (ver
 * fichero /res/values/attrs.xml). El tipo de letra debe estar en el directorio
 * assets siguiendo la convención <nombre atributo
 * font>-<[Bold][Italic][Regular]>.ttf
 * 
 * @author danielme.com
 * 
 */
public class CustomTextView extends TextView
{
	/**
	 * Se guarda la tipografía en atributos estáticos para evitar el coste de
	 * obtenerlas siempre que se necesitan desde assets.
	 */
	private static HashMap<String, Typeface> tipografias;

	/**
	 * Estilo de letra (bold, italic...)
	 *  @see android.graphics.Typeface 
	 */
	private int fontStyle;

	//contructor necesario en Android 5
	@TargetApi(Build.VERSION_CODES.LOLLIPOP)
	public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
	{
		super(context, attrs, defStyleAttr, defStyleRes);
		setFont(attrs);
	}

	public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr)
	{
		super(context, attrs, defStyleAttr);
		setFont(attrs);
	}

	public CustomTextView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		setFont(attrs);
	}

	private void setFont(AttributeSet attrs)
	{
		// se obtiene el valor del atributo "font" que será el tipo de letra
		String font = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.danieme.blog.android.customfonts", "font");
		if (font != null)
		{
			// se obtienen automáticamente todas los tipos de letras existentes
			// la primera vez. Se ubicarán en /assets/fonts
			if (tipografias == null)
			{
				tipografias = new HashMap<String, Typeface>();
				String[] ttfs = null;
				try
				{
					ttfs = getContext().getAssets().list("fonts");
				}
				catch (IOException e)
				{
					Log.e("CustomFonts", "error al leerse los assets", e);
				}
				if (ttfs != null)
				{
					for (String file : ttfs)
					{
						tipografias.put(file, Typeface.createFromAsset(getContext().getAssets(), "fonts/" + file));
					}
				}

			}
			Typeface typeface = null;
			if (fontStyle == Typeface.BOLD)
			{
				typeface = tipografias.get(font + "-Bold.ttf");
			}

			else if (fontStyle == Typeface.ITALIC)
			{
				typeface = tipografias.get(font + "-Italic.ttf");
			}
			else
			{
				typeface = tipografias.get(font + "-Regular.ttf");
			}
			//si no se ha encontrado se deja el valor por omisión establecido por TextView
			if (typeface != null)
			{
				super.setTypeface(typeface);
			}
		}

	}

	//se sobreescribe este método para guardar el valor de style
	@Override
	public void setTypeface(Typeface tf, int style)
	{
		super.setTypeface(tf, style);
		fontStyle = style;
	}

}

El valor de la variable font será un atributo definido en la declaración del CustomTextView en el XML. Si no se proporciona este valor o bien el tipo de letra solicitado no existe se aplicará la configuración por omisión del sistema. Para poder utilizar este atributo hay que definirlo en el fichero /values/attrs.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="CustomTextView">
        <attr name="font" format="string"/>            
    </declare-styleable>

</resources>

Ahora en el layout utilizaremos nuestro CustomTextView y para el Textview que utiliza la letra por defecto del sistema bastará con no definir el valor font.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res/com.danieme.blog.android.customfonts"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/background_light"
    android:orientation="vertical" >

    <com.danieme.blog.android.customfonts.CustomTextView
        android:id="@+id/textViewSystem"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/system"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" />

    <com.danieme.blog.android.customfonts.CustomTextView
        android:id="@+id/textViewRoboto"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_normal"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize" 
        custom:font="Roboto"/>

    <com.danieme.blog.android.customfonts.CustomTextView
        android:id="@+id/textViewRobotoItalic"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_italic"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize"
        android:textStyle="italic"
        custom:font="Roboto" />

    <com.danieme.blog.android.customfonts.CustomTextView
        android:id="@+id/textViewRobotoBold"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/textView_margin"
        android:text="@string/roboto_bold"
        android:textColor="@android:color/black"
        android:textSize="@dimen/textView_fontSize"
        android:textStyle="bold"
        custom:font="Roboto" />

</LinearLayout>

Código de ejemplo

El proyecto completo se encuentra disponible en GitHub.

Para más información sobre cómo utilizar GitHub, consultar este artículo.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: