Android offre un système de communication trés ingénieux permettant de faire passer l’information entre Activitys ou plus généralement composants applicatifs . Ce système, déjà mentionné dans une partie précédente, est connu sous le nom d’Intent. Nous verrons donc dans cette partie comment transmettre l’information d’une Activity à une autre à l’aide de ce mécanisme d’Intents. Les lecteurs n’ayant toujours pas compris la différence subtile entre Activity et View seront probablement éclairé par la lecture des lignes suivante.

Introduction

Android sandbox les applications. Le mot “sandbox” est un anglicisme qui signifie simplement “bac à sable”. Il vous paraît très probablement étrange de parler de “bac à sable” dans un problème d’informatique mais c’est pourtant bel et bien la traduction adéquate dans le contexte. Le sandboxing est une pratique de plus en plus courante dans la téléphonie mobile qui consiste à séparer presque totalement les applications entre elles. Lorsque Android exécute une application, il restreint cette dernière à des actions bien définies (accès mémoire, accès sur les capteurs, etc…). Cette pratique permet de protéger le système au maximum en évitant de laisser des applications faire comme bon leurs semble.

Malgré l’énorme apport sur la sécurité, le sandoxing restreint fortement la communication entre applications. C’est dans l’optique de contourner ce “problème”, que les Intents ont été conçus. Pour faire simple, un Intent est un ensemble de données qui peut être passé à un autre composant applicatif (de la même application ou non) de façon implicite (requête pour une action - lire de la musique ou scanner un code barre par exemple) ou explicite (lancement d’une classe précise).

Le développement sur iPhone suit également cette règle de “sandboxing”. Malheureusement le système de communication entre application appelé “URL scheme” est beaucoup moins évolutif et permissif. Ce dernier consiste simplement à enregistrer l’application auprès du système qui laissera alors les applications démarrer l’application pour une URI (sous Safari par exemple). Cela montre très clairement l’avantage d’Android sur l’iPhone en matière de communication entre composants applicatifs. De plus, ce qui est faisable avec l’iPhone l’est très souvent sous Android - et inversement.

Démarrage d’une Activity grâce aux Intents

Pour bien décrire le système des Intents, nous allons créer une petite application (sans aucun but réel, je l’accorde) composée de 2 écrans : le premier dispose d’un bouton permettant de démarrer une nouvelle Activity de façon explicite. La seconde Activity affichera simplement un champ de recherche permettant d’effectuer une recherche sur Google. Le code de cette application est disponible dans ce dossier zippé.

Démarrage d’une activité de façon explicite

Pour commencer, créons une première Activity qui affichera un unique bouton central. Le code XML ci-dessous est composé d’une unique balise Button. Cette simplicité de l’interface graphique provient du fait que l’espace disponible au développeur pour afficher son application (l’intégralité de l’écran moins la barre de tâches et la barre de nom) est disponible sous la forme d’un FrameLayout. L’image ci-dessous montre la hiérarchie des vues générées avec ce fichier XML. On aperçoit facilement (en bleu) un FrameLayout.

Les paramètres android:layout_gravity="center" et android:text="Cliquez ici pour démarrer" permettent respectivement de centrer le bouton dans sa vue parente et de définir le texte du bouton.

<?xml version="1.0" encoding="utf-8"?>
<Button
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/button"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:layout_gravity="center"
	android:text="Cliquez ici pour démarrer" />

Notre interface graphique étant prête, il suffit maintenant de créer notre première Activity. Pour faciliter la compréhension, j’ai préféré directement commenter le code. N’oubliez pas d’inclure cette nouvelle Activity dans le fichier Manifest.xml pour qu’Android autorise son exécution.

package com.cyrilmottier.android.tutorial4;
 
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
 
/*
 * La classe EntryPoint implémente l'interface View.OnClickListener. Cela permet ainsi
 * d'enregistrer l'activité auprès des vues pour qu'elle puiise recevoir les évènements de "clic".
 */
public class EntryPoint extends Activity implements View.OnClickListener {
 
	/*
	 * Cette variable permettra de conserver une référence sur le bouton
	 * de l'interface
	 */
	private Button mButton;
 
	/*
	 * La redéfinition de la méthode onCreate(Bundle) permet d'effectuer des actions
	 * supplémentaires à l'Activity de base.
	 */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*
         * On créé l'interface graphique en utilisant le fichier entry.xml.
         * Ce dernier étant disponible comme ressource (dans /res/layout), il suffit d'utiliser
         * le fichier R.java et sa sous-classe layout.
         */
        setContentView(R.layout.entry);
        /*
         * Récupère une référence sur le bouton en utilisant son identifiant
         */
        mButton = (Button)findViewById(R.id.button);
        /*
         * On enregistre l'activité auprès du bouton pour recevoir les évènements
         * "clic" provoqué par l'utilisateur.
         */
        mButton.setOnClickListener(this);
    }
 
    /*
     * La méthode onClick(View) provient de l'interface View.OnClickListener.
     */
	@Override
	public void onClick(View v) {
		if (v == mButton) {
			/*
			 * Nous sommes maintenant sûr que la vue ayant été cliquée est le bouton
			 * de notre interface. Il suffit donc de créer un nouvel Intent pour démarrer
			 * la seconde activité.
			 */
			Intent intent = new Intent(EntryPoint.this, GoogleSearch.class);
			startActivity(intent);
		}
	}
}

Notre première Activity est maintenant terminée. Le lancement d’une nouvelle Activity de façon explicite s’effectue en deux lignes. La première, Intent intent = new Intent(EntryPoint.this, GoogleSearch.class); crée simplement un nouvel Intent dont le contexte de départ est l’Activity courante (EntryPoint.this) et l’Activity de destination se nomme GoogleSearch.

Démarrage d’une activité de façon implicite

Il ne nous reste maintenant plus qu’à définir l’Activity GoogleSearch en utilisant des principes similaires à ceux précédemment décrits. Commençons, tout d’abord par créer l’interface graphique :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="wrap_content"
	android:layout_gravity="center"
	android:padding="10px">
 
	<TextView
		android:text="Entrez la recherche à effectuer dans le champ ci-dessous puis cliquez sur le bouton"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:layout_marginBottom="10px"
		android:gravity="center_horizontal" />
 
	<EditText
		android:id="@+id/editText"
		android:layout_marginBottom="5px"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
 
	<ImageButton
		android:id="@+id/imageButton"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/logo_google"
		android:layout_gravity="center"></ImageButton>
 
</LinearLayout>

Il ne me semble pas très important de décrire le fichier XML précédent car il peut être considéré comme classique. Il est à noter, tout de même, une légère différence avec les fichiers XML de notre HelloWorld : les ressources de type chaines de caractères, dimensions, etc. ne sont pas externalisés dans un fichier externe. Dans notre cas cela n’a pas réellement d’impact puisque nous n’allons pas internationaliser l’application.

package com.cyrilmottier.android.tutorial4;
 
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
 
public class GoogleSearch extends Activity implements View.OnClickListener {
 
	private ImageButton mImageButton;
	private EditText mEditText;
 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.google);
		mImageButton = (ImageButton)findViewById(R.id.imageButton);
		mEditText = (EditText)findViewById(R.id.editText);
		mImageButton.setOnClickListener(this);
	}
 
	@Override
	public void onClick(View view) {
		if (view == mImageButton) {
			final String requete = "http://www.google.fr/search?q=" + mEditText.getText();
			Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(requete));
			startActivity(intent);
		}
	}
}

Un clic sur le bouton “Google” créé un nouvel Intent à l’aide du constructeur Intent(String,Uri). La chaine de caractère passée en paramètre est une action à effectuer (ici ACTION_VIEW : action définie par le framework qui consiste à démarrer un navigateur web sur l’Uri donnée). On démarre enfin l’activité par un simple startActivity(Intent). Une action ne définissant pas une application en particulier, Android va tenter de chercher une application s’étant définie comme capable de répondre à l’action ACTION_VIEW. C’est cette “non certitude” sur l’application à ouvrir qui explique le nom de cette méthode : implicite. Généralement le navigateur utilisé est celui inclus de base dans le téléphone.

Conclusion

Les Intents sont des composants essentiels de l’architecture Android. Le démarrage explicite ou implicite d’une Activity à l’aide d’Intents n’est qu’une petite infime partie des possibilités de ce mécanisme. Cette partie fait donc office d’introduction aux Intents et vous permet de mieux comprendre la communication entre composants applicatifs Android. Vous êtes maintenant prêts à coder vos premiers programmes à écrans multiples !

La programmation sur terminaux mobiles est régie par de nombreuses contraintes souvent inhérentes à l’accès à des ressources limitées : puissance processeur faible, quantité de mémoire disponible limitée, durée de vie de la batterie courte, bande-passante faible, etc. La réalisation d’interfaces graphiques met rapidement en évidence un autre problème du développement sur OS mobiles : une taille d’écran relativement petite. Les designers sont donc confrontés à des problèmes d’agencements de l’information et des widgets dans l’interface. Il sont, de plus, contraints à utiliser des éléments graphiques de taille assez conséquente afin que ces derniers puisse être activés au toucher. Conserver la facilité d’utilisation et l’ergonomie d’une interface mobile passe donc souvent par la suppression d’éléments dans l’interace.

Android 1.5 introduit un widget, déjà connu de beaucoup d’utilisateurs, permettant de résoudre partiellement le problème : le SlidingDrawer. Cet élément graphique avait fait son apparition à la première release d’Android dans le HomeScreen mais n’était pas disponible en tant que widget dans l’API Android. Ce widget se comporte comme un tiroir : tirer ou cliquer sur la poignée provoque son ouverture et affiche alors l’ensemble des éléments présents à l’intérieur comme dans la figure ci-dessous. Cela permet de ranger des éléments non essentiel au premier abord tout en les conservant à portée de main.

L’utilisation de ce SlidingDrawer est relativement aisée et consiste à définir une interface du type :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
 
	<View
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:background="#ffff00" />
 
 
	<SlidingDrawer
		android:id="@+id/drawer"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:handle="@+id/handle"
		android:content="@+id/content"
		android:orientation="vertical">
 
		<ImageView
			android:id="@+id/handle"
			android:background="#00ff00"
			android:src="@drawable/arrow"
			android:layout_width="88dip"
			android:layout_height="44dip" />
 
		<GridView
			android:id="@+id/content"
			android:background="#fff"
			android:layout_width="fill_parent"
			android:layout_height="fill_parent" />
 
	</SlidingDrawer>
 
</FrameLayout>

Comme le montre le code précédent, l’instanciation d’un SlidingDrawer se fait par XML (il n’est pas possible de créer un SlidingDrawer de façon manuelle ou difficilement) en définissant 2 vues filles qui représente le handle (la poignée) et le content (le contenu). Les attributs android:handle et android:content permettant de préciser au SlidingDrawer les vues handle et content sont obligatoires.

Les options disponibles sur le widget SlidingDrawer ne sont pas nombreuses mais offre de belles perspectives :

  • android:orientation : définit l’orientation horizontale ou verticale. Le sliding drawer “glissera” alors de bas en haut (horizontale) ou de droite à gauche (verticale).
  • android:topOffset & android:bottomOffset : permettent de définir des marges en haut et en bas du drawer.
  • android:allowSingleTap & android:animateOnClick : permet d’activer ou non l’ouverture du SlidingDrawer par un clic sur la poignée et d’activer ou non l’animation à l’ouverture/fermeture.

Il est enfin possible de suivre l’évolution du SlidingDrawer et ainsi de déterminer si ce dernier est ouvert, fermé, etc. Pour ce faire le widget fournit les méthodes isOpen() et isMoving() ainsi qu’un ensemble de listeners : SlidingDrawer.OnDrawerCloseListener,
SlidingDrawer.OnDrawerOpenListener et SlidingDrawer.OnDrawerScrollListener.

L’ajout de ce nouvel élément graphique va trés probablement apporter de nombreux améliorations dans les futures interfaces graphiques des applications Android. Je regrette néanmoins l’absence de certaines options ou l’incohérence des méthodes. En effet, il est impossible de faire en sorte qu’un SlidingDrawer glisse de bas en haut. Un attribut android:handleGravity permettant de positionner la poignée à droite, à gauche ou au centre fait également défaut (même si il semble difficile de justifier une position qui varie en fonction de l’orientation : “gauche” devenant “bas” par exemple). On aurait également pu définir des SlidingDrawer multiple (en association avec la méthode bringChildToFront(View child) de ViewGroup afin de s’assurer du bon “empilement” des vues dans le FrameLayout ou RelativeLayout englobant.

Je déplore enfin le système de listeners associé à SlidingDrawer. Les listeners se décomposent en effet en 3 interfaces différentes ce qui n’est pas, à mon humble avis, Android-friendly. Il aurait été préférable de définir un listener global définit ci-dessous et permettant la gestion multiple de SlidingDrawers dans une même classe :

public static interface OnDrawerChangeListener {
  /**
   * Invoked when the drawer becomes fully open.
   */
  public void onDrawerOpened(SlidingDrawer drawer);
 
  /**
   * Invoked when the drawer becomes fully closed.
   */
  public void onDrawerClosed(SlidingDrawer drawer);
 
  /**
   * Invoked when the user starts dragging/flinging the drawer's handle.
   */
  public void onScrollStarted(SlidingDrawer drawer);
 
  /**
   * Invoked when the user stops dragging/flinging the drawer's handle.
   */
  public void onScrollEnded(SlidingDrawer drawer);
}

Je pense que l’équipe de développement des widgets Android a longuement réfléchit à ce problème et a considéré qu’un SlidingDrawer ne devait être qu’unique dans une même Activity. L’ajout d’un paramètre mentionnant quel SlidingDrawer a changé est dans ce cas inutile.

Introduction

Voilà maintenant un petit moment que je souhaitais parler de la gestion de ce que j’appelle les TouchEvent sous Android. Les TouchEvent sont les actions utilisateur générées lorsque ce dernier interagit avec l’écran. Le framework Android propose plusieurs façon de gérer ces évenements :

  • Hériter de la classe View : il est ainsi possible de redéfinir la méthode onTouchEvent(MotionEvent event) appelée par le système lors d’un TouchEvent
  • Utiliser la notion de Listener : if suffit d’implémenter l’interface View.OnTouchListener et de s’inscrire auprès de la vue sur laquelle on souhaite suivre les évènements par un simple setOnTouchListener(OnTouchListener listener)
  • Hériter de la classe Activity : cette classe comporte une méthode onTouchEvent(MotionEvent event) qui est appelée si aucune des vues présente dans l’activité n’a consommé l’évènement. C’est en quelque sorte le dernier moyen de récupérer un évènement avant qu’il ne soit tout simplement perdu

Le but de ce post n’est pas d’expliquer comment gérer les TouchEvents dans votre application mais plutôt de comprendre comment Android transporte l’événement dans votre arborescence de Views. Si vous souhaitez simplement étudier les TouchEvent et leur utilisation dans vos vues, je vous conseille de lire la Javadoc associée à MotionEvent. Pour résumer, il suffit de regarder la valeur de event.getAction() pour savoir ce que l’utilisateur vient de faire. Les valeurs possibles sont :

  • MotionEvent.ACTION_DOWN : L’utilisateur vient d’appuyer sur l’écran. C’est la première valeur récupérée suite à une action sur l’écran
  • MotionEvent.ACTION_MOVE : Fait suite à l’événement précédent et indique que l’utilisateur n’a pas relaché la pression sur l’écran et est en train de bouger
  • MotionEvent.ACTION_UP : Envoyé lorsque l’utilisateur cesse d’appuyer sur l’écran
  • MotionEvent.ACTION_CANCEL : Action un peu spéciale dont je parlerai dans la suite de cet article

Préparation du cadre

Elaborons tout d’abord le cadre de notre étude : nous disposons d’une activité affichant à l’écran l’interface partiellement donnée ci-dessous. Cette dernière est composée d’un FrameLayout affichant 3 carrés empilés les uns sur les autres :

<FrameLayout
    android:id="@+id/frameLayout"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
 
    <TextView
        android:text="Z = 3"
        android:gravity="bottom|right"
        android:padding="10px"
        android:textColor="#000"
        android:background="#0000ff"
        android:layout_width="240px"
        android:layout_height="200px" />
 
    <TextView
        android:id="@+id/z2"
        android:text="Z = 2"
        android:gravity="bottom|right"
        android:padding="10px"
        android:textColor="#000"
        android:background="#00ff00"
        android:layout_width="160px"
        android:layout_height="150px" />
 
    <TextView
        android:id="@+id/z1"
        android:text="Z = 1"
        android:gravity="bottom|right"
        android:padding="10px"
        android:textColor="#000"
        android:background="#ff0000"
        android:layout_width="80px"
        android:layout_height="100px" />
 
</FrameLayout>

Le code source de l’activité principale est assez succinct et utilise la technique des listeners pour récupérer les TouchEvents :

package com.cyrilmottier.android.touchevent;
 
import android.app.Activity;
import android.os.Bundle;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.ToggleButton;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
 
public class TouchActivity extends Activity implements View.OnTouchListener {
 
    private static final String TAG_LOG = "TouchActivity";
    private static final boolean LOG = true;
 
    private FrameLayout mFrameLayout;
    private TextView mZ1;
    private TextView mZ2;
 
    private ToggleButton mButtonZ1;
    private ToggleButton mButtonZ2;
    private ToggleButton mButtonFrameLayout;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        mFrameLayout = (FrameLayout)findViewById(R.id.frameLayout);
        mZ1 = (TextView)findViewById(R.id.z1);
        mZ2 = (TextView)findViewById(R.id.z2);
        mButtonFrameLayout = (ToggleButton)findViewById(R.id.buttonFrameLayout);
        mButtonZ1 = (ToggleButton)findViewById(R.id.buttonZ1);
        mButtonZ2 = (ToggleButton)findViewById(R.id.buttonZ2);
 
        mZ1.setOnTouchListener(this);
        mZ2.setOnTouchListener(this);
        mFrameLayout.setOnTouchListener(this);
 
    }
 
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (v == mZ1) {
            log("mZ1: " + stringValue(event));
            return mButtonZ1.isChecked();
        } else if (v == mZ2) {
            log("mZ2: " + stringValue(event));
            return mButtonZ2.isChecked();
        } else if (v == mFrameLayout) {
            log("mFrameLayout: " + stringValue(event));
            return mButtonFrameLayout.isChecked();
        }
        return false;
    }
 
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        log("Activity: " + stringValue(event));
        return true;
    }
 
    private String stringValue(MotionEvent event) {
 
        final int action = event.getAction();
 
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                return "ACTION_DOWN";
            case MotionEvent.ACTION_MOVE:
                return "ACTION_MOVE";
            case MotionEvent.ACTION_UP:
                return "ACTION_UP";
            case MotionEvent.ACTION_CANCEL:
                return "ACTION_CANCEL";
        }
 
        return "";
    }
 
    private static void log(String message){
        if (LOG) {
            Log.d(TAG_LOG, message);
        }
    }
}

Le code complet de ce programme est disponible dans le zip accessible à cette adresse. Je vous conseille vivement de lancer vous même ce programme car il vous permettra de tester de façon plus approfondie et interactive les points que nous traiterons ci-dessous. Une fois lancé, vous devriez obtenir une interface comme suit :

Comportement d’Android face aux TouchEvents

Nous voilà près à analyser la façon dont Android gère et fait naviguer l’information dans l’arborescence des vues. Nous parlions en introduction de “consommer les évènements”. Pour informer Android qu’un évènement à été consommé ou non par une vue, il faut gérer le retour de la méthode s’occupant des TouchEvent. Dans le cas de l’exemple, on utilise la méthode onTouch(MotionEvent event) et on gère le code de retour (true ou false) en fonction de l’état des ToggleButton.

En laissant inactif l’ensemble des vues (ToggleButtons éteints), puis en effectuant un TouchEvent, la sortie de débogage affiche une séquence concordant avec le modèle suivant :

05-05 16:52:23.225: DEBUG/TouchActivity(723): mZ1: ACTION_DOWN
05-05 16:52:23.225: DEBUG/TouchActivity(723): mZ2: ACTION_DOWN
05-05 16:52:23.245: DEBUG/TouchActivity(723): mFrameLayout: ACTION_DOWN
05-05 16:52:23.255: DEBUG/TouchActivity(723): Activity: ACTION_DOWN
05-05 16:52:24.264: DEBUG/TouchActivity(723): Activity: ACTION_MOVE
05-05 16:52:24.305: DEBUG/TouchActivity(723): Activity: ACTION_MOVE
05-05 16:52:25.150: DEBUG/TouchActivity(723): Activity: ACTION_UP

Ce premier test nous permet d’en savoir plus sur la gestion de l’information de touch. Android envoie d’abord l’information à la vue (sur laquelle on appui bien sûr) de plus haut niveau, c’est à dire celle étant en sommet de pile. Si cette dernière ne consomme pas l’évenement, il est retransmis à la vue directement inférieure. Cette action se répète jusqu’à ce qu’une vue consomme l’évènement. Lorsqu’aucune vue ne consomme l’évènement, c’est l’activité qui sert de dernier “recourt”.

On réitère maintenant l’opération mais en activant la seconde vue (c’est à dire Z = 2). Une pression suivie d’un mouvement et d’un relâchement sur la vue Z = 1 (vue rouge) donne la sortie suivante :

05-05 16:52:53.104: DEBUG/TouchActivity(723): mZ1: ACTION_DOWN
05-05 16:52:53.114: DEBUG/TouchActivity(723): mZ2: ACTION_DOWN
05-05 16:52:55.585: DEBUG/TouchActivity(723): mZ2: ACTION_MOVE
05-05 16:52:56.826: DEBUG/TouchActivity(723): mZ2: ACTION_MOVE
05-05 16:52:57.214: DEBUG/TouchActivity(723): mZ2: ACTION_UP

En gardant la même configuration et répétant la même procédure mais sur la vue Z = 3, on revient au premier cas cité : l’activité est donc la dernière à gérer l’évènement.

Pour résumer, Android passe simplement l’information de la vue de plus haut niveau à la vue de plus bas niveau. Ce passage d’information est effectué jusqu’à ce qu’une vue consomme (renvoie true lors de la gestion du TouchEvent). Dès lors que l’évènement ACTION_DOWN a été consommé, l’ensemble des évènements suivants (ACTION_DOWN et ACTION_UP) sont directement envoyés à la vue dite cible.

Interception de TouchEvents

La gestion des TouchEvents est, après étude succincte, tout à fait logique. Il existe également une autre méthode permettant de contourner certains problèmes relatifs aux ViewGroups. En effet, prenons un ViewGroup contenant un ensemble d’ImageView. L’utilisateur peut faire “glisser” les images de l’une à l’autre un peu à l’instar de la galerie photo de l’iPhone. Il peut être intéressant de faire en sorte que la ViewGroup gère directement sans se soucier du retour des onTouchEvent(MotionEvent event) des ImageViews. Pour ce faire, on utilise la méthode onInterceptTouchEvent(MotionEvent event).

La documentation incluse dans le SDK explique de façon exhaustive cette méthode et peut se résumer de la façon suivante :

  • 1 : L’évènement ACTION_DOWN est reçu ici (comprendre la ViewGroup
  • 2 : L’évènement circule de vue en vue comme expliqué précédemment. Si la méthode onTouchEvent(MotionEvent event) reçoit l’évènement ACTION_DOWN, il suffit de faire en sorte que cette dernière renvoit true. Ainsi les évènements suivants ne sont plus reçus par la méthode onInterceptTouchEvent(MotionEvent event) mais passe directement à onTouchEvent(MotionEvent event)
  • 3 : Tant que cette méthode retourne false, les évènements seront envoyés à la fois dans cette méthode et à la vue cible (celle ayant consommé l’ACTION_DOWN)
  • 4 : Si true est retourné, aucun autre évènement n’est envoyé à cette méthode et la vue cible reçoit l’action ACTION_DOWN. Les évènements suivant sont envoyés à la méthode onTouchEvent(MotionEvent event) de la ViewGroup

Les méthodes disponibles et les façons de récupérer les TouchEvents sous Android sont nombreuses. Notez, tout de même, que la philosophie d’Android est de gérer, le plus possible, les évènements au niveau le plus bas dans l’arbre (sur les feuilles) et donc au niveau View. Evitez au maximum de gérer par exemple, les TouchEvents au niveau de l’Activity.