Translate

mardi 4 février 2014

Slide Menu

 Les Slide Menus sont de plus en plus utilisés et on les retrouve notamment au niveau des applications Facebook, Gmail etc.
Ce menu permet de mieux gérer l'espace puisqu'il apparait et disparait à la demande. Il permet également de disposer de l'ensemble des fonctionnalités de notre application. L'autre spécificité de ce menu est qu'il apparait par un simple mouvement du doigt de gauche vers la droite.



Dans le présent article je détaillerai étape par étape la construction d'une application qui donne l’heure et la date actuelles partout dans le monde. Elle aura un menu exposant les Time Zones. Le clic sur un élément nous donnera l'heure actuelle dans ce fuseau horaire.
Le code source est disponible Code Source

Nous commencerons par exposer la structure du fichier XML de notre activité. Nous passerons ensuite à la définition des éléments de notre Slide Menu. Enfin, nous décrirons la création et la manipulation du Slide Menu par l'activité (Java) à partir des objets : DrawerLayout, ListView et ActionBarDrawerToggle.

1. Structure du fichier XML de l’activité


Le fichier XML doit respecter la structure suivante : un DrawerLayout dans lequel nous mettons un Layout qui contiendra le contenu de notre écran et une ListView qui contiendra les éléments de notre menu.

<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/menu_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<!-- The LinearLayout consumes the entire space available 
using match_parent in both dimensions. -->

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/time_zone_current_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

<!-- The menu elements ListView. -->

<ListView
android:id="@+id/menu_elements"
android:layout_width="200dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="#123"
android:choiceMode="singleChoice"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"/>

</android.support.v4.widget.DrawerLayout>

2. Définition de la structure des éléments du menu


Il faut également définir la structure des éléments du SlideMenu dans le fichier element_menu.xml. Pour notre exemple nous avons un élément qui contient uniquement un TextView. Mais rien n'empêche d'avoir des éléments à structures complexes ou un menu ayant des éléments de différentes compositions.

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/menu_time_zone_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/hello_world"
android:textColor="#fff"/>

C'est déjà fini du côté XML. Passons maintenant du côté de l'activité pour voir comment interagir avec le menu.

3. Implémentation de l’activité


Au niveau du code Java nous disposons principalement des trois objets suivants :

private DrawerLayout menuLayout; //Layout Principal
private ListView menuElementsList; //Menu
private ActionBarDrawerToggle menuToggle; //Gère l'ouverture et la fermeture du menu

Le SlideMenu peut être appelé par le bouton home de notre activité, pour ce faire nous devons activer ce dernier.

getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeButtonEnabled(true);

Nous commençons par récupérer le layout et la liste.

menuLayout = (DrawerLayout) findViewById(R.id.menu_layout);
menuElementsList = (ListView) findViewById(R.id.menu_elements);

Nous ajoutons un effet à appliquer quand le menu est ouvert, et nous spécifions la direction d'ouverture du SlideMenu (ici GravityCompat.START donc de gauche à droite).

menuLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);

Attention, ceci doit être la même valeur que celle spécifiée en XML dans la ListView (android:layout_gravity="start")

Nous créons un adapter et ajoutons la liste des éléments du SlideMenu. Ensuite nous appliquons l’adapter à notre menuElementsList.

menuElementsList.setAdapter(adapter);

Passons maintenant à la création du menuToggle qui gérera l’ouverture et la fermeture du menu.

menuToggle = newActionBarDrawerToggle(this,menuLayout, R.drawable.ic_drawer,R.string.drawer_open,R.string.drawer_close) {
//Exécutée à la fermeture du menu
publicvoidonDrawerClosed(View view) {
      getActionBar().setTitle(activityTitle);
      invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
//Exécutée à l’ouverture du menu
publicvoidonDrawerOpened(View drawerView) {
      getActionBar().setTitle(menuTitle);
      invalidateOptionsMenu(); // calls onPrepareOptionsMenu()
}
};
menuLayout.setDrawerListener(menuToggle);

Afin d’interagir avec les clics sur les éléments du SlideMenu, nous devons créer un OnItemClickListener.

menuElementsList.setOnItemClickListener(newOnItemClickListener() {
@Override
publicvoidonItemClick(AdapterView<?> arg0, View view, int position,long id) {
... Faites ce que vous désirez suite au clic sur l’élément ayant comme index "position"...
}
});

Et pour finir, comment masquer les éléments du menu optionnel une fois que notre SlideMenu est ouvert et comment les réafficher à sa fermeture ?
Rappelons que la méthode onPrepareOptionsMenu() est appelée à l’ouverture et à la fermeture de notre SlideMenu via invalidateOptionsMenu().
Ceci n'est nullement obligatoire. Vous pouvez garder les actions de votre menu optionnel affichées. Pour cela ne faites pas appel à la méthode invalidateOptionsMenu();

@Override
publicbooleanonPrepareOptionsMenu(Menu menu) {
      // Cache l'élément du menu optionnel à l'ouverture et le fait réapparaitre à la fermeture
      booleandrawerOpen = menuLayout.isDrawerOpen(menuElementsList);
      menu.findItem(R.id.action_search).setVisible(!drawerOpen);
      returnsuper.onPrepareOptionsMenu(menu);
}
  
Le code source est disponible Code Source

jeudi 25 juillet 2013

Download File

Dans ce tutoriel nous allons voir comment télécharger un document à partir de son url.
Le téléchargement doit se dérouler en arrière plan afin de ne pas bloquer l'application. Il est également utile de suivre l'évolution du téléchargement, et nous aurons aussi besoin de d'informer l'utilisateur que le téléchargement est terminé. C'est pour cela que nous utiliserons AsyncTask avec ces méthodes doInBackground, onProgressUpdate et onPostExecute .
L'information sur l'évolution du téléchargement sera affichée au niveau d'une Notification.


Pour télécharger le document :

String url = "URL du document à télécharger ou du WebService de téléchargement";
HttpGet httpget = new HttpGet(URIUtil.encodeQuery(url));
HttpResponse response;
DefaultHttpClient httpclient = new DefaultHttpClient();
response = httpclient.execute(httpget);
if (response.getStatusLine().getStatusCode() != 200) {
       throw new IOException();
}
HttpEntity entity = response.getEntity();
OutputStream output = new FileOutputStream(chemin du document à écrire);
Int count = -1;
while ((count = input.read(data)) != -1) {
       output.write(data, 0, count);
}
output.flush();
output.close();

input.close();

Pour les notifications, l'utilisation de AsyncTask consultez le projet suivant : https://github.com/DroiDev/DownloadDocument

Afficher une image à partir de son URL

Pour afficher une image dans un ImageView directement à partir de son URL nous écrirons une AsyncTask qui téléchargera l'image en arrière plan (ne bloque pas l'application). Une c'est fait cette AsyncTask affichera l'image à l'écran.

AsyncTask
public class ImagePreview extends AsyncTask<String, Integer, Object> {

       public ImagePreview() {
             super();
       }

       @Override
       protected final Bitmap doInBackground(final String... url) {
             Bitmap bitmap = null;
             try {
                    bitmap = BitmapFactory.decodeStream((InputStream) new URL(
                                        url[0]).getContent());
             } catch (MalformedURLException e) {
                    e.printStackTrace();
             } catch (IOException e) {
                    e.printStackTrace();
             }
             return bitmap;
       }

       @Override
       protected final void onPostExecute(final Object result) {
             super.onPostExecute(result);
             ImageView photo = (ImageView) findViewById(R.id.image_preview);
             if (result != null)
                    photo.setImageBitmap((Bitmap) result);
             else
                    Toast.makeText(getApplicationContext(), R.string.download_fail,
                                        Toast.LENGTH_LONG).show();
       }

}

Il ne reste plus qu'à appeler cette AsyncTask avec l'url de l'image
ImagePreview d = new ImagePreview();
d.execute(url de l'image à afficher);

Un exemple est disponible : https://github.com/DroiDev/DownloadDocument

samedi 4 mai 2013

Parcelable : Comment passer/communiquer un objet d'une classe à une autre

Sous Android, il est possible de faire passer des données d'une Activité à une autre en utilisant :


Intent intent = new Intent(this, ClassB.class);
intent.putExtra(Key, VALUE);
startActivity(intent);

Cependant VALUE peut être int, String, Boolean... mais ne peut être un objet.

Un moyen simple pour rendre possible la communication d'objets entre les Activités est d'implémenter, au niveau de la classe de l'objet, l'interface Parcelable

public class ParcelableObject implements Parcelable

Ajouter un constructeur prenant comme paramètre un Parcel

public ParcelableObject(Parcel in) {        

nom = in.readString();
prenom = in.readString();
mail = in.readString();
boolean droits[] = new boolean[1];
in.readBooleanArray(droits);
estEtudiant = droits[0];
age = in.readInt();

}

Ajouter une méthode qui va écrire l'objet en Parcel
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(nom);
dest.writeString(prenom);
dest.writeString(mail);
boolean droits[] = { estEtudiant };//Pour les boulean on ne peut passer qu'un BooleanArray
dest.writeBooleanArray(droits);
dest.writeInt(age);
}

Remarque très importante : L'ordre d'écriture dans le Parcel doit être le même au niveau de la lecture. Dans notre cas nom -> prenom -> mail -> estEtudiant ->age

Et enfin créer un Parcelable.Creator

public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
    public ParcelableObject createFromParcel(Parcel in) {
     return new ParcelableObject(in);
     }

    public ParcelableObject[] newArray(int size) {
     return new ParcelableObject[size];
     }
};

 Au niveau de l'ActivitéA nous aurons :

ParcelableObject object = new ParcelableObject("Farouk", "f.ferjani.farouk@gmail.com", "FERJANI", false, 24);
Intent intent = new Intent(this, ClassB.class);
intent.putExtra(Key, object);
startActivity(intent);

 Au niveau de l'ActivitéB nous aurons :


Bundle extras = getIntent().getExtras();
if (extras != null) {
    ParcelableObject doc = extras.getParcelable(Key);
}
            
  Codecomplet de la classe ParcelableObject 

package com.example.tutoparcelable;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Document.
 *
 * @author Farouk
 *
 */
public class ParcelableObject implements Parcelable {
       private String nom = null;
       private String prenom = null;
       private String mail = null;
       private int age;
       private boolean estEtudiant;

       public ParcelableObject(final String prenom, final String mail,
                    final String nom, final boolean estEtudiant, final int age) {
             this.prenom = prenom;
             this.nom = nom;
             this.mail = mail;
             this.age = age;
             this.estEtudiant = estEtudiant;
       }

       public ParcelableObject(Parcel in) {
             readFromParcel(in);
       }

       public final String getNom() {
             return nom;
       }

       public final String getPrenom() {
             return prenom;
       }

       public final String getMail() {
             return mail;
       }

       public final int getAge() {
             return age;
       }

       public final boolean isEtudiant() {
             return estEtudiant;
       }

       @Override
       public void writeToParcel(Parcel dest, int flags) {
             dest.writeString(nom);
             dest.writeString(prenom);
             dest.writeString(mail);
             boolean droits[] = { estEtudiant };//Pour les boulean on ne peut passer qu'un BooleanArray
             dest.writeBooleanArray(droits);
             dest.writeInt(age);
       }

       private void readFromParcel(Parcel in) {
             nom = in.readString();
             prenom = in.readString();
             mail = in.readString();
             boolean droits[] = new boolean[1];
             in.readBooleanArray(droits);
             estEtudiant = droits[0];
             age = in.readInt();
       }

       @SuppressWarnings("rawtypes")
       public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
             public ParcelableObject createFromParcel(Parcel in) {
                    return new ParcelableObject(in);
             }

             public ParcelableObject[] newArray(int size) {
                    return new ParcelableObject[size];
             }
       };

       @Override
       public int describeContents() {
             // TODO Auto-generated method stub
             return 0;
       }
}