Réaliser une custom ProgressBar/SeekBar sur Android

| Axon de Tuto Mobile | Djug |

Dans cet article, on va faire d'une pierre deux coups, on va apprendre à modifier la SeekBar (comparable au UISlider sur iPhone, pour ceux qui connaissent) qui peut s'avérer utile comme timeline sur un lecteur audio/vidéo, et la ProgressBar Horizontale (et uniquement l'horizontale, il existe en effet des ProgressBar dites « indéterminées » que l'on peut comparer à ce qu'on appelle un loader, ou icône de chargement), qui peut être utilisée pour observer la progression d'un téléchargement par exemple.
Commentez ♪

- Titre : Réaliser une custom ProgressBar/SeekBar sur Android

- Auteurs : Axon de Tuto Mobile, Djug

- Parution : 11 mars 2011

- Licence : Copyright ® 2011 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation

- II. ProgressBar (horizontale)

- II-1. Layout

- II-2. les fichiers Style

- II-3. les Layer-List

- II-4. Résultat

- III. SeekBar

- III-1. Présentation

- III-2. Style

- III-3. Selector

- III-4. Quel ThumbOffSet choisir ?

- III-5. Layout

- III-6. Résultat

- IV. Remerciements

- V. Lien

I - Présentation

Nous allons voir dans ce tutoriel, comment customiser la progressBar ainsi que la SeekBar. Ces deux widgets se ressemblent beaucoup et pour cause : ils utilisent le même design (pas du tout à mon goût).

II - ProgressBar (horizontale)

II-1 - Layout

Une ProgressBar par défaut se constitue d'une image de type loading, qu'on pourra retrouver dans les fichiers images du SDK sous le nom de « spinner » (exemple : spinner_black_48.png).
Pour obtenir une ProgressBar Horizontale (que l'on abrégera par PBH pour la suite de notre article), il suffit d'ajouter un style bien précis à la ProgressBar que l'on a déclarée dans le fichier XML de notre Activity :

<ProgressBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"
android:progress="50"
android:max="100"
android:secondaryProgress="80"/>

On remarquera ici plusieurs attributs pour notre PBH :

* Max : permet de définir la valeur totale de la progression de notre barre, je mets toujours 100 pour avoir une notion de pourcentage dans nos calculs, ce qui les simplifiera ;
* Progress : indique tout simplement le niveau de progression ;
* secondProgress : indique un deuxième niveau de progression, ce qui peut être utile dans une écoute de musique en streaming par exemple, où le progress indique la progression de lecture, et le secondProgress indique la progression du téléchargement ;
* Style : le style de notre barre, il fait appel ici à l'attribut de style (un attribut de style est en fait une référence/un pointeur de style) de la PBH native d'Android.

II-2 - les fichiers Style

C'est ici que nous allons intervenir, nous allons étendre le style de la PBH (comme si l'on étendait une classe Java) et en l'appliquant à notre progressBar. La documentation des styles sur Android est très faible, elle ne fait qu'énumérer les styles disponibles, sans vraiment nous indiquer à quoi ils correspondent et comment ils sont construits, nous allons donc faire un tour dans les sources pour nous documenter.

Comme l'indique le fichier themes.xml des sources :

<item name="progressBarStyleHorizontal">@android:style/Widget.ProgressBar.Horizontal</item>

L'attribut de style progressBarStyleHorizontal fait référence au style Widget.ProgressBar.Horizontal, où l'on trouvera les spécifications dans le fichier styles.xml :

<style name="Widget.ProgressBar.Horizontal">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
</style>

On ne va pas s'intéresser ici à « indeterminateOnly » et à « indeterminateDrawable », qui concernent les PBH qui n'indiquent pas l'évolution de progression de la barre (mais vous pouvez également modifier l'image si ça vous chante).

Pour modifier ce style, rien de plus simple !

On va créer un fichier styles.xml dans notre projet, et y insérer notre nouveau style en indiquant qu'il est une extension de Widget.ProgressBar.Horizontal :

<resources>
<style name="CustomProgressBarHorizontal" parent="android:Widget.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/custom_progress_bar_horizontal</item>
<item name="android:minHeight">10dip</item>
<item name="android:maxHeight">20dip</item>
</style>
</resources>

II-3 - les Layer-List

Par manque d'imagination, j'ai appelé ce nouveau style CustomProgressBarHorizontal. Vous remarquerez que j'ai modifié la hauteur minimum (minHeight) pour avoir une barre plus fine, et que j'ai modifié l'image qui est utilisée en tant que progressDrawable (qui en fait un fichier xml, qui définit les images de fond, de progress, et de secondaryProgress de notre barre).

Et là, PAF ! Eclipse (désolé pour ceux qui utilise un autre IDE) n'est pas content et nous indique que le drawable custom_progress_bar_horizontal n'existe pas, ce qui est plutôt logique, puisqu'on ne l'a pas encore créée.

Une fois de plus, on va s'aider des sources pour comprendre comment la classe ProgressBar utilise le fichier xml, il suffit ici d'aller fouiller dans les fichiers de notre SDK : %ANDROID_SDK_PATH%/platforms/android-n/data/res/drawable/ (où n est la version de l'API).

Le fichier progress_horizontal est un fichier XML contenant des drawables, des classes bien pratiques permettant de faire des dessins plus ou moins basiques (dégradé, angle, cercle, etc.) à l'aide du XML, je vous conseille d'aller faire un tour dans la documentation des drawables pour vous y familiariser.

Dans ce fichier, on y retrouve un layer-list, qui est en fait un tableau d'autres drawables. Ces drawables sont dessinés les uns au-dessus des autres en suivant l'ordre dans lequel ils sont écrits dans le fichier : donc pour notre PBH, il faudra donc dessiner le background, le secondaryProgress et le progress pour finir :

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ff9d9e9d"
android:centerColor="#ff5a5d5a"
android:centerY="0.75"
android:endColor="#ff747674"
android:angle="270" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#80ffd300"
android:centerColor="#80ffb600"
android:centerY="0.75"
android:endColor="#a0ffcb00"
android:angle="270" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ffffd300"
android:centerColor="#ffffb600"
android:centerY="0.75"
android:endColor="#ffffcb00"
android:angle="270" />
</shape>
</clip>
</item>
</layer-list>

Quand on regarde de plus près, on remarque que ces drawables sont de simples rectangles (shapes) comprenant des coins arrondis et un dégradé.

Pour modifier notre PBH, on va ici faire simple et modifier les dégradés (les simplifier et changer les couleurs), cela peut sembler un peu simple, mais cela suffira à modifier notre PBH pour qu'elle puisse correspondre au thème général de votre application et qu'elle soit identique sur tous les terminaux.

Ce qui nous donne les codes suivants, avec un thème bleu à notre PBH :

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ffffffff"
android:centerColor="#ffdddddd"
android:centerY="0.50"
android:endColor="#ffffffff"
android:angle="270" />
</shape>
</item>
<item android:id="@android:id/secondaryProgress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#770e75af"
android:endColor="#771997e1"
android:angle="90" />
</shape>
</clip>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="5dip" />
<gradient
android:startColor="#ff0e75af"
android:endColor="#ff1997e1"
android:angle="90" />
</shape>
</clip>
</item>
</layer-list>

II-4 - Résultat

On aura donc un fond blanc avec un léger dégradé vers le gris en son centre. On laisse les coins arrondis (border-radius) à 5dip, ce qui nous fera l'effet un demi-cercle sur les extrémités de notre PBH si on lui fixe un layout_height à wrap_content (eh oui, n'oubliez pas qu'on a mis le minHeight de notre style à 10dip).
En appliquant notre style à la PBH, on aura donc le rendu suivant :

Figure 1 - Custom ProgressBar Horizontale

Figure 2 - ProgressBar type 'loader'

Remarque : cela ne vous coûte rien de garder les mêmes ID pour vos drawables (android.R.id.progress par exemple), ils sont utilisés dans la classe ProgressBar.java des sources, cela vous évitera quelques petits soucis.

III - SeekBar

III-1 - Présentation

Quand on regarde le visuel d'une SeekBar, ce n'est rien d'autre qu'une progressBar Horizontale avec un petit « sélectionneur » en plus : chez Google, ils l'ont baptisé thumb (traduction anglaise de « pouce »).

Libre à chacun d'apprécier ou pas le design de ce thumb, personnellement, ce n'est toujours pas mon cas.

III-2 - Style

Je ne vais bien entendu pas m'étendre ici sur la modification de la barre de progression, il suffit de reprendre la partie sur la ProgressDialog de cet article, mais je vous ferai juste remarquer que si l'on regarde une fois de plus les sources, le progressDrawable de la seekBar fait appel au même fichier que la PBH :

<style name="Widget.SeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">@android:drawable/progress_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumb">@android:drawable/seek_thumb</item>
<item name="android:thumbOffset">8dip</item>
<item name="android:focusable">true</item>
</style>

On va s'intéresser ici aux items « thumb » et « thumbOffset » (décalage du thumb).
La customisation du thumb va se faire de la même manière que celle de la PBH, on va étendre le style du widget SeekBar, en modifiant le drawable du thumb.

Remarque : il est également possible de changer le thumb et le thumbOffSet directement dans les attributs d'une SeekBar lors sa déclaration dans le layout de notre Activity, mais je trouve plus propre le fait de le faire dans l'extension du style, de plus, si vous avez plusieurs SeekBar, vous n'aurez pas à modifier leurs attributs à chaque fois, c'est le style que vous aurez appliqué qui s'en chargera.

Dans notre nouveau style de SeekBar, on change donc notre thumb :

<item name="android:thumb">@drawable/seek_bar_thumb</item>

III-3 - Selector

Et, comme dans les sources, ce fichier sera un fichier XML comprenant un <selector>, un autre drawable (stateListDrawable) qui définit plusieurs autres drawables selon l'état du widget, des états qui dépendent la plupart du temps des actions de l'utilisateur : focus, pressed, selected, etc. :

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/custom_thumb_state_pressed" />
<item android:state_focused="true"
android:drawable="@drawable/custom_thumb_state_selected" />
<item android:state_selected="true"
android:drawable="@drawable/custom_thumb_state_selected" />
<item android:drawable="@drawable/custom_thumb_state_default" />

</selector>

Ces quatre items, dont les noms parlent d'eux-mêmes, sont dans notre cas, d'autres fichiers XML, comprenant des formes (shapes), comme on a pu les rencontrer dans la première partie de cet article (cf. documentation des drawables).

Voici par exemple celui qui décrit l'état « normal » (par défaut) de notre thumb (les deux autres sont quasiment identiques, juste les couleurs définies sont modifiées) :

<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="35dip"
android:height="35dip" />
<stroke
android:width="1dip"
android:color="#ffffffff" />
<gradient
android:startColor="#ffcdcdcd"
android:endColor="#fff8f8f8"
android:angle="270"
android:type="linear" />
</shape>

Un simple disque, de rayon 35dip, avec une bordure et un dégradé :

Figure 3 - SeekBar avec thumb par défaut

Figure 4 - seekBar etat focused

Figure 5 - seekBar état pressed

Figure 6 - seekbar état normal

Lorsqu'on glisse le sélecteur au fond à droite, on remarque que l'image est coupée, chose qui se reproduira du côté gauche, et c'est là qu'intervient le thumbOffSet.

III-4 - Quel ThumbOffSet choisir ?

Le style de la SeekBar android, qu'on a pu voir plus haut dans cet article, définit un thumbOffSet par défaut à 8dip, pourquoi 8dip ? Il n'y a qu'à regarder les fichiers images utilisés pour le thumb de base !

Pour cela, sur votre poste, regardez de plus près

%ANDROID_SDK_PATH%/platforms/android-7/data/res/drawable-mdpi/seek_thumb_normal.jpg

Pour un terminal avec une densité moyenne, un dip est égal à un pixel, c'est pour cela que j'ai choisi le dossier drawable-mdpi : plus de renseignements sur les densités d'écran ici.

On s'aperçoit que l'image a une largeur de 32 pixels, et qu'elle possède deux zones vides de 8 pixels de chaque côté du thumb qui y est dessiné, ce qui explique le 8 pour le thumbOffset dans le style par défaut des seekBar.

Remarque : rien ne vous empêche effectivement de créer vos « thumb » avec des fichiers images, à l'aide notamment de Photoshop ou Gimp, j'ai choisi ici de les créer à l'aide des drawables android et de leur xml.

Comme nos « thumb » sont des drawables en xml, ils n'auront pas de marges vides, nous allons donc mettre notre thumbOffset à 0dip dans notre style :

<style name="CustomSeekBar" parent="android:Widget.SeekBar">
<item name="android:progressDrawable">@drawable/custom_seek_bar</item>
<item name="android:thumb">@drawable/seek_bar_thumb</item>
<item name="android:thumbOffset">0dip</item>
</style>

III-5 - Layout

Et on l'insère dans le layout de notre Activity :

<SeekBar
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:progress="50"
android:max="100"
style="@style/CustomSeekBar"
android:secondaryProgress="80"
android:layout_margin="5dip" />

III-6 - Résultat

Il n'y a plus qu'à admirer le résultat final de notre article !

IV - Remerciements

Je tiens à remercier Alex (dit Sakaroz) qui a écrit ce tutoriel Android très intéressant. Je vous invite à aller faire un tour sur son blog : http://blog.sakaroz.com/ et son site pro : http://www.sakaroz.com/
Et je tiens à remercier tout particulièrement Feanorin qui a mis ce tutoriel au format Developpez.com.
Merci également à Claude Leloup d'avoir pris le temps de le relire et de le corriger.

V - Lien

Tutoriel origine sur Tuto Mobile