Réaliser une custom ProgressBar/SeekBar sur Android [Tutoriel Android n°24]
Dans cet article, on va faire 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/video, et la ProgressBar Horizontale (et uniquement l’horizontale, il existe en effet des ProgressBar dites « indéterminée » que l’ont 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.
Ces deux widget se ressemblent beaucoup et pour cause : ils utilisent le même design (pas du tout à mon goût).
A l’assaut des styles de la ProgressBar (Horizontale !)
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é 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, ou 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 appelle 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.
Retour aux sources…
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’indiquer 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 fichiers 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 à “inderterminateOnly” et à “indeterminateDrawable », qui concerne les PBH qui n’indique 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>
Par manque d’imagination, j’ai appelé ce nouveau style CustomProgressBarHorizontal. Vous remarquerez que j’ai modifié la hauteur minimum (minHeight) pour avoir la possibilité d’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 drawable, 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 du la documentation des drawable pour vous y familiariser.
Dans ce fichier, on y retrouve un layer-list, qui est en fait un tableau d’autres drawable. 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 corresponde au thème général de votre application et qu’elle soit identique sur tous les terminaux.
Ce qui nous donne les codes suivant, 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>
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 (et 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 :
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ées dans la classe ProgressBar.java des sources, cela vous évitera quelques petits soucis.
Customisation de la SeekBar
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.
Je ne vais bien entendu pas m’étaler ici sur la modification de la barre de progression, il suffit de reprendre la première partie 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 à ses 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>
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 4 items, dont les noms parlent d’eux même, sont dans notre cas d’autres fichiers XML, comprenant des formes (shape), comme on a pu les rencontrer dans la première partie de cette article (cf. documentation des drawable ).
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é :
Lorsque l’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.
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ées 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 choisis le dossier drawable-mdpi : plus de renseignements sur les densité d’écran ici).
On s’aperçoit que l’image à une largeur de 32 pixel, et qu’elle possède deux zones de vide 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 image, à l’aide notamment de Photoshop ou Gimp, j’ai choisis ici de les créer à l’aide des drawable android et de leur xml.
Comme nos thumb sont des drawable 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>
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" />
Il n’y a plus qu’à admirer le résultat final de notre article !
Vous pouvez trouver les sources du projet ici.
Je tenais à remercier Alex (dit Sakaroz) qui a écrit ce tutorial 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/

Cette création par Tuto Mobile est mise à disposition selon les termes de la licence Creative Commons Paternité - Pas d'Utilisation Commerciale - Partage des Conditions Initiales à l'Identique 3.0 Unported.
Encore un peu de lecture :
-
Chronologie #SFRGate : SFR et les Galaxy Nexus simlockés -
[Concours] Gagner 4 formations vidéos pour apprendre le développement Android -
Gestion des Preferences [Tutoriel Android n°25]














Dans mon cas, la seule fois où j’ai essayé d’utiliser une progressBar c’était dans une customDialog, et dès que j’essayais de la mettre à jour l’application plantait…
Je trouve dommage de ne pas trouver de code java dans ce tutorial… :/
Quelle erreur avais tu pour ton plantage ?
Tu ne devrais pas trouver dommage de ne pas avoir de java pour la customisation de widget : je m’explique
je pense que c’est un un point positif d’Android de ne pas toucher à la logique applicative lorsque l’on a envie de modifier l’UI, c’est le cas ici et c’est en partie ce que j’ai voulais montrer, on a complètement modifier les progressBar sans toucher une ligne de code java, et je trouve ça plutôt pratique et clair pour les développeurs.
Ensuite, pour ce qui est de l’utilisation de ces widget, ce n’était pas le sujet de cet article
Je ne sais plus l’erreur que j’avais, il faudrait que je m’y remette… ça fait déjà quelques mois ^^ »
Savez-vous comment faire un text separator entre plusieurs checkbox:
2 options checkbox et un text separator(comme titre pour les 2 prochaines options).
Merci à vous.
Pour un tutoriel en réalité augmenté, je propose un truc du genre d’une application que j’ai faite (Boussole et Carte) qui est sur l’android market. Il y a une version lite (Boussole et Carte lite) qui permet d’utiliser les fonctions de réalité augmentée. Cette fonctionnalité est détaillée dans l’aide de l’application. Si ça t’intéresse toi et d’autres tutomobile addicts, je t’envoie tout ça.
Excellent !
Bonjour,
Tout d’abord, bravo pour cet excellent tutoriel, très bien expliqué, tout niquel !
Maintenant, j’aimerais réaliser une seekbar ainsi qu’un progressbar, verticale cette fois ci ! Seulement étant débutant, je n’arrive pas encore à très bien me débrouiller… Pourriez vous m’aider ?
Merci d’avance.
La progressBar verticale n’est pas fournie par Android par défaut, il te faut créer une progressBar verticale toi même
Bonjour, merci pour le tuto
je voudrais savoir comment je pourrais lier cette barre de progression à un processus téléchargement que je voudrais effectuer.
En fait dans les tutos que j’ai lu, progressbar se crée en JAVA notamment
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(« Downloading file.. »); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
return mProgressDialog;
alors que dans notre cas ça se crée en XML
Merci
Bonjour,
merci pour tout ces tutos
je voulais savoir comment il etait possible de refresh une progressBar qui se trouve dans une notification (sans recréer un notification qui fera lager le téléphone)
merci
Salut,
je suis a la recherche d’une seekbar spécifique. Je sais pas si elle existe, mais j’aimerai faire comme l’iphone avec les points qui permet de savoir à quel page on est.
Pensez vous cela possible?
Plop !
Just un petit commentaire pour dire merci, car ce tuto m’a fort dépanné. Effectivement, la doc officielle sur le sujet est juste lamentable. Ce post m’a donc évité quelques arrachages de cheveux (déjà qu’il ne m’en reste plus beaucoup…)
C’est exactement ce que je cherchais mais je ne comprends pas comment il faut l’utiliser.
J’ai juste besoin de la progress bar.
J’ai créé un fichier styles.xml dans mon projet et j’y ai ajouté le code XML ressources comme indiqué mais ensuite? que faire du code XML layer-list avec les shapes ?
ah et by the way, le styles.xml il faut le mettre où? dans drawable?
Bon ben j’ai trouvé: styles.xml va dans res/values/ et les shapes dans un fichier nommé custom_progress_bar_horizontal.xml et placé dans drawable.
Vraiment très bon tuto, simple et claire. Il m’as beaucoup aider, merci !
slt
j’ai une application android avec une carte arduino ADK et je veut afficher le signal cardiographie(ECG) sur un téléphone android .
j’ai une problème qu niveaux de la laison entre la carte arduino et le téléphone.