Blog technique sur mes expériences de développeur.
1er septembre 2013
Ce nouvel article sur Android est l’occasion de mettre en place dans un projet les nouveaux concepts mis à disposition par Google, à savoir :
Avant toute chose, mettez à jour les composants Android via le SDK Manager !
Nous allons tout d’abord créer un nouveau projet Android avec pour SDK Minimum l’API 7 (Android 2.1).
Dans ce projet, nous allons avoir besoin d’utiliser deux bibliothèques :
Pour la première bibliothèque, il n’y a rien à faire, elle est incluse directement à la création de votre projet si vos outils sont à jour !
Nous allons donc voir comment intégrer la bibliothèque android-support-v7 à notre projet.
C’est un projet qu’il convient d’importer dans notre «workspace» comme «Existing Android Code Into Workspace». Chez moi, il est situé dans «C:\Program Files\Android\android-sdk\extras\android\support\v7\appcompat».
Dans votre espace de travail, dépliez le dossier «libs» de la bibliothèque android-support-v7-appcompat et ajoutez chacun des fichiers jar s’y trouvant au «Build Path» comme sur la capture d’écran suivante :
Toujours sur la bibliothèque android-support-v7-appcompat, faites un clic droit, placez votre souris sur l’entrée «Build Path», puis cliquez sur l’entrée «Configure Build Path».
Dans la fenêtre qui s’ouvre, rendez-vous das l’onglet «Order and Export» puis cochez les fichiers jar en prenant soin de décocher l’entrée «Android Dependencies».
Nous Allons maintenant ajouter le projet fraîchement importé comme une bibliothèque de notre projet principal. Pour se faire, allez dans les propriétés du projet Android, puis dans l’onglet Android et tout en bas, ajoutez le projet android-support-v7-appcompat comme bibliothèque.
La première chose à faire est de modifier le fichier de style de notre application pour lui indiquer d’utiliser notre nouvelle action bar.
Rendez-vous donc dans le fichier «res/values/styles.xml» de notre projet et changez le parent du AppBaseTheme comme dans l’extrait de code suivant par un thème AppCompat :
<resources>
<style name="AppBaseTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar" />
<style name="AppTheme" parent="AppBaseTheme" />
</resources>
A partir de maintenant, nos activités n’hériteront plus de la classe Activity, mais de la classe ActionBarActivity. Par exemple :
public class MainActivity extends ActionBarActivity { }
Je propose maintenant d’ajouter sur notre actionBar une icône permettant de rafraîchir la vue. Le principe est simple : je souhaite afficher une icône de rafraichissement, qui une fois cliquée, laisse sa place à une progress bar.
J’ai décidé de créer mon menu dans le fichier «res/menu/main.xml». Avec l’action bar native, voici ce que vous auriez écrit :
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/refresh"
android:icon="@drawable/ic_navigation_refresh"
android:title="@string/ac_refresh"
android:showAsAction="always"
/>
</menu>
Le problème c’est que ce code ne fonctionnera pas avec ActionBarCompat. Nous devons procéder à quelques modifications. Sur les anciennes versions d’Android, l’attribut showAsAction n’existe pas. Pour pouvoir l’utiliser, nous devons donc déclarer notre propre namespace XML. Voici ce que ça donne :
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myApp="http://schemas.android.com/apk/res-auto"
>
<item
android:id="@+id/refresh"
android:icon="@drawable/ic_navigation_refresh"
android:title="@string/ac_refresh"
myApp:showAsAction="always"
/>
</menu>
Nous pouvons donc maintenant «inflater» notre menu en surchargeant la méthode «onCreateOptionsMenu» de notre Activity :
public final class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
A noter que le layout «activity_main» est le layout créé automatiquement au moment de la création du projet Android.
Vous pouvez d’ores et déjà compiler le projet. Vous devriez avoir le résultat suivant :
Nous allons maintenant procéder au changement d’état de notre icône de rafraichissement pour qu’elle affiche une progress bar après avoir cliqué dessus. Pour celà rien de plus simple, nous allons tout simplement lui définir une actionView.
Nous allons commencer par créer le layout de la progress bar que l’on souhaite afficher. Voici le contenu de mon fichier «res/layout/progressbar.xml» :
<?xml version="1.0" encoding="utf-8"?>
<ProgressBar
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
Il nous suffit ensuite de surcharger la méthode «onOptionsItemSelected» de notre Activité. Normalement, voici ce qu’on aurait tendance à écrire :
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if(R.id.refresh == item.getItemId()) {
item.setActionView(R.layout.progressbar);
}
return super.onOptionsItemSelected(item);
}
Le problème avec ce code, c’est que la méthode «setActionView» a été ajouté avec l’API 11 d’Android. Autrement dit, si ce morceau de code fonctionnera parfaitement sur Android 3 et +, il plantera sur Android 2.3. Nous allons donc passer par la méthode statique «setActionView» de la classe MenuItemCompat :
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if(R.id.refresh == item.getItemId()) {
MenuItemCompat.setActionView(item, R.layout.progressbar);
}
return super.onOptionsItemSelected(item);
}
Après avoir cliqué sur sur l’icône, voici ce que vous devriez avoir :
Nous allons maintenant mettre en place le navigation drawer.
La première chose à faire est de modifier la layout de notre Activité. Notre nouveau layout sera basé sur un DrawerLayout :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
>
</android.support.v4.widget.DrawerLayout>
Ce container prend 2 fils :
Par exemple :
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
>
<!-- contenu de l'activité -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
/>
<!-- contenu du drawer -->
<TextView
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:text="@string/drawer"
android:background="@android:color/black"
android:textColor="@android:color/white"
/>
</android.support.v4.widget.DrawerLayout>
Afin que le deuxième enfant soit considéré comme le drawer navigation, il est très important de lui préciser l’attribut «layout_gravity».
Nous allons dans un premier temps garder la référence à notre DrawerLayout :
public final class MainActivity extends ActionBarActivity {
private DrawerLayout drawerLayout;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
}
}
Nous allons maintenant mettre en place la mécanique grâce à un objet de la classe ActionBarDrawerToggle :
public final class MainActivity extends ActionBarActivity {
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
// On crée une instance de la classe prenant pour paramètres :
// 1. l'activité
// 2. le drawer layout
// 3. la ressource graphique
// 4 et 5. des ressources textuelles permettant d'indiquer l'état du drawer.
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
// On définit notre ActionBarDrawerToggle comme listener.
drawerLayout.setDrawerListener(drawerToggle);
// On précise que l'on souhaite afficher la ressource graphique
drawerToggle.setDrawerIndicatorEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
// On synchronise.
drawerToggle.syncState();
}
}
Notre mécanique est maintenant en place. Seul petit problème, le navigation drawer ne s’affiche et ne disparaît qu’avec un mouvement du doigt. Il nous reste encore à mettre en place la mécanique lorsqu’on clique sur la ressource graphique dans l’actionBar.
Il suffit de simplement mettre à jour notre méthode «onOptionsItemSelected» en prenant en compte le click sur l’item «home» :
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if(android.R.id.home == item.getItemId()) {
if (drawerLayout.isDrawerOpen(Gravity.LEFT) == false) {
drawerLayout.openDrawer(Gravity.LEFT);
}
else {
drawerLayout.closeDrawers();
}
}
else if(R.id.refresh == item.getItemId()) {
MenuItemCompat.setActionView(item, R.layout.progressbar);
}
return super.onOptionsItemSelected(item);
}
C’est maintenant terminé. Vous devriez avoir ceci :
Comme d’habitude, voici le code complet :
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.rolandl.blog.actionbarcompat_drawer"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="fr.rolandl.blog.actionbarcompat_drawer.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
>
<!-- contenu de l'activité -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<!-- contenu du drawer -->
<TextView
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="left"
android:text="@string/drawer"
android:background="@android:color/black"
android:textColor="@android:color/white" />
</android.support.v4.widget.DrawerLayout>
package fr.rolandl.blog.actionbarcompat_drawer;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.view.MenuItemCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarActivity;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
public final class MainActivity extends ActionBarActivity {
private DrawerLayout drawerLayout;
private ActionBarDrawerToggle drawerToggle;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawerLayout = (DrawerLayout) findViewById(R.id.drawerLayout);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
drawerLayout.setDrawerListener(drawerToggle);
drawerToggle.setDrawerIndicatorEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
drawerToggle.syncState();
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
if(android.R.id.home == item.getItemId()) {
if (drawerLayout.isDrawerOpen(Gravity.LEFT) == false) {
drawerLayout.openDrawer(Gravity.LEFT);
}
else {
drawerLayout.closeDrawers();
}
}
else if(R.id.refresh == item.getItemId()) {
MenuItemCompat.setActionView(item, R.layout.progressbar);
}
return super.onOptionsItemSelected(item);
}
}