jan
22
2012

[Tutoriel Android] Partie 18 – Contrôles personnalisés

Aujourd’hui nous allons aborder le thème des contrôles personnalisés.

Tout d’abord qu’est-ce qu’un contrôle personnalisé ? (Custom control ou encore Custom component).

Hé bien un c’est un composant que nous allons utiliser dans notre application pour afficher une donnée, nous allons développer un contrôle personnalisé car soit ce composant n’existe pas dans le SDK Android, soit parce que cet élément est redondant dans toute l’interface.

Le cas où le composant n’existe pas

  • Diagramme (camembert,bâton,etc…)
  • Compteur de vitesse
  • Thermomètre
  • Etc…

Cas où le composant existe mais on le modifie pour le réutiliser tout le temps

  • Chargement d’une image
  • Bannière publicitaire
  • Menu
  • etc…

Héritage !

L’héritage est très important dans la création d’un contrôle.

Les contrôles se différencies avec deux grands types

  1. Affichage à base de canvas (View)
  2. Affichage à base d’OpenGL (SurfaceView)
A vous de choisir en fonction de ce que vous voulez faire, par exemple pour la galerie d’image Android, une SurfaceView sera plus performante.
Ensuite il est possible d’utiliser plusieurs méthodes pour créer un contrôle à base de View.
  1. On part d’une base vierge (View)
  2. On part d’un élément existant (TextView,etc…)
  3. On part d’une liste d’éléments (ViewGroup,LinearLayout,etc…)
Vous avez plusieurs possibilités selon votre besoin.
Partir d’une base vierge demande pas mal de travail au niveau conception pour rendre son contrôle performant et adaptable sur tous les écrans (échelle etc..).

Chargement d’une image asynchrone

Imaginons que nous avons une ListView avec des images à charger, pour rendre notre application performante nous allons utiliser un thread pour charger nos images sinon on risque de bloquer l’utilisateur si la vitesse de téléchargement est trop lente.

Donc il nous faut 1 ImageView et 1 Thread à chaque fois, mais c’est embêtant j’ai 2 activités qui utilises le même fonctionnement, voir même plusieurs applications !

Pas d’inquiétudes matelot nous allons faire un contrôle pour cela

Tout d’abord on va partir d’un contrôle existant (ImageView) puis embarquer un Thread dans ce contrôle pour gérer le chargement en asynchrone (AsyncTrask).

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.widget.ImageView;

/***
 * ImageLoader
 *
 * @author ace
 *
 */
public class ImageLoaderView extends ImageView {

	/**
	 * Constructeur hérité
	 *
	 * @param context
	 * @param attrs
	 */
	public ImageLoaderView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public void loadImage(String url) {
		new DownloadImage(this).execute(url);
	}

	private class DownloadImage extends AsyncTask {

		final ImageView imageView;

		public DownloadImage(ImageView imageView) {
			this.imageView = imageView;
		}

		@Override
		protected Bitmap doInBackground(String... params) {
			if (params.length == 0)
				return null;

			try {
				URL url = new URL(params[0]);
				InputStream stream = url.openStream();

				Bitmap bf = BitmapFactory.decodeStream(stream);

				return bf;

			} catch (MalformedURLException e) {
				return null;
			} catch (IOException e) {
				return null;
			}
		}

		@Override
		protected void onPostExecute(Bitmap result) {
			if(result != null)
				imageView.setImageBitmap(result);

			super.onPostExecute(result);
		}

	}

}

Je vous donne la classe directement, pas besoin d’explication vous savez déjà charger une image (Partie 8) et vous connaissez aussi les threads (Partie 9).

Nous allons maintenant utiliser notre nouveau composant dans notre layout principal

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<com.aceart.formation.component.ImageLoaderView
android:id="@+id/imageLoader1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<com.aceart.formation.component.ImageLoaderView
android:id="@+id/imageLoader2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<com.aceart.formation.component.ImageLoaderView
android:id="@+id/imageLoader3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

</LinearLayout>

Trois images loader avec chacun un ID.
Dirigeons nous maintenant vers notre activité

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final ImageLoaderView imgL1 = (ImageLoaderView) findViewById(R.id.imageLoader1);
        imgL1.loadImage("http://www.laptopspirit.fr/wp-content/uploads/new/mandriva-logo.thumbnail.jpg");

        final ImageLoaderView imgL2 = (ImageLoaderView) findViewById(R.id.imageLoader2);
        imgL2.loadImage("http://i73.photobucket.com/albums/i240/jazzk1/android-logo-mask.png");

        final ImageLoaderView imgL3 = (ImageLoaderView) findViewById(R.id.imageLoader3);
        imgL3.loadImage("http://upload.wikimedia.org/wikipedia/fr/7/78/Quakelive_logo_300.png");

    }

On récupère nos ImageLoader et on effectue l’action de chargement.
Lancer l’application et miracle le chargement est en asynchrone et pas besoin de réécrire le code et qui plus est vous pouvez réutiliser ce code dans les ListView etc…

Création d’un graphique

Nous allons maintenant voir comment créer un custom components grâce au canvas.

Les étapes :

  1. Définir le modèle de données (Classe Item)
  2. Créer le contrôle (Héritage de la classe View)
  3. Implémenter la méthode onDraw
  4. Dessiner le diagramme grâce au canvas
  5. Afficher

Rien de plus simple en fait.

Voici la classe item que nous utiliserons pour fournir en données notre vue

public class PieChartItem {
	private double value;
	private int color;

	public PieChartItem(double value, int color) {
		this.value = value;
		this.color = color;
	}
	public double getValue() {
		return value;
	}
	public void setValue(double value) {
		this.value = value;
	}
	public int getColor() {
		return color;
	}
	public void setColor(int color) {
		this.color = color;
	}
}

Notre objet contient une valeur et une couleur.

Notre vue maintenant

public class PieChartView extends View {

	private List<PieChartItem> items;

	public PieChartView(Context context, AttributeSet attrs) {
		super(context, attrs);
		items = new ArrayList&lt;PieChartItem&gt;();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		drawChart(canvas);
		super.onDraw(canvas);
	}

	private void drawChart(Canvas canvas) {
		if (items != null) {
		}
	}
}

On a bien hérité de la vue, on a le bon constructeur et la méthode onDraw est implémentée :w00t:
Il reste plus qu’a appeler notre petit diagramme dans le layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:orientation="vertical">

<com.aceart.formation.component.PieChartView
 android:id="@+id/pieChart"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:background="@android:color/white" />

</LinearLayout>

Je vous donnes le code d’exemple tout de même pour pouvoir vous amusez :lol:

Classe complète:

public class PieChartView extends View {

	private List<PieChartItem> items;

	public PieChartView(Context context, AttributeSet attrs) {
		super(context, attrs);
		items = new ArrayList&lt;PieChartItem&gt;();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		drawChart(canvas);
		super.onDraw(canvas);
	}

	private void drawChart(Canvas canvas) {
		if (items != null) {

			// angle de départ
			float angleDepart = 0.f;
			double somme = getSomme();

			// Zone carrée
			RectF zoneDessin = new RectF(0, 0, getWidth(), getWidth());

			// Pinceau
			Paint paint = new Paint();
			paint.setAntiAlias(true);
			paint.setStyle(Paint.Style.FILL);

			// On passe chaque item en revue
			for (PieChartItem item : items) {

				// on change la couleur
				paint.setColor(item.getColor());

				// On calcul l'angle d'ouverutre
				float sweepAngle = (float) ((item.getValue() / somme) * 360);

				// On dessine l'arc
				canvas.drawArc(zoneDessin, angleDepart, sweepAngle, true, paint);

				// On change l'angle de départ
				angleDepart += sweepAngle;
			}
		}
	}

	private double getSomme() {
		double somme = 0;

		for (PieChartItem item : items) {
			somme += item.getValue();
		}

		return somme;
	}

	public void addValue(PieChartItem item) {
		this.items.add(item);
	}

	public void setValues(List&lt;PieChartItem&gt; items) {
		this.items = items;
	}

}

Les appels dans l’activité principale :

public class FormationCustomComponentActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final PieChartView pieChartView = (PieChartView) findViewById(R.id.pieChart);

        pieChartView.addValue(new PieChartItem(25, Color.GREEN));
        pieChartView.addValue(new PieChartItem(75, Color.RED));

    }
}

Et le rendu pour finir

Maintenant à vous de vous amuser à créer vos propres composants.

  • Cyrille

    Merci bien pour tes tutos qui sont géniaux.
    Peux tu expliquer par contre la ligne suivante dans le constructeur.
    items = new ArrayList<PieChartItem>(); 
    Je ne comprends pas la syntaxe.
    Merci.

  • http://www.ace-art.fr/wordpress Acesyde

    Cette ligne permet d’initialiser le tableau de données, sinon tu auras un NullPointerException ;)