Créer une vue personnalisée et composée avec Android

Lars Vogel

Traducteur

Christophe LOUVET

Cet article décrit la création d'une vue personnalisée avec Android. Il est basé sur Eclipse 4.4, Java 1.6 et Android 4.4.

Nous remercions Lars Vogel qui nous a aimablement autorisés à traduire et héberger cet article.

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. Commentez .

- Titre : Créer une vue personnalisée et composée avec Android

- Auteur : Lars Vogel

- Traducteur : Christophe LOUVET

- Parution : 22 août 2014

- Mise à jour : 20 février 2020

- Public visé :

- Licence : Le contenu de cet article est rédigé par Lars Vogel et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- I. Sommaire

- II. Vue personnalisée

- II-A. Vue par défaut

- II-B. Comment Android dessine la hiérarchie des vues

- II-C. Les raisons de création des vues

- II-D. La responsabilité des vues

- II-E. Les façons de créer des vues personnalisées

- II-F. Utiliser de nouvelles vues dans des fichiers layout

- II-G. Capture d'écran (images) de vues

- III. Vues composées (compound views)

- IV. Créer une vue personnalisée (custom view)

- IV-A. Créer une vue

- IV-B. Mesure

- IV-C. Définir des custom layout managers

- V. Cycle de vie

- V-A. Cycle de vie des événements liés à la fenêtre

- V-B. Parcours des événements du cycle de vie

- V-C. Cycle de vie d'une activité

- VI. Définir des attributs supplémentaires pour votre vue personnalisée

- VII. Exercice : vue composée

- VII-A. Créer un projet

- VII-B. Définir et utiliser des attributs supplémentaires

- VII-C. Créer une vue composée

- VII-D. Create Activity

- VIII. l'API Canvas

- VIII-A. Vue d'ensemble

- VIII-B. La classe Canvas

- VIII-C. La classe Paint

- VIII-D. Shader

- IX. Données de vue persistante

- X. Exemple pour une vue personnalisée

- XI. Liens et documentation

- XI-A. Code source

- XI-B. Ressources vues personnalisées

- XI-C. Ressources Android générales

- XII. Remerciements Developpez

I - Sommaire

Cet article décrit la création d'une vue personnalisée avec Android. Il est basé sur Eclipse 4.4, Java 1.6 et Android 4.4.

II - Vue personnalisée

II-A - Vue par défaut

Le framework Android fournit plusieurs vues par défaut, mais le développeur peut aussi créer ses propres vues et les utiliser dans ses applications. La classe de base pour les vues est View.

II-B - Comment Android dessine la hiérarchie des vues

Quand une activité reçoit le focus, elle doit fournir le nœud racine de sa hiérarchie de layout au système Android. La procédure de dessin est ensuite démarrée.

Le dessin commence avec le nœud racine du layout. La hiérarchie de layout est traversée dans l'ordre des déclarations, c'est-à-dire les parents sont dessinés avant leurs enfants et les enfants le sont dans l'ordre de leur déclaration.

Le dessin de layout s'effectue en deux passes.

- La passe d'évaluation - implémentée dans la méthode measure(int, int), c'est une traversée de haut en bas dans la hiérarchie de la vue. Chaque vue stocke son évaluation.

- La passe layout - implémentée dans la méthode layout(int, int), c'est aussi une traversée de haut en bas dans la hiérarchie de la vue. Pendant cette phase, chaque layout manager est responsable du positionnement de tous ses enfants. Il utilise les tailles précalculées dans la passe d'évaluation.

Les passes évaluation et layout se déroulent toujours ensemble.

Les layout managers peuvent lancer la passe d'évaluation plusieurs fois. Par exemple : LinearLayout supporte l'attribut weight, lequel distribue l'espace vide restant parmi les vues et les mesures RelativeLayout des vues enfants plusieurs fois, pour régler les contraintes données par le fichier de layout.

Une vue ou une activité peut déclencher la passe d'évaluation et la passe layout avec un appel à la méthode requestLayout().

Après l'évaluation et le calcul du layout, les vues se dessinent elles-mêmes. Cette opération peut être déclenchée avec la méthode invalidate() de la classe View.

II-C - Les raisons de création des vues

Une vue est typiquement créée pour fournir une expérience utilisateur impossible avec les vues par défaut. Utiliser des vues personnalisées permet au développeur d'effectuer des optimisations de performance. En effet, dans le cas d'un layout personnalisé, le développeur peut optimiser le layout manager pour son cas d'utilisation.

II-D - La responsabilité des vues

Les vues sont responsables de l'évaluation, de la pagination, et du dessin d'elles-mêmes et de leurs éléments enfants (en cas d'utilisation de ViewGroup). Les vues sont aussi responsables de la sauvegarde de leur état UI et de la gestion des touch events.

II-E - Les façons de créer des vues personnalisées

Les vues personnalisées sont classées soit comme vues composées, soit comme vues personnalisées. Il est possible de créer une vue personnalisée par :

1. Vues composées ;

2. Vues personnalisées :

II-F - Utiliser de nouvelles vues dans des fichiers layout

Les vues composées et personnalisées peuvent être utilisées dans des fichiers layout. Pour cela, vous avez besoin du nom complet qualifié, c'est-à-dire à l'aide du nom du package et de la classe.

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

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />

<de.vogella.android.ownview.MyDrawView
android:id="@+id/myDrawView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

Astuce : optionnellement, vous pouvez aussi déclarer votre espace de noms dans le fichier de layout, comme pour l'espace de noms Android.

II-G - Capture d'écran (images) de vues

Chaque classe View supporte la création d'une image de son affichage courant. Le code suivant montre un exemple de cela.

# Construction du cache de dessin
view.buildDrawingCache();

# Création Bitmap
Bitmap cache = view.getDrawingCache();

# Sauvegarde Bitmap
saveBitmap(cache);
view.destroyDrawingCache();

III - Vues composées (compound views)

Les vues composées (également appelées composants composés) sont des ViewGroups préconfigurés basés sur des vues existantes avec certaines interactions prédéfinies.

Celles-ci permettent également d'ajouter des API personnalisées pour mettre à jour et interroger l'état de la vue composée.

Pour un tel contrôle, vous définissez un fichier de layout et l'assignez à votre vue composée.

Dans l'implémentation de votre vue composée, vous devez prédéfinir l'interaction avec la vue. Vous devez définir un fichier layout et étendre la classe ViewGroup correspondante. Dans cette classe, vous étendez le fichier layout et implémentez la logique de connexion de la vue.

Pour des questions de performances, vous pouvez réécrire votre vue composée dans une vue personnalisée avec extends View. Cela peut généralement aplatir votre hiérarchie de vue. Le dessin nécessitera dans ce cas une traversée moindre et sera plus rapide s'il est correctement implémenté.

IV - Créer une vue personnalisée (custom view)

IV-A - Créer une vue

En étendant la classe View ou une de ses sous-classes, vous pouvez créer votre propre vue personnalisée.

Pour dessiner la vue, utilisez la méthode onDraw(). Avec celle-ci, vous recevez un Canvas Object, lequel vous permet d'effectuer dessus, des opérations de tracé. Ex.  tracer des lignes, un cercle, du texte ou des bitmaps. Si la vue doit être redessinée, appelez la méthode invalidate(), laquelle déclenche un appel à la méthode onDraw() de la vue.

Si vous définissez vos propres vues, assurez-vous de consulter la classe ViewConfiguration, car elle contient plusieurs constantes de définition de vues.

Pour dessiner vos vues, vous utilisez généralement l'API Canvas 2D.

IV-B - Mesure

Le layout manager appelle la méthode onMeasure() de la vue. La vue reçoit le paramètre de layout depuis le layout manager. Un layout manager a la charge de déterminer la taille de tous ses enfants.

La vue doit appeler la méthode setMeasuredDimenstion(int, int) avec le retour de la fonction.

IV-C - Définir des custom layout managers

Vous pouvez implémenter votre layout manager personnalisé en étendant la classe ViewGroup. Cela vous permet d'implémenter de façon plus efficace des layout managers ou d'implémenter des effets actuellement inexistants dans la plate-forme Android.

Un layout manager personnalisé peut surcharger les méthodes onMeasure() et onLayout(), et spécialiser le calcul de ses enfants. Par exemple, il peut omettre le support de consommation du temps de layout_weight de la classe LinearLayout.

Pour calculer la taille d'un enfant, vous pouvez utiliser la méthode measureChildWithMargins() de la classe ViewGroup.

Stocker des paramètres de layout dans une classe interne à votre implémentation de ViewGroup est une bonne pratique. Par exemple, ViewGroup.LayoutParams implémente les commandes de paramètres de layout et LinearLayout.LayoutParams implémente les paramètres additionnels spécifiques à LinearLayout, comme par exemple, le paramètre layout_weight.

V - Cycle de vie

V-A - Cycle de vie des événements liés à la fenêtre

Une vue est affichée si elle est attachée à une hiérarchie de layout attachée elle-même à une fenêtre. Une vue a plusieurs ancres de cycle de vie.

La méthode onAttachedToWindow() est appelée une fois la fenêtre accessible.

La méthode onDetachedFromWindow() est utilisée quand la vue est retirée de son parent (et si le parent est détaché d'une fenêtre). Ceci se produit si l'activité est recyclée, par exemple (ex. via l'appel à la méthode finished()) ou si la vue est recyclée dans une ListView. La méthode onDetachedFromWindow() peut être utilisée pour stopper les animations et nettoyer les ressources utilisées par la vue.

V-B - Parcours des événements du cycle de vie

Le parcours des événements du cycle de vie consiste à animer, mesurer, placer et dessiner.

Toutes les vues doivent savoir comment se mesurer et se placer elles-mêmes. La méthode requestLayout() peut indiquer à la vue de se mesurer et de se repositionner elle-même.Comme cette opération peut influer sur le layout d'autres vues, elle appelle aussi la méthode requestLayout() de son parent.

Cet appel récursif est la raison pour laquelle vous ne devriez pas gérer vous-même, depuis la vue, les profondeurs de vues comme les mesures et opérations des layout qui peuvent être très coûteuses, si énormément de hiérarchies de vues sont recalculées.

La méthode onMeasure() détermine la taille pour la vue et ses enfants, elle doit régler la dimension via la méthode setMeasuredDimension(), qui est appelée avant son retour.

Onlayout() positionne les vues sur la base du résultat de l'appel à la méthode onMeasure(), Cet appel se produit une fois, c'est pourquoi onMeasure() ne peut se produire qu'une fois.

V-C - Cycle de vie d'une activité

Les vues n'ont pas accès aux événements du cycle de vie des activités. Si les vues doivent être informées des événements, vous devez créer une interface dans la vue à partir de laquelle vous voulez appeler les méthodes du cycle de vie de l'activité.

VI - Définir des attributs supplémentaires pour votre vue personnalisée

Vous pouvez définir des attributs supplémentaires pour vos vues personnalisées ou composées. Pour définir des attributs supplémentaires, créez un fichier attrs.xml dans votre dossier res/values. La suite montre un exemple d'attributs définis pour une nouvelle vue nommée ColorOptionsView.

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ColorOptionsView">
<attr name="titleText" format="string" localization="suggested" />
<attr name="valueColor" format="color" />
</declare-styleable>
</resources>

Pour utiliser ces attributs dans votre fichier layout, vous devez déclarer ceux-ci dans l'en-tête XML. Dans le listing suivant, ceci est fait via la partie du code xmlns:custom. Ces attributs sont aussi assignés à la vue.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<!-- défini un nouvel espace de noms pour vos attributs -->
xmlns:custom="http://schemas.android.com/apk/res/com.vogella.android.view.compoundview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<!-- Supposons que ceci soit votre nouveau composant. Il utilise vos nouveaux attributs -->
<com.vogella.android.view.compoundview.ColorOptionsView
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
custom:titleText="Background color"
custom:valueColor="@android:color/holo_green_light"
/>
</LinearLayout>

L'exemple suivant montre comment vos composants peuvent accéder à ces attributs.

package com.vogella.android.view.compoundview;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ColorOptionsView extends View {

private View mValue;
private ImageView mImage;

public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);

TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.Options, 0, 0);
String titleText = a.getString(R.styleable.Options_titleText);
int valueColor = a.getColor(R.styleable.Options_valueColor,
android.R.color.holo_blue_light);
a.recycle();

// plus de choses ...
}
}

VII - Exercice : vue composée

VII-A - Créer un projet

Créez un nouveau projet Android avec les données suivantes :

Tableau 1. Nouveau projet Android

Propriété

Valeur

Application Name

Exemple vue composée

Project Name

com.vogella.android.customview.compoundview

Package name

com.vogella.android.customview.compoundview

API (Minimum, Target, Compile with)

Latest

Template

Empty Activity

Activity

MainActivity

Layout

activity_main

VII-B - Définir et utiliser des attributs supplémentaires

Créez le fichier d'attributs attrs.xml suivant dans votre dossier res/values :

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Options">
<attr name="titleText" format="string" localization="suggested" />
<attr name="valueColor" format="color" />
</declare-styleable>
</resources>

Changez le fichier layout utilisé par l'activité, par celui-ci :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res/com.vogella.android.view.compoundview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:attr/listDivider"
tools:context=".MainActivity" >

<com.vogella.android.view.compoundview.ColorOptionsView
android:id="@+id/view1"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:selectableItemBackground"
android:onClick="onClicked"
custom:titleText="Background color"
custom:valueColor="@android:color/holo_green_light"
/>
<com.vogella.android.view.compoundview.ColorOptionsView
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:background="?android:selectableItemBackground"
android:onClick="onClicked"
custom:titleText="Foreground color"
custom:valueColor="@android:color/holo_orange_dark"
/>
</LinearLayout>

VII-C - Créer une vue composée

Créez le fichier de layout suivant, nommé view_color_options.xml pour votre vue composée :

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:textSize="18sp"
/>
<View
android:layout_width="26dp"
android:layout_height="26dp"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:layout_centerVertical="true"
android:visibility="gone"
/>
</merge>

Créez la vue composée suivante :

package com.vogella.android.customview.compoundview;

import com.vogella.android.view.compoundview.R;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class ColorOptionsView extends LinearLayout {

private View mValue;
private ImageView mImage;

public ColorOptionsView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ColorOptionsView, 0, 0);
String titleText = a.getString(R.styleable.ColorOptionsView_titleText);
int valueColor = a.getColor(R.styleable.ColorOptionsView_valueColor,
android.R.color.holo_blue_light);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_color_options, this, true);
TextView title = (TextView) getChildAt(0);
title.setText(titleText);
mValue = getChildAt(1);
mValue.setBackgroundColor(valueColor);
mImage = (ImageView) getChildAt(2);
}

public ColorOptionsView(Context context) {
this(context, null);
}
public void setValueColor(int color) {
mValue.setBackgroundColor(color);
}
public void setImageVisible(boolean visible) {
mImage.setVisibility(visible ? View.VISIBLE : View.GONE);
}
}

VII-D - Create Activity

Changez votre activité par le code suivant et lancez votre application.

package com.vogella.android.customview.compoundview;

import com.vogella.android.view.compoundview.R;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Étend le menu ; ceci ajoute des items à la barre d'actions si elle est présente.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
public void onClicked(View view) {
String text = view.getId() == R.id.view1 ? "Arrière-plan" : "Avant-plan";
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
}

L'application lancée devrait ressembler à la capture d'écran suivante :

VIII - l'API Canvas

VIII-A - Vue d'ensemble

L'API Canvas permet de créer des effets graphiques complexes.

Vous peignez sur une surface bitmap. La classe Canvas fournit les méthodes de tracé pour dessiner sur un bitmap et la classe Paint spécifie comment le dessin est effectué sur le bitmap.

VIII-B - La classe Canvas

L'objet Canvas contient le bitmap sur lequel vous dessinez. Il fournit également les méthodes pour les opérations de dessin, ex. drawARGB() pour dessiner avec une couleur précise, drawBitmap() pour dessiner un bitmap, drawText() pour dessiner un texte, drawRoundRect() pour tracer un rectangle avec des coins arrondis et bien plus.

VIII-C - La classe Paint

Pour dessiner sur un objet Canvas, vous utilisez un objet de type Paint.

La classe Paint permet de spécifier la couleur, la police de caractères et certains effets pour les opérations de dessin.

La méthode setStyle() permet quant à elle de spécifier l'effet barré (Paint.Style.STROKE), le remplissage (Paint.Style.FILL) ou les deux (Paint.Style.STROKE_AND_FILL).

Vous pouvez affecter le canal Alpha du dessin via la méthode setAlpha().

Via les Shaders, vous pouvez définir si le dessin est rempli avec plus d'une couleur.

VIII-D - Shader

Un shader permet de définir le contenu qui peut être dessiné sur un objet Paint. Par exemple, vous pouvez utiliser un BitmapShader pour définir qu'un bitmap doit être utilisé pour dessiner. Ceci vous permet, par exemple, de dessiner une image avec des coins arrondis. Définissez simplement un BitmapShader pour votre objet Paint et utilisez la méthode drawRoundRect() pour dessiner un rectangle avec des coins arrondis.

Les autres Shaders fournis par la plate-forme Android sont LinearGradient, RadialGradient et SweepGradient pour dessiner des couleurs dégradées.

Pour utiliser un Shader, assignez-le à votre objet Paint via la méthode setShader().

Si la zone remplie est plus grande que le Shader, vous pouvez définir via le Shader tile mode, comment le reste doit être rempli. La constante Shader.TileMode.CLAMP définit que les coins des bords devraient être utilisés pour remplir l'espace supplémentaire, la constante Shader.TileMode.REPEAT définit que l'image sera répétée.

IX - Données de vue persistante

La plupart des vues standards peuvent sauvegarder leur état, et donc peuvent être gardées persistantes par le système. Le système Android appelle la méthode onSaveInstanceState() et onRestoreInstanceState(Parcable) pour sauvegarder et restaurer l'état de la vue.

La règle appliquée par convention consiste à étendre View.BasedSavedState, comme une classe interne statique dans la vue, pour la persistance des données.

Android recherche l'ID de la vue à partir du layout et passe un bundle à la vue permettant la restauration de son état.

Vous devez sauvegarder et restaurer l'interface utilisateur telle que l'utilisateur l'a laissé. Ex. la position des scrollings et la sélection active.

X - Exemple pour une vue personnalisée

Voici un exemple de vue personnalisée à l'URL suivante :

Android custom views and touch.

XI - Liens et documentation

XI-A - Code source

Codes source d'exemple

XI-B - Ressources vues personnalisées

Exemple Canvas étendu

Code source pour une listView utilisant des effets 3D

XI-C - Ressources Android générales

Android Location API and Google Maps

Android and Networking

Android Homepage

Android Issues / Bugs

Android Google Groups

XII - Remerciements Developpez

Vous pouvez retrouver l'article original à l'adresse Creating custom and compound Views in Android - Tutorial. Nous remercions Lars Vogel qui nous a aimablement autorisés à traduire et héberger ses articles.

Nous remercions aussi chrtophe pour sa traduction, ainsi que jacques_jean pour sa relecture orthographique.