Ludovic ROLAND

Blog technique sur mes expériences de développeur.

Android : mise en place de l’ActionBarCompat et du navigation drawer

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 :

  • l’ActionBarCompat qui remplace ActionBarSherlock, rendant ainsi possible d’afficher une action bar sur les terminaux Android 2.1 et + ;
  • le navigation drawer qui remplace le sliding menu.

Avant toute chose, mettez à jour les composants Android via le SDK Manager !

Création d’un nouveau projet

Nous allons tout d’abord créer un nouveau projet Android avec pour SDK Minimum l’API 7 (Android 2.1).

Les dépendances

Dans ce projet, nous allons avoir besoin d’utiliser deux bibliothèques :

  • la bibliothèque android-support-v4 qui va nous permettre d’utiliser le navigation drawer et une version rétro-compatible des fragments ;
  • la bibliothèque android-support-v7 qui va nous permettre d’utiliser l’actionBarCompat.

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 !

Intégrer la bibliothèque android-support-v7

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.

Mise en place d’actionBarCompat

Le style

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>

Les activités

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 { }

Les menus

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 :

Mise en place du navigation drawer

Nous allons maintenant mettre en place le navigation drawer.

Le layout

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 :

  • le premier contiendra le contenu de notre Activité ;
  • le second contiendra notre drawer navigation.

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».

Mise en place de la mécanique

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 :

Le code complet

Comme d’habitude, voici le code complet :

Le fichier AndroidManifest.xml

<?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>

Le fichier activity_main.xml

<?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>

Le fichier MainActivity.java

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);
	}
}

Commentaires