jeudi 12 avril 2012

Tutoriel Android Query: Chargement asynchrone d'images dans une listview

Ce post sera sûrement le premier d'une série de posts consacrés à une bibliothèque que je découvre, Android Query ou aQuery. Cette bibliothèque suit l'état d'esprit du framework jQuery, dans le sens elle permet au développeur de coder moins pour des taches courantes.

Voici un liste de ce qu'elle permet de faire à l'heure actuelle selon son auteur:

  • Moins de code
  • Appels AJAX
  • Chargement des images
  • Parcours des XML
  • le chainage des fonctions
  • l'attachement de listeners
  • l'authentification Facebook, Twitter, Google Reader, Picasa, Youtube, au contacts, emfin åa toutes les API Google ou même à votre propre service.
  • Ne plus se préoccuper de la fragmentation (accélération matérielle, etc...)
  • Ne plus se préoccuper de différents layout pour les différentes tailles d'écran
  • Etc...
Ça paraît prometteur sur le papier, et ça l'est, en tous cas pour ce que j'en ai testé. Pour vous en convaincre, voici une petite démo, qui est déjà en partie obsolète par rapport à la dernière version d'aQuery:



Pour vous montrer la puissance de cette librairie, voici comment tout simplement je l'ai implémentée dans mon appli pour faire un chargement passif de mes image web dans une listview en utilisant un cache sur la SDCard.


  • Tout d'abord, récupérez le projet librairie sur Google Code : http://code.google.com/p/android-query/
  • Ajoutez la bibliothèque à votre projet:
     
  • OPTIONNEL: Au chargement de mon activité, j'ai préféré désigner moi-même un répertoire de cache sur ma carte SD plutôt que de garder le répertoire par défaut dans le dossier interne de l'appli:

    // Le dossier de cache sera créé dans le répertoire de stockage "externe" dans le dossier prévu à cet effet sous le nom du package de votre appli
    String dir = Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + getPackageName() + "/files/images";
    
    final File directory = new File(dir);
    
    // On teste tout de même si le stockage externe est dispo
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()))
    {
        // Fichier qui va indiquer au système que notre dossier ne contient rien qui intéresse la galerie 
        File nomedia = new File(dir + "/.nomedia");
        if (!nomedia.exists())
        {
            try
            {
                directory.mkdirs();
                nomedia.createNewFile();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
        AQUtility.setCacheDir(directory);
    }
    
    
    
  • Au chargement de ma ListActivity (ou de mon ListFragment en l'occurence), j'initialise une instance de aQuery:

    listAq = new AQuery(this.getActivity());

  • Dans mon adapter, je charge l'image à partir de son URL web. Si elle est déjà en cache je l'affiche, sinon je la télécharge:


    public View getView(int position, View convertView, ViewGroup parent)
    {
        View v = super.getView(position, convertView, parent);
    
        //la fonction recycle permet de ne pas dupliquer l'objet aQuery de nombreuses fois lors du parcours de la liste
        AQuery aq = listAq.recycle(v);
    
        
        final String url = "http://une.url.vers/mon.image.jpg";
        Bitmap placeholder = aq.getCachedImage(url);
    
        //Si la liste est en train de défiler rapidement on affiche l'image en mémoire ou rien (ou un placeholder, mais dans mon cas le placeholder est l'image en cache):
        if(aq.shouldDelay(position, v, parent, url))
        {
         
            aq.id(R.id.ImageView_icon).image(placeholder,1f);
     
        //Sinon on télécharge l'image     
        }else
        {
            //Si l'image était en cache en la charge
            if(placeholder!=null)
            {
                //la fonction progress gère le chargement de l'image. La vue R.id.progressBar1 reste affichée jusqu'au chargement
                aq.id(R.id.ImageView_icon).progress(R.id.progressBar1).image(placeholder, 1f);
    
            //Si non on la télécharge, toujours avec une vue de chargement
            }else
            {
                //placeholder2 remplacera l'image en cas d'échec du téléchargement
                Bitmap placeholder2 = aq.getCachedImage(R.id.placeholder);
                aq.id(R.id.ImageView_icon).progress(R.id.progressBar1).image(url, true, true, 0, 0, placeholder2, 0, 1f);
            }
        }
    
        return v;
    }
    

Voilà, vous avez un chargement asynchrone de vos images sans bug et dans un cache. Ma façon de l'implémenter est assez verbeuse, mais si vous avez des besoins moins poussés que moi quelques lignes suffisent. Lorsque je testerai d'autres fonctionnalités de aQuery je posterai mes retours. Je ne peux que vous conseiller de lire la doc de la librairie, qui est très bien fournie.

mercredi 11 avril 2012

Menu radial animé sur Android avec Satellite Menu

Au hasard d'une recherche sur des librairies utiles, je suis tombé sur un projet bien sympathique nommé Satellite Menu.

Ce projet s'inspire d'une appli qui a marqué les esprits par son menu radial très original, Path.
Le principe est très simple: un bouton circulaire en bas à gauche de l'écran. Lorsque l'on clique dessus, d'autres bouton viennent graviter autour de celui-ci pour accéder aux fonctionnalités de l'appli.




Voilà dans le principe.
Dans la pratique on va procéder comme suit:

  • Pour utiliser le fameux menu, d'abord déclarez le bouton principal dans votre layout:
     
    <framelayout 
        android:layout_height="fill_parent" 
        android:layout_width="fill_parent" 
        android:orientation="vertical" 
        xmlns:android="http://schemas.android.com/apk/res/android" 
        xmlns:sat="http://schemas.android.com/apk/res/android.view.ext">
    
        <android.view.ext.satellitemenu 
            android:id="@+id/menu" 
            android:layout_gravity="bottom|left" 
            android:layout_height="wrap_content" 
            android:layout_margin="8dp" 
            android:layout_width="wrap_content" 
            sat:closeonclick="true"  
            sat:expandduration="500" 
            sat:mainimage="@drawable/ic_launcher" 
            sat:satellitedistance="170dp" 
            sat:totalspacingdegree="90">    
        </android.view.ext.satellitemenu>
    
    </framelayout>

     Vous noterez le préfixe sat, qui permet de définir les propriété propres au menu circulaire. (vous pouvez jouer avec ces options, je pense qu'elles sont assez explicites).
  • Dans votre activité, déclarez vos boutons comme de façon similaire à un menu classique:

    SatelliteMenu menu = (SatelliteMenu) findViewById(R.id.menu);
            
    List items = new ArrayList();
    items.add(new SatelliteMenuItem(4, R.drawable.ic_1));
    items.add(new SatelliteMenuItem(4, R.drawable.ic_3));
    items.add(new SatelliteMenuItem(4, R.drawable.ic_4));
    items.add(new SatelliteMenuItem(3, R.drawable.ic_5));
    items.add(new SatelliteMenuItem(2, R.drawable.ic_6));
    items.add(new SatelliteMenuItem(1, R.drawable.ic_2));
    
    menu.addItems(items);        
            
    menu.setOnItemClickedListener(new SateliteClickedListener() {
       
     public void eventOccured(int id) {
      Log.i("sat", "Clicked on " + id);
     }
    });
    
Voilà, vous savez désormais comment créer un beau menu radial original pour votre appli Android.

vendredi 6 avril 2012

Vérifier une adresse e-mail en Java

Petit article rapide aujourd'hui, comme l'indique le titre, comment vérifier la validité d'une adresse e-mail sous Android ou en Java.

Il y a plusieurs méthodes, la première étant de se hisser sur les épaules des spécialistes du web, Apache, et leur classe de validation EmailValidator.
Un simple appel au singleton et le tour est joué:

if(EmailValidator.getInstance().isValid("climbatize.reload@gmail.com"))
{
    Log.d(TAG,"Cette adresse est valide");
} 

Une autre méthode consiste à utiliser une expression régulière. Voici une méthode java avec celle utilisée par la messagerie K-9:


 
public final static Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile(
           "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
           "\\@" +
           "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
           "(" +
           "\\." +
           "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
           ")+"
       );

public static boolean isValiEmail(String email)
{
    return EMAIL_ADDRESS_PATTERN.matcher(email).matches();
}




Maintenant à vous de choisir, il existe d'autres expressions régulière et d'autres méthodes, et je suis prèt à en débattre avec vous :)

Twitter