Ce post fait suite à celui de mon ami et confrère Yakhya
Dabo qui traite de la
création de vues sur Android. Après lecture de son article, vous aurez
compris qu’il est préférable d’utiliser sans modération des ressources et des
ficher XML pour la création des vues.
Cependant, j’ai pensé qu’il peut être intéressant de rajouter à l’analyse
de mon ami, une technique souvent utilisée par les développeurs pour créer des
vues personnalisées.
Cette méthode consiste à définir la classe java qui
implémente la vue directement dans le fichier XML représentant cette dernière puis
d’utiliser le LayoutInflater pour l’instancier.
Vous vous dites sûrement que c’est bien beau tout ce
discours mais à quoi ça peut bien servir ?
D’une part, outre le fait de dissocier la représention
graphique de son implémentation, cette technique permet de faciliter le
découplage entre les différentes classes de votre application. En effet, la
classe qui va créer la vue n’a pas forcément besoin de connaître celle qui l’implémente
du moment que cette dernière respecte le contrat définit à l’aide d’une interface java (voir exemple).
D’autre part, cela nous permet de visualiser avec l’éditeur graphique d’Eclipse la vue telle qu’elle serait affichée sur l’appareil mobile en utilisant les méthodes isInEditMode() et onFinishInflate() pour éviter des erreurs d’initialisation de la vue en fonction du contexte.
D’autre part, cela nous permet de visualiser avec l’éditeur graphique d’Eclipse la vue telle qu’elle serait affichée sur l’appareil mobile en utilisant les méthodes isInEditMode() et onFinishInflate() pour éviter des erreurs d’initialisation de la vue en fonction du contexte.
Par ailleurs, il faut aussi noter que cette façon de faire n’empêche
en rien de faire usage de l’include
pour l’insertion de notre vue dans une autre composition.
Pour illustrer ce concept, je vous propose de jeter un coup
d’œil sur l’exemple suivant qui permet de créer une boite de dialogue
personnalisée afin d’afficher différents types de messages d’erreur :
- Le contrat définissant l’affichage
d’un message d’erreur :
/**
* Base interface for a message shower
* @author ndongo
*
*/
public interface MessageShower {
/**
*
The listener of the user response
*/
public static interface MessageShowerListener{
/**
* Call back of the confirmation button
*/
public
void onConfirm();
/**
* Call back of the cancel button
*/
public void
onCancel();
}
/**
* The
type of the message to show
*
@author ndongo
*
*/
public static enum MessageType{
NetworkConnectionFailed,
DataBaseConnectionFailed,
// others
type of messages...
}
/**
*
Sets the type of the message to show
*
@param type the type
*/
public void setType(MessageType type);
/**
*
Sets the listener
*
@param listener the listener
*/
public void setListener(MessageShowerListener listener);
}
- La vue en XML dans le
répertoire res/layout (avec alternative
ou pas):
<?xml version="1.0"
encoding="utf-8"?>
<fr.yayandongo.articles.SimpleMessageShower xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/showerror_background"
>
<TextView
android:id="@+id/messageTv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_top_msg_text"
android:gravity="center"
android:singleLine="false"
android:text="@string/message_on_graphic_editor"
android:textColor="@color/red"
android:textSize="@dimen/text_nomal"
/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/messageTv"
android:layout_marginTop="30dp"
android:gravity="center_horizontal"
>
<Button
android:id="@+id/confirmBtn"
android:layout_width="@dimen/msg_btn_width"
android:layout_height="@dimen/msg_btn_height"
android:text="@string/msg_confirm_btn"
android:textSize="@dimen/text_nomal"
/>
<Button
android:id="@+id/cancelBtn"
android:layout_width="@dimen/msg_btn_width"
android:layout_height="@dimen/msg_btn_height"
android:layout_marginLeft="@dimen/margin_between_msg_btns"
android:layout_toRightOf="@+id/confirmBtn"
android:text="@string/msg_cancel_btn"
android:textSize="@dimen/text_nomal"
/>
</RelativeLayout>
</fr.yayandongo.articles.SimpleMessageShower>
- La classe qui implémente
la vue :
/**
* A Simple message shower
* @author ndongo
*
*/
public class SimpleMessageShower extends RelativeLayout
implements MessageShower {
private
TextView msgTv = null;
private
Button confrimBtn = null;
private
Button cancelBtn = null;
private
MessageShower.MessageShowerListener listener = null;
/**
*
@param context
*/
public
SimpleMessageShower(Context context) {
super(context);
}
/**
*
@param context
*
@param attrs
*/
public
SimpleMessageShower(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* @param context
* @param attrs
* @param defStyle
*/
public
SimpleMessageShower(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected
void onFinishInflate() {
if
(isInEditMode()) {
return;
}
//
initialize the view
msgTv = (TextView)
findViewById(R.id.messageTv);
confrimBtn = (Button)
findViewById(R.id.confirmBtn);
confrimBtn.setOnClickListener(new OnClickListener() {
@Override
public
void onClick(View v) {
notifyListener(true);
}
});
cancelBtn = (Button)
findViewById(R.id.cancelBtn);
cancelBtn.setOnClickListener(new OnClickListener() {
@Override
public
void onClick(View v) {
notifyListener(false);
}
});
}
private void notifyListener(boolean isConfirmed){
if
(listener == null) {
return;
}
if
(isConfirmed) {
listener.onConfirm();
} else
{
listener.onCancel();
}
}
/**
*
@see MessageShower#setType(MessageType type)
*/
@Override
public void setType(MessageType type) {
switch (type) {
case
DataBaseConnectionFailed:
msgTv.setText(R.string.message_database_failed);
break;
case
NetworkConnectionFailed:
msgTv.setText(R.string.message_network_failed);
break;
default:
msgTv.setText(R.string.message_default);
break;
}
}
/**
*
@see MessageShower#setListener(MessageShowerListener listener)
*/
@Override
public void setListener(MessageShowerListener listener) {
this.listener
= listener;
}
}
- L’instanciation et l’utilisation
de la vue dans une autre classe :
protected Dialog onCreateDialog(int
id) {
if (id == ERROR_DIALOG) {
final
Dialog dialog = new Dialog(this, R.style.errorShowerDialog);
final View msgShower =
LayoutInflater.from(this).inflate(R.layout.message_shower,
null);
((MessageShower)
msgShower).setListener(new MessageShower.MessageShowerListener(){
@Override
public void
onConfirm() {
dialog.dismiss();
// process some thing...
}
@Override
public void
onCancel() {
dialog.dismiss();
// process some thing...
}
});
//...
depending on the process, we set the message type
//
for the example lets use Network failed
((MessageShower)
msgShower).setType(MessageType.NetworkConnectionFailed);
dialog.setContentView(msgShower);
return
dialog;
}
return
super.onCreateDialog(id);
}
En résumé la plateforme Android offre un moyen clair et efficace de
séparer la description des vues et de leur
implémentation avec l’utilisation de l’XML. Il est ainsi dommage de ne pas en
profiter pour réaliser des applications robustes et évolutives.
Dans le même cadre, le concept que nous avons traité ici
repose sur cette base et a pour but principal
de faciliter le découplage et la
maintenabilité des applications.