Sie sind auf Seite 1von 21

20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

FK

Accueil > Cours > Créez des applications pour Android > TP : un explorateur de chiers

Créez des applications pour Android

20 heures  Moyenne

Mis à jour le 26/02/2020

 

TP : un explorateur de chiers
Petit à petit, on se rapproche d'un contenu qui pourrait s'apparenter à celui des applications
professionnelles. Bien entendu, il nous reste du chemin à parcourir, mais on commence à vraiment
voir comment fonctionne Android !

A n de symboliser notre entrée dans les entrailles du système, on va s'affairer ici à déambuler dans
ses méandres. Notre objectif : créer un petit explorateur qui permettra de naviguer entre les chiers
contenus dans le terminal et faire en sorte de pouvoir exécuter certains de ces chiers.

Objectifs 
Spéci cations techniques

Activité principale

Un nouveau genre d'activité

La première chose à faire est de véri er qu'il est possible de lire la carte SD avec les méthodes vues
aux chapitres précédents. S'il est bien possible de lire la carte, alors on af che la liste des chiers du
répertoire, ce qui se fera dans une ListView . Cependant, comme notre mise en page sera
uniquement constituée d'une liste, nous allons procéder différemment par rapport à d'habitude. Au
lieu d'avoir une activité qui af che un layout qui contient une ListView , on va remplacer notre

Activity par une ListActivity . Comme l'indique le nom, une ListActivity est une activité

qui est principalement utilisée pour af cher une ListView Comme il s'agit d'une classe qui
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 1/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
qui est principalement utilisée pour af cher une ListView . Comme il s agit d une classe qui

dérive de Activity , il faut la traiter comme une activité normale, si ce n'est que vous n'avez pas

besoin de préciser un layout avec void setContentView (View view) , puisqu'on sait qu'il n'y a
qu'une liste dans la mise en page. Elle sera alors ajoutée automatiquement.

Il est possible de récupérer la ListView qu'af che la ListActivity à l'aide de la méthode

ListView getListView () . Cette ListView est une ListView tout à fait banale que vous
pouvez traiter comme celles vues dans le cours.

Adaptateur personnalisé

On associera les items à la liste à l'aide d'un adaptateur personnalisé. En effet, c'est la seule solution
pour avoir deux couleurs dans les éléments de la liste. On n'oubliera pas d'optimiser cet adaptateur
a n d'avoir une liste uide. Ensuite, on voudra que les éléments soient triés de la manière suivante :

Les répertoires en premier, les chiers en second.


Dans chacune de ces catégories, les éléments sont triés dans l'ordre alphabétique sans tenir
compte de la casse.

Pour cela, on pourra utiliser la méthode void sort (Comparator<? super T> comparator) qui
permet de trier des éléments en fonction de règles qu'on lui passe en paramètres. Ces règles
implémentent l'interface Comparator de manière à pouvoir dé nir comment seront triés les
objets. Votre implémentation de cette interface devra redé nir la méthode
int compare(T lhs, T rhs) dont l'objectif est de dire qui est le plus grand entre lhs et rsh .
Si lhs est plus grand que rhs , on renvoie un entier supérieur à 0, si lhs est plus petit que

rhs , on renvoie un entier inférieur à 0, et s'ils sont égaux, on renvoie 0. Vous devrez véri er que
cette méthode respecte la logique suivante :

compare(a,a) renvoie 0 pour tout a parce que a==a .

compare(a,b) renvoie l'opposé de compare(b,a) pour toutes les paires ( a , b ) (par


exemple, si a > b , alors compare(a,b) renvoie un entier supérieur à 0 et compare(b,a)
un entier inférieur à 0).

Si compare(a,b) > 0 et compare(b,c) > 0 , alors compare(a,c) > 0 quelque que soit la
combinaison ( a , b , c ).

Je comprends que ce soit un peu compliqué à comprendre, alors voici un exemple qui trie les
entiers :
java

1 import java.util.Comparator;
2
3 public class EntierComparator implements Comparator<Integer> {
4 @Override
5 public int compare(Integer lhs, Integer rhs) {
6 // Si lhs est supérieur à rsh, alors on retourne 1
7 if(lhs > rhs)
8 return 1;
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 2/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
;
9 // Si lhs est inférieur à rsh, alors on retourne -1
10 if(lhs < rhs)
11 return -1;
12 // Si lhs est égal à rsh, alors on retourne 0
13 return 0;

14 }
15 }
16

Ensuite, dans le code, on peut l'utiliser pour trier un tableau d'entiers :


java

1 // Voici un tableau avec des entiers dans le mauvais ordre


2 Integer[] tableau = {0, -1, 5, 10, 9, 5, -10, 8, 21, 132};
3
4 // On convertit le tableau en liste
5 List<Integer> entiers = new ArrayList<Integer>(Arrays.asList(tableau));
6
7 // On écrit tous les entiers dans le Logcat, ils sont dans le désordre !
8 for(Integer i : entiers)
9 Log.d("Avant le tri", Integer.toString(i));
10
11 // On utilise une méthode qui va trier les éléments de la liste
12 Collections.sort(entiers, new EntierComparator());
13
14 // Désormais, les entiers seront triés !
15 for(Integer i : entiers)
16 Log.d("Après le tri", Integer.toString(i));
17
18 //La liste contient désormais {-10, -1, 0, 5, 5, 8, 9, 10, 21, 132}
19

Préférences
Nous n'avons qu'une préférence ici, qui chez moi a pour identi ant repertoireColorPref et qui
contient la couleur dans laquelle nous souhaitons af cher les répertoires.

Comme il n'existe pas de vue qui permette de choisir une couleur, on va utiliser une vue développée
par Google dans ses échantillons et qui n'est pas incluse dans le code d'Android. Tout ce qu'il faut
faire, c'est créer un chier Java qui s'appelle ColorPickerView et d'y insérer le code suivant :
java

1 import android.content.Context;
2 import android.graphics.Canvas;
3 import android.graphics.Color;
4 import android.graphics.ColorMatrix;
5 import android.graphics.Paint;
6 import android.graphics.RectF;
7 import android.graphics.Shader;
8 import android.graphics.SweepGradient;
9 import android.view.MotionEvent;
10 import android.view.View;
11
12 public class ColorPickerView extends View {
13 public interface OnColorChangedListener {
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 3/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
14 void colorChanged(int color);
15 }
16
17 private Paint mPaint;
18 private Paint mCenterPaint;

19 private final int[] mColors;


20 private OnColorChangedListener mListener;
21
22 ColorPickerView(Context c, OnColorChangedListener l, int color) {
23 super(c);
24 mListener = l;
25 mColors = new int[] {0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFF00,
0xFFFF0000};
26 Shader s = new SweepGradient(0, 0, mColors, null);
27
28 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
29 mPaint.setShader(s);
30 mPaint.setStyle(Paint.Style.STROKE);
31 mPaint.setStrokeWidth(32);
32
33 mCenterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
34 mCenterPaint.setColor(color);
35 mCenterPaint.setStrokeWidth(5);
36 }
37
38 private boolean mTrackingCenter;
39 private boolean mHighlightCenter;
40
41 @Override
42 protected void onDraw(Canvas canvas) {
43 int centerX = getRootView().getWidth()/2 - (int)(mPaint.getStrokeWidth()/2);
44 float r = CENTER_X - mPaint.getStrokeWidth()*0.5f;
45
46 canvas.translate(centerX, CENTER_Y);
47
48 canvas.drawOval(new RectF(-r, -r, r, r), mPaint);
49 canvas.drawCircle(0, 0, CENTER_RADIUS, mCenterPaint);
50
51 if (mTrackingCenter) {
52 int c = mCenterPaint.getColor();
53 mCenterPaint.setStyle(Paint.Style.STROKE);
54
55 if (mHighlightCenter) {
56 mCenterPaint.setAlpha(0xFF);
57 } else {
58 mCenterPaint.setAlpha(0x80);
59 }
60 canvas.drawCircle(0, 0, CENTER_RADIUS + mCenterPaint.getStrokeWidth(), mCenterPaint);
61
62 mCenterPaint.setStyle(Paint.Style.FILL);
63 mCenterPaint.setColor(c);
64 }
65 }
66
67 @Override
68 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
69 setMeasuredDimension(getRootView().getWidth(), CENTER_Y*2);
70 }
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 4/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
70 }
71
72 private static final int CENTER_X = 100;
73 private static final int CENTER_Y = 100;
74 private static final int CENTER_RADIUS = 32;
75
76 private int floatToByte(float x) {
77 int n = java.lang.Math.round(x);
78 return n;
79 }
80 private int pinToByte(int n) {
81 if (n < 0) {
82 n = 0;
83 } else if (n > 255) {
84 n = 255;
85 }
86 return n;
87 }
88
89 private int ave(int s, int d, float p) {
90 return s + java.lang.Math.round(p * (d - s));
91 }
92
93 private int interpColor(int colors[], float unit) {
94 if (unit <= 0) {
95 return colors[0];
96 }
97 if (unit >= 1) {
98 return colors[colors.length - 1];
99 }
100
101 float p = unit * (colors.length - 1);
102 int i = (int)p;
103 p -= i;
104
105 int c0 = colors[i];
106 int c1 = colors[i+1];
107 int a = ave(Color.alpha(c0), Color.alpha(c1), p);
108 int r = ave(Color.red(c0), Color.red(c1), p);
109 int g = ave(Color.green(c0), Color.green(c1), p);
110 int b = ave(Color.blue(c0), Color.blue(c1), p);
111
112 return Color.argb(a, r, g, b);
113 }
114
115 private int rotateColor(int color, float rad) {
116 float deg = rad * 180 / 3.1415927f;
117 int r = Color.red(color);
118 int g = Color.green(color);
119 int b = Color.blue(color);
120
121 ColorMatrix cm = new ColorMatrix();
122 ColorMatrix tmp = new ColorMatrix();
123
124 cm.setRGB2YUV();
125 tmp.setRotate(0, deg);
126 cm.postConcat(tmp);
127 tmp.setYUV2RGB();
128 tC t(t )
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 5/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
128 cm.postConcat(tmp);
129
130 final float[] a = cm.getArray();
131
132 int ir = floatToByte(a[0] * r + a[1] * g + a[2] * b);
133 int ig = floatToByte(a[5] * r + a[6] * g + a[7] * b);
134 int ib = floatToByte(a[10] * r + a[11] * g + a[12] * b);
135
136 return Color.argb(Color.alpha(color), pinToByte(ir), pinToByte(ig), pinToByte(ib));
137 }
138
139 private static final float PI = 3.1415926f;
140
141 @Override
142 public boolean onTouchEvent(MotionEvent event) {
143 float x = event.getX() - CENTER_X;
144 float y = event.getY() - CENTER_Y;
145 boolean inCenter = java.lang.Math.sqrt(x*x + y*y) <= CENTER_RADIUS;
146
147 switch (event.getAction()) {
148 case MotionEvent.ACTION_DOWN:
149 mTrackingCenter = inCenter;
150 if (inCenter) {
151 mHighlightCenter = true;
152 invalidate();
153 break;
154 }
155 case MotionEvent.ACTION_MOVE:
156 if (mTrackingCenter) {
157 if (mHighlightCenter != inCenter) {
158 mHighlightCenter = inCenter;
159 invalidate();
160 }
161 } else {
162 float angle = (float)java.lang.Math.atan2(y, x);
163
164 float unit = angle/(2*PI);
165 if (unit < 0) {
166 unit += 1;
167 }
168 mCenterPaint.setColor(interpColor(mColors, unit));
169 invalidate();
170 }
171 break;
172 case MotionEvent.ACTION_UP:
173 mListener.colorChanged(mCenterPaint.getColor());
174 if (mTrackingCenter) {
175 mTrackingCenter = false;
176 invalidate();
177 }
178 break;
179 }
180 return true;
181 }
182 }
183

Ce n'est pas grave si vous ne comprenez pas ce code compliqué, il permet juste d'af cher le joli
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 6/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

rond de couleur et de sélectionner une couleur. En fait, la vue contient un listener qui s'appelle
OnColorChangedListener . Ce listener se déclenche dès que l'utilisateur choisit une couleur. A n de

créer un objet de type ColorPickerView , on doit utiliser le constructeur

ColorPickerView(Context c, OnColorChangedListener listener, int color) avec listener le


listener qui sera déclenché dès qu'une couleur est choisie et color la couleur qui sera choisie par
défaut au lancement de la vue.

Notre préférence, elle, sera une boîte de dialogue qui af chera ce ColorPickerView . Comme il
s'agira d'une boîte de dialogue qui permettra de choisir une préférence, elle dérivera de
DialogPreference .

Au moment de la construction de la boîte de dialogue, la méthode de callback


void onPrepareDialogBuilder(Builder builder) est appelée, comme pour toutes les
AlertDialog . On utilise builder pour construire la boîte, il est d'ailleurs facile d'y insérer une
vue à l'aide de la méthode AlertDialog.Builder setView(View view) .

Notre préférence a un attribut de type int qui permet de retenir la couleur que choisit
l'utilisateur. Elle peut avoir un attribut de type OnColorChangedListener ou implémenter elle-

même OnColorChangedListener , dans tous les cas cette implémentation implique de redé nir la
fonction void colorChanged(int color) avec color la couleur qui a été choisie. Dès que
l'utilisateur choisit une couleur, on change notre attribut pour désigner cette nouvelle couleur.

On n'enregistrera la bonne couleur qu'à la fermeture de la boîte de dialogue, celle-ci étant marquée
par l'appel à la méthode void onDialogClosed(boolean positiveResult) avec positiveResult
qui vaut true si l'utilisateur a cliqué sur OK .

Réagir au changement de préférence

Dès que l'utilisateur change de couleur, il faudrait que ce changement se répercute


immédiatement sur l'af chage des répertoires. Il nous faut donc détecter les changements de
con guration. Pour cela, on va utiliser l'interface OnSharedPreferenceChangeListener . Cette
interface fait appel à la méthode de callback
void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) dès qu'un
changement de préférence arrive, avec sharedPreferences l'ensemble des préférences et key
la clé de la préférence qui vient d'être modi ée. On peut indiquer à SharedPreferences qu'on
souhaite ajouter un listener à l'aide de la méthode
void registerOnSharedPreferenceChangeListener
(SharedPreferences.OnSharedPreferenceChangeListener listener)
.

Options
Ouvrir le menu d'options ne permet d'accéder qu'à une option Cliquer sur celle ci enclenche un
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 7/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
Ouvrir le menu d options ne permet d accéder qu à une option. Cliquer sur celle-ci enclenche un
intent explicite qui ouvrira la PreferenceActivity .

Navigation

Il est recommandé de conserver un File qui représente le répertoire courant. On peut savoir si
un chier est un répertoire avec la méthode boolean isDirectory() et, s'il s'agit d'un répertoire,
on peut voir la liste des chiers qu'il contient avec File[] listFiles() .

Pour effectuer des retours en arrière, il faut détecter la pression du bouton adéquat. À chaque fois
qu'on presse un bouton, la méthode de callback
boolean onKeyDown(int keyCode, KeyEvent event) est lancée, avec keyCode un code qui
représente le bouton pressé et event l'évènement qui s'est produit. Le code du bouton
Retour arrière est KeyEvent.KEYCODE_BACK .

Il existe deux cas pour un retour en arrière :

Soit on ne se trouve pas à la racine de la hiérarchie de chier, auquel cas on peut revenir en
arrière dans cette hiérarchie. Il faut passer au répertoire parent du répertoire actuel et ce
répertoire peut se récupérer avec la méthode File getParentFile() .

Soit on se trouve à la racine et il n'est pas possible de faire un retour en arrière. En ce cas, on
propose à l'utilisateur de quitter l'application avec la méthode de Context que vous
connaissez déjà, void finish() .

Visualiser un chier

Nous allons bien entendu utiliser des intents implicites qui auront pour action ACTION_VIEW . Le
problème est de savoir comment associer un type et une donnée à un intent, depuis un chier.
Pour la donnée, il existe une méthode statique de la classe Uri qui permet d'obtenir l'URI d'un
chier : Uri.fromFile(File file) . Pour le type, c'est plus délicat. Il faudra détecter l'extension du
chier pour associer un type qui corresponde. Par exemple, pour un chier .mp3 , on indiquera le
type MIME audio/mp3 . En n, si on veut moins s'embêter, on peut aussi passer le type MIME
audio/* pour chaque chier audio.

Pour rajouter une donnée et un type en même temps à un intent, on utilise la méthode
void setDataAndType(Uri data, String type) , car, si on utilise la méthode void setData(Uri) ,
alors le champ type de l'intent est supprimé, et si on utilise void setType(String) , alors le
champ data de l'intent est supprimé. Pour récupérer l'extension d'un chier, il suf t de récupérer
son nom avec String getName() , puis de récupérer une partie de ce nom : toute la partie qui se
trouve après le point qui représente l'extension :
java

1 fichier.getName().substring(fichier.getName().indexOf(".") + 1)
2

https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 8/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

int indexOf(String str) va trouver l'endroit où se trouve la première instance de str dans la
chaîne de caractères, alors que String substring(int beginIndex) va extraire la sous-chaîne de
caractères qui se situe à partir de beginIndex jusqu'à la n de cette chaîne. Donc, si le chier

s'appelle chanson.mp3 , la position du point est 7 (puisqu'on commence à 0), on prend donc la
sous-chaîne à partir du caractère 8 jusqu'à la n, ce qui donne « mp3 ». C'est la même chose que si
on avait fait :
java

1 "musique.mp3".subSequence(8, "musique.mp3".length())
2

N'oubliez pas de gérer le cas où vous n'avez pas d'activité qui puisse intercepter votre intent.

Ma solution


Interface graphique
Facile, il n'y en a pas ! Comme notre activité est constituée uniquement d'une ListView , pas
besoin de lui attribuer une interface graphique avec setContentView .

Choisir une couleur avec ColorPickerPreferenceDialog

Tout le raisonnement a déjà été expliqué dans les spéci cations techniques :
java

1 public class ColorPickerPreferenceDialog extends DialogPreference implements


OnColorChangedListener{
2 private int mColor = 0;
3
4 public ColorPickerPreferenceDialog(Context context, AttributeSet attrs) {
5 super(context, attrs);
6 }
7
8 /**
9 * Déclenché dès qu'on ferme la boîte de dialogue
10 */
11 protected void onDialogClosed(boolean positiveResult) {
12 // Si l'utilisateur a cliqué sur « OK »
13 if (positiveResult) {
14 persistInt(mColor);
15 // Ou getSharedPreferences().edit().putInt(getKey(), mColor).commit();
16 }
17 super.onDialogClosed(positiveResult);
18 }
19
20 /**
21 * Pour construire la boîte de dialogue
22 */
23 protected void onPrepareDialogBuilder(Builder builder) {
24 // On récupère l'ancienne couleur ou la couleur par défaut
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 9/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
25 int oldColor = getSharedPreferences().getInt(getKey(), Color.BLACK);
26 // On insère la vue dans la boîte de dialogue
27 builder.setView(new ColorPickerView(getContext(), this, oldColor));
28
29 super.onPrepareDialogBuilder(builder);

30 }
31
32 /**
33 * Déclenché à chaque fois que l'utilisateur choisit une couleur
34 */
35 public void colorChanged(int color) {
36 mColor = color;
37 }
38 }
39

Il faut ensuite ajouter cette boîte de dialogue dans le chier XML des préférences :
xml

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


2 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
3 <PreferenceCategory android:title="@string/couleurs_pref" >
4 <sdz.chapitreTrois.explorateur.ColorPickerPreferenceDialog
5 android:key="repertoireColorPref"
6 android:title="Répertoires"
7 android:summary="Choisir une couleur des répertoires"
8 android:dialogTitle="Couleur des répertoires" />
9 </PreferenceCategory>
10 </PreferenceScreen>
11

Il suf t ensuite de déclarer l'activité dans le Manifest :


xml

1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 package="sdz.chapitreTrois.explorateur"
3 android:versionCode="1"
4 android:versionName="1.0" >
5
6 <uses-sdk
7 android:minSdkVersion="7"
8 android:targetSdkVersion="7" />
9
10 <application
11 android:icon="@drawable/ic_launcher"
12 android:label="@string/app_name"
13 android:theme="@style/AppTheme" >
14 <activity
15 android:name=".ExplorateurActivity"
16 android:label="@string/title_activity_explorateur" >
17 <intent-filter>
18 <action android:name="android.intent.action.MAIN" />
19 <category android:name="android.intent.category.LAUNCHER" />
20 </intent-filter>
21 </activity>
22 <activity
23 android:name=".ExploreurPreference"
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 10/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
p
24 android:label="@string/title_activity_exploreur_preference" >
25 </activity>
26 </application>
27 </manifest>
28

… puis de créer l'activité :


java

1 public class ExploreurPreference extends PreferenceActivity {


2 @Override
3 public void onCreate(Bundle savedInstanceState) {
4 super.onCreate(savedInstanceState);
5 addPreferencesFromResource(R.xml.preference);
6 }
7 }
8

L'activité principale
Attributs

Voici les différents attributs que j'utilise :


java

1 /**
2 * Représente le texte qui s'affiche quand la liste est vide
3 */
4 private TextView mEmpty = null;
5
6 /**
7 * La liste qui contient nos fichiers et répertoires
8 */
9 private ListView mList = null;
10
11 /**
12 * Notre adaptateur personnalisé qui lie les fichiers à la liste
13 */
14 private FileAdapter mAdapter = null;
15
16 /**
17 * Représente le répertoire actuel
18 */
19 private File mCurrentFile = null;
20
21 /**
22 * Couleur voulue pour les répertoires
23 */
24 private int mColor = 0;
25
26 /**
27 * Indique si l'utilisateur est à la racine ou pas
28 * Pour savoir s'il veut quitter
29 */
30 private boolean mCountdown = false;
31
32 /**
33 * Les préférences partagées de cette application
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 11/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

34 */
35 private SharedPreferences mPrefs = null;
36

Comme je fais implémenter OnSharedPreferenceChangeListener à mon activité, je dois redé nir la


méthode de callback :
java

1 /**
2 * Se déclenche dès qu'une préférence a changé
3 */
4 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
5 mColor = sharedPreferences.getInt("repertoireColorPref", Color.BLACK);
6 mAdapter.notifyDataSetInvalidated();
7 }
8

L'adaptateur

J'utilise un Adapter que j'ai créé moi-même a n d'avoir des items de la liste de différentes
couleurs :
java

1 /**
2 * L'adaptateur spécifique à nos fichiers
3 */
4
5 private class FileAdapter extends ArrayAdapter<File> {
6 /**
7 * Permet de comparer deux fichiers
8 *
9 */
10 private class FileComparator implements Comparator<File> {
11 public int compare(File lhs, File rhs) {
12 // Si lhs est un répertoire et pas l'autre, il est plus petit
13 if(lhs.isDirectory() && rhs.isFile())
14 return -1;
15 // Dans le cas inverse, il est plus grand
16 if(lhs.isFile() && rhs.isDirectory())
17 return 1;
18
19 // Enfin, on ordonne en fonction de l'ordre alphabétique sans tenir compte de la casse
20 return lhs.getName().compareToIgnoreCase(rhs.getName());
21 }
22 }
23
24 public FileAdapter(Context context, int textViewResourceId, List<File> objects) {
25 super(context, textViewResourceId, objects);
26 mInflater = LayoutInflater.from(context);
27 }
28
29 private LayoutInflater mInflater = null;
30
31 /**
32 * Construit la vue en fonction de l'item
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 12/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

33 */
34 public View getView(int position, View convertView, ViewGroup parent) {
35 TextView vue = null;
36
37 if(convertView != null)

38 // On recycle
39 vue = (TextView) convertView;
40 else
41 // On inflate
42 vue = (TextView) mInflater.inflate(android.R.layout.simple_list_item_1, null);
43
44 File item = getItem(position);
45 //Si c'est un répertoire, on choisit la couleur dans les préférences
46 if(item.isDirectory())
47 vue.setTextColor(mColor);
48 else
49 // Sinon, c'est du noir
50 vue.setTextColor(Color.BLACK);
51
52 vue.setText(item.getName());
53 return vue;
54 }
55
56 /**
57 * Pour trier rapidement les éléments de l'adaptateur
58 */
59 public void sort () {
60 super.sort(new FileComparator());
61 }
62 }
63

Méthodes secondaires

Ensuite, j'ai une méthode qui permet de vider l'adaptateur :


java

1 /**
2 * On enlève tous les éléments de la liste
3 */
4
5 public void setEmpty() {
6 // Si l'adaptateur n'est pas vide…
7 if(!mAdapter.isEmpty())
8 // Alors on le vide !
9 mAdapter.clear();
10 }
11

J'ai aussi développé une méthode qui me permet de passer d'un répertoire à l'autre :
java

1 /**
2 * Utilisé pour naviguer entre les répertoires
3 * @param pFile le nouveau répertoire dans lequel aller
4 */
5
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 13/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

6 public void updateDirectory(File pFile) {


7 // On change le titre de l'activité
8 setTitle(pFile.getAbsolutePath());
9
10 // L'utilisateur ne souhaite plus sortir de l'application

11 mCountdown = false;
12
13 // On change le répertoire actuel
14 mCurrentFile = pFile;
15 // On vide les répertoires actuels
16 setEmpty();
17
18 // On récupère la liste des fichiers du nouveau répertoire
19 File[] fichiers = mCurrentFile.listFiles();
20
21 // Si le répertoire n'est pas vide…
22 if(fichiers != null) {
23 // On les ajoute à l'adaptateur
24 for(File f : fichiers)
25 mAdapter.add(f);
26 // Puis on le trie
27 mAdapter.sort();
28 }
29 }
30

Cette méthode est d'ailleurs utilisée par la méthode de callback onKeyDown :


java

1 public boolean onKeyDown(int keyCode, KeyEvent event) {


2 // Si on a appuyé sur le retour arrière
3 if(keyCode == KeyEvent.KEYCODE_BACK) {
4 // On prend le parent du répertoire courant
5 File parent = mCurrentFile.getParentFile();
6 // S'il y a effectivement un parent
7 if(parent != null)
8 updateDirectory(parent);
9 else {
10 // Sinon, si c'est la première fois qu'on fait un retour arrière
11 if(mCountdown != true) {
12 // On indique à l'utilisateur qu'appuyer dessus une seconde fois le fera sortir
13 Toast.makeText(this, "Nous sommes déjà à la racine ! Cliquez une seconde fois pour
quitter", Toast.LENGTH_SHORT).show();
14 mCountdown = true;
15 } else
16 // Si c'est la seconde fois, on sort effectivement
17 finish();
18 }
19 return true;
20 }
21 return super.onKeyDown(keyCode, event);
22 }
23

Gestion de l'intent pour visualiser un chier


java
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 14/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

1 /**
2 * Utilisé pour visualiser un fichier
3 * @param pFile le fichier à visualiser
4 */
5 private void seeItem(File pFile) {
6 // On crée un intent
7 Intent i = new Intent(Intent.ACTION_VIEW);
8
9 String ext = pFile.getName().substring(pFile.getName().indexOf(".") + 1).toLowerCase();
10 if(ext.equals("mp3"))
11 i.setDataAndType(Uri.fromFile(pFile), "audio/mp3");
12 /** Faites en autant que vous le désirez */
13
14 try {
15 startActivity(i);
16 // Et s'il n'y a pas d'activité qui puisse gérer ce type de fichier
17 } catch (ActivityNotFoundException e) {
18 Toast.makeText(this, "Oups, vous n'avez pas d'application qui puisse lancer ce fichier",
Toast.LENGTH_SHORT).show();
19 e.printStackTrace();
20 }
21 }
22

Les menus

Rien d'étonnant ici, normalement vous connaissez déjà tout. À noter que j'ai utilisé deux layouts
pour le menu contextuel de manière à pouvoir le changer selon qu'il s'agit d'un répertoire ou d'un
chier :
java

1 @Override
2 public boolean onCreateOptionsMenu(Menu menu) {
3 getMenuInflater().inflate(R.menu.activity_explorateur, menu);
4 return true;
5 }
6
7 @Override
8 public boolean onOptionsItemSelected (MenuItem item)
9 {
10 switch(item.getItemId())
11 {
12 case R.id.menu_options:
13 // Intent explicite
14 Intent i = new Intent(this, ExploreurPreference.class);
15 startActivity(i);
16 return true;
17 }
18 return super.onOptionsItemSelected(item);
19 }
20
21 @Override
22 public void onCreateContextMenu(ContextMenu menu, View vue, ContextMenuInfo menuInfo) {
23 super.onCreateContextMenu(menu, vue, menuInfo);
24
25 MenuInflater inflater = getMenuInflater();
26 // On récupère des informations sur l'item par apport à l'adaptateur
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 15/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
26 // On récupère des informations sur l item par apport à l adaptateur
27 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
28
29 // On récupère le fichier concerné par le menu contextuel
30 File fichier = mAdapter.getItem(info.position);
31 // On a deux menus, s'il s'agit d'un répertoire ou d'un fichier
32 if(!fichier.isDirectory())
33 inflater.inflate(R.menu.context_file, menu);
34 else
35 inflater.inflate(R.menu.context_dir, menu);
36 }
37
38 @Override
39 public boolean onContextItemSelected(MenuItem item) {
40 AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)
item.getMenuInfo();
41 // On récupère la position de l'item concerné
42 File fichier = mAdapter.getItem(info.position);
43 switch (item.getItemId()) {
44 case R.id.deleteItem:
45 mAdapter.remove(fichier);
46 fichier.delete();
47 return true;
48
49 case R.id.seeItem:
50 seeItem(fichier);
51 return true;
52 }
53 return super.onContextItemSelected(item);
54 }
55

onCreate

Voici la méthode principale où se situent toutes les initialisations :


java

1 public void onCreate(Bundle savedInstanceState) {


2 super.onCreate(savedInstanceState);
3
4 // On récupère la ListView de notre activité
5 mList = (ListView) getListView();
6
7 // On vérifie que le répertoire externe est bien accessible
8 if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
9 // S'il ne l'est pas, on affiche un message
10 mEmpty = (TextView) mList.getEmptyView();
11 mEmpty.setText("Vous ne pouvez pas accéder aux fichiers");
12 } else {
13 // S'il l'est, on déclare qu'on veut un menu contextuel sur les éléments de la liste
14 registerForContextMenu(mList);
15
16 // On récupère les préférences de l'application
17 mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
18 // On indique que l'activité est à l'écoute des changements de préférences
19 mPrefs.registerOnSharedPreferenceChangeListener(this);
20 // On récupère la couleur voulue par l'utilisateur, par défaut il s'agira du rouge
21 mColor = mPrefs.getInt("repertoireColorPref", Color.RED);
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 16/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

22
23 // On récupère la racine de la carte SD pour qu'elle soit le répertoire consulté au départ
24 mCurrentFile = Environment.getExternalStorageDirectory();
25
26 // On change le titre de l'activité pour y mettre le chemin actuel

27 setTitle(mCurrentFile.getAbsolutePath());
28
29 // On récupère la liste des fichiers dans le répertoire actuel
30 File[] fichiers = mCurrentFile.listFiles();
31
32 // On transforme le tableau en une structure de données de taille variable
33 ArrayList<File> liste = new ArrayList<File>();
34 for(File f : fichiers)
35 liste.add(f);
36
37 mAdapter = new FileAdapter(this, android.R.layout.simple_list_item_1, liste);
38 // On ajoute l'adaptateur à la liste
39 mList.setAdapter(mAdapter);
40 // On trie la liste
41 mAdapter.sort();
42
43 // On ajoute un Listener sur les items de la liste
44 mList.setOnItemClickListener(new OnItemClickListener() {
45
46 // Que se passe-t-il en cas de clic sur un élément de la liste ?
47 public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
48 File fichier = mAdapter.getItem(position);
49 // Si le fichier est un répertoire…
50 if(fichier.isDirectory())
51 // On change de répertoire courant
52 updateDirectory(fichier);
53 else
54 // Sinon, on lance l'item
55 seeItem(fichier);
56 }
57 });
58 }
59 }
60

Télécharger le projet

Améliorations envisageables

Quand la liste est vide ou le périphérique externe est indisponible


On se trouve en face d'un écran blanc pas très intéressant… Ce qui pourrait être plus excitant, c'est
un message qui indique à l'utilisateur qu'il n'a pas accès à ce périphérique externe. On peut faire ça
en indiquant un layout pour notre ListActivity ! Oui, je sais, je vous ai dit de ne pas le faire, parce
que notre activité contient principalement une liste, mais là on pousse le concept encore plus loin.
Le layout qu'on utilisera doit contenir au moins une ListView pour représenter celle de notre
ListActivity , mais notre application sera bien incapable de la trouver si vous ne lui précisez pas
où elle se trouve. Vous pouvez le faire en mettant comme identi ant à la ListView

https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 17/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

android:id="@android:id/list" . Si vous voulez q'un widget ou un layout s'af che quand la liste
est vide, vous devez lui attribuer l'identi ant android:id="@android:id/empty" . Pour ma
correction, j'ai le XML suivant :
xml

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


2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="fill_parent"
5 android:layout_height="fill_parent"
6 android:paddingLeft="8dp"
7 android:paddingRight="8dp">
8
9 <ListView android:id="@android:id/list"
10 android:layout_width="fill_parent"
11 android:layout_height="0dip"
12 android:layout_weight="1"
13 android:drawSelectorOnTop="false"/>
14
15 <TextView android:id="@android:id/empty"
16 android:layout_width="fill_parent"
17 android:layout_height="0dip"
18 android:layout_weight="1"
19 android:text="@string/empty"/>
20 </LinearLayout>
21

Détection automatique du type MIME


Parce que faire une longue liste de « Si on a cette extension pour ce chier, alors le type MIME, c'est
celui-là » est quand même long et contraignant, je vous propose de détecter automatiquement le
type MIME d'un objet. Pour cela, on utilisera un objet de type MimeTypeMap . A n de récupérer cet
objet, on passe par la méthode statique MimeTypeMap MimeTypeMap.getSingleton() .

Petite digression pour vous dire que le design pattern singleton a pour objectif de faire en
sorte que vous ne puissiez avoir qu'une seule instance d'un objet. C'est pourquoi on utilise la
méthode getSingleton() qui renvoie toujours le même objet. Il est impossible de
construire autrement un objet de type MimeTypeMap .

Ensuite c'est simple, il suf t de donner à la méthode String


getMimeTypeFromExtension(String extension) l'extension de notre chier. On obtient ainsi :
java

1 MimeTypeMap mime = MimeTypeMap.getSingleton();


2 String ext = fichier.getName().substring(fichier.getName().indexOf(".") + 1).toLowerCase();
3 String type = mime.getMimeTypeFromExtension(ext);
4

Détecter les changements d'état du périphérique externe


C'est bien beau tout ça mais si l'utilisateur se décide tout à coup à changer la carte SD en pleine
https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 18/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms
C est bien beau tout ça, mais si l utilisateur se décide tout à coup à changer la carte SD en pleine
utilisation, nous ferons face à un gros plantage ! Alors comment contrer ce souci ? C'est simple. Dès
que l'état du périphérique externe change, un broadcast intent est transmis pour le signaler à tout
le système. Il existe tout un tas d'actions différentes associées à un changement d'état, je vous

propose de ne gérer que le cas où le périphérique externe est enlevé, auquel cas l'action est
ACTION_MEDIA_REMOVED . Notez au passage que l'action pour dire que la carte fonctionne à nouveau
est ACTION_MEDIA_MOUNTED .

Comme nous l'avons vu dans le cours, il faudra déclarer notre broadcast receiver dans le
Manifest :
xml

1 <receiver android:name=".ExplorerReceiver"
2 android:exported="false">
3 <intent-filter>
4 <action android:name="android.intent.action.MEDIA_REMOVED" />
5 <action android:name="android.intent.action.MEDIA_MOUNTED" />
6 </intent-filter>
7 </receiver>
8

Ensuite, dans le receiver en lui-même, on fait en sorte de viser la liste des éléments s'il y a un
problème avec le périphérique externe, ou au contraire de la repeupler dès que le périphérique
fonctionne correctement à nouveau. À noter que dans le cas d'un broadcast Intent avec l'action

ACTION_MEDIA_MOUNTED , l'intent aura dans son champ data l'emplacement de la racine du


périphérique externe :
java

1 public class ExplorerReceiver extends BroadcastReceiver {


2 private ExplorateurActivity mActivity = null;
3
4 public ExplorerReceiver(ExplorateurActivity mActivity) {
5 super();
6 this.mActivity = mActivity;
7 }
8
9 @Override
10 public void onReceive(Context context, Intent intent) {
11 if(intent.getAction().equals(Intent.ACTION_MEDIA_REMOVED))
12 mActivity.setEmpty();
13 else if(intent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED))
14 mActivity.updateDirectory(new File(intent.getData().toString()));
15 }
16 }
17

J'AI TERMINÉ CE CHAPITRE ET JE PASSE AU SUIVANT

 LE STOCKAGE DE DONNÉES LES BASES DE DONNÉES 


https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 19/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

Le professeur
Frédéric Espiau

Découvrez aussi ce cours en...

 
Livre PDF

OPENCLASSROOMS

ENTREPRISES

CONTACT

EN PLUS

Français
 

https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 20/21
20/06/2020 TP : un explorateur de fichiers - Créez des applications pour Android - OpenClassrooms

https://openclassrooms.com/fr/courses/2023346-creez-des-applications-pour-android/2027006-tp-un-explorateur-de-fichiers 21/21

Das könnte Ihnen auch gefallen