Android 101: Estilos e Temas

Neste artigo vamos investigar o que são estilos e temas em Android e como criá-los.

O que são estilos e themes em Android?

Na documentação para programadores ficamos a saber que:

Um estilo é um conjunto de propriedades que especificam a aparência e formato de uma View ou janela. Um estilo pode especificar propriedades, tais como altura, cor da fonte, preenchimento, tamanho da fonte, cor de fundo e muito mais. Um estilo é definido num recurso XML que é independente do XML que especifica o layout.

Um theme é um estilo aplicado a uma Activity ou aplicação inteira, ao invés de uma View individual. Quando um estilo é aplicado como um theme, todas as Views na Activity ou aplicação irão usar todas as propriedades de estilo por ele definidas.

Resumindo, um estilo é um conjunto de propriedades que definem a aparência de uma View ou janela. Um theme é um tipo específico de estilo que tem alcance sobre a Activity ou toda a aplicação.

Declarar e aplicar um estilo

Vamos ver como declarar um estilo. Adicionar ao ficheiro res/values/styles.xml (se ainda não existir, crie-o).

<resources>
    <style name="AppTheme.Button" parent="@android:style/Widget.Button">
        <item name="android:textSize">12sp</item>
        <item name="android:paddingLeft">10dp</item>
        <item name="android:paddingRight">10dp</item>
    </style>
</resources>

Este estilo tem como atributo pai o widget Button, o que significa que este estilo herdará todas as definições de estilo que um botão padrão tem. Ou seja, se não declaramos nenhuma regra no nosso estilo, os botões que usarem este estilo serão exactamente iguais aos botões padrão, mas neste caso substituímos as propriedades de tamanho do texto e do padding.

<Button
    style="@style/AppTheme.Button"
    android:text="@string/buttontext" />

O que fizemos aqui for criar um botão especifico na nossa aplicação com um estilo personalizado. Algo mais interessante do que isto seria alterar todos os botões na nossa aplicação da mesma forma. Para isso não é necessário alterar todas as definições de todos os botões, basta usar um theme.

Declarar e aplicar um theme

Eis como declarar um theme:

<style name="AppTheme.Light" parent="@android:style/Theme.Holo.Light">
    <item name="android:textSize">14sp</item>
    <item name="android:buttonStyle">@style/AppTheme.Button</item>
    <item name="android:editTextStyle">@style/AppTheme.EditText</item>
    <item name="listItemStyle">@style/AppTheme.ListItemStyle</item>
</style>

Praticamente o mesmo que a declaração de um estilo, mas note a diferença no atributo parent do estilo. O parent é um theme out-of-the-box do Android. Algo que pode notar na segunda propriedade do theme é que estamos a associar a todos os botões da aplicação um estilo (android:buttonStyle). Como pode ver, fazemos referência ao estilo criado anteriormente.

Agora este theme pode ser aplicado a uma Activity ou a toda a aplicação. Necessita editar o ficheiro AndroidManifest.xml.

<application android:theme="@style/AppTheme.Light">
<activity android:theme="@style/AppTheme.Light ">

Use um ou outro, dependendo do alcance necessário.

Herança em estilos

Na nossa aplicação criamos dois temas, um com tons claros (Light) e um mais escuro (Dark).

Light
Light
Dark
Dark

Portanto tivemos que criar duas definições de themes.

<style name="AppTheme.Light" parent="@android:style/Theme.Holo.Light">
    <item name="android:textSize">14sp</item>
    <item name="android:buttonStyle">@style/AppTheme.Button</item>
    <item name="android:editTextStyle">@style/AppTheme.EditText</item>
    <item name="listItemStyle">@style/AppTheme.ListItemStyle</item>
</style>
<style name="AppTheme.Dark" parent="@android:style/Theme.Holo">
    <item name="android:textSize">14sp</item>
    <item name="android:buttonStyle">@style/AppTheme.Button</item>
    <item name="android:editTextStyle">@style/AppTheme.EditText</item>
    <item name="listItemStyle">@style/AppTheme.ListItemStyle.Dark</item>
</style>

Além do atributo parent há outra forma de declarar herança através da nomenclatura dos estilos. Por exemplo, para a declaração dos widgets no tema Dark, podemos herdar todas as definições a partir da definição do tema Light e só substituir o que precisamos:

<style name="AppTheme.ListItemStyle.Dark">
    <item name="android:background">@color/list_item_dark</item>
</style>

AppTheme.ListItemStyle.Dark é interpretado como uma herança do estilo já definido AppTheme.ListItemStyle. Esta é uma forma mais estruturada e legível de declarar relações de parentesco, especialmente se tivermos vários temas ou estilos para widgets.

Veja todos os widgets disponíveis para substituição aqui.

Criar um estilo personalizado para uma vista

Uma das propriedades na definição do theme que mostrei é listItemStyle.

<style name="AppTheme.Light" parent="@android:style/Theme.Holo.Light">
    <item name="android:textSize">14sp</item>
    <item name="listItemStyle">@style/AppTheme.ListItemStyle</item>
</style>
<style name="AppTheme.ListItemStyle">
    <item name="android:background">@color/list_item</item>
</style>

Tive a necessidade de criar este valor personalizado porque queria criar um estilo para um LinearLayout, que seria diferente dependendo do theme activo. Essa opção não existe no namespace android (como o android:textSize, por exemplo).

Esta é a minha definição da vista:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/feedListItem"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    style="?listItemStyle">
    <ImageView android:id="@+id/picture"
        android:layout_height="100dip"
        android:layout_width="100dip"/>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/user"
        android:orientation="vertical"
        android:layout_height="wrap_content"
        android:layout_width="fill_parent"
        android:padding="10dp">
        <TextView android:id="@+id/textUsername"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:layout_gravity="center_horizontal"
            android:textStyle="bold"/>
        <TextView android:id="@+id/textContent"
            android:layout_height="fill_parent"
            android:layout_width="fill_parent"
            android:paddingTop="6dp"
            android:maxLines="3"/>
    </LinearLayout>
</LinearLayout>

Repare no atributo style no LinearLayout raiz.

A última coisa que precisamos fazer para que isso funcione é declarar o novo atributo. Caso contrário, terá um erro que diz que o atributo não existe:

No ficheiro res/values/attrs.xml crie uma nova declaração:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="listItemStyle" format="reference" />
</resources>

Mudar o estilo ou theme em tempo de execução

Como tínhamos dois themes diferentes queríamos permitir que o utilizador alterasse o theme activo através das preferências.

Guardamos a preferência selecionada nas SharedPreferences da aplicação e carregamo-la no método onCreate da Activity.

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    SharedPreferences prefs = HoneybuzzApplication.getSharedPreferences();
    String themeName = prefs.getString(HoneybuzzApplication.PREFERENCE_THEME, "AppTheme.Light");
    int themeId = getResources().getIdentifier(themeName, "style", getPackageName());
    setTheme(themeId);
}

Terá que fazer isto para todas as Activities na sua aplicação ou, como nos fizemos, criar uma classe Activity que trata de todas as personalizações e da qual todas as Activities vão herdar.

Também é necessário recarregar o theme de cada vez que o utilizador alterar as preferências da aplicação. Fazêmo-lo na nossa Activity da qual as outras vão herdar.

// handle updates to preferences
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    if (key.equals(HoneybuzzApplication.PREFERENCE_THEME)) {
        // recreate activity so theme gets updated
        this.recreate();
    }
}

Mais recursos

Artigos relacionados