Pages

lundi 7 octobre 2013

Communication entre deux fragments Android

J’ai déjà introduit la notion de fragment dans un article précédent. Dans cet article je vais plutôt m’attarder sur la méthode préconisée pour faire communiquer des fragments entre eux.



Nous prendrons comme exemple une activité qui possède deux fragments : une liste et une zone détail.




Le but est de pouvoir ouvrir un écran de détail lorsque l’on clique sur un élément de la liste. En mode portrait nous avons un enchaînement de deux écrans alors qu’en mode paysage nous avons un seul écran présentant la liste et le détail.

Les layouts 
Pour proposer une disposition différente en fonction de l’orientation de l’écran, nous nous appuyons sur le mécanisme proposé par le système Android pour personnaliser les fichiers de ressources en fonction de l’orientation ou de la densité de l’écran, de la langue…

Le premier layout nommé fragment_joueurs.xml, définit l’écran qui s’affichera en mode paysage. Il est placé dans le répertoire /res/layout-land et il définit un fragment ListeJoueurFragment et un FrameLayout dans lequel les détails seront placés.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="horizontal"
              android:layout_width="match_parent" android:layout_height="match_parent">

    <fragment class="com.ehret.score.fragment.joueur.ListeJoueurFragment"
              android:id="@+id/joueurListe"
              android:layout_weight="1"
              android:layout_width="0px"
              android:layout_height="match_parent" />

    <FrameLayout android:id="@+id/joueurDetail"
                 android:layout_weight="1"
                 android:layout_width="0px"
                 android:layout_height="match_parent"
                 android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

Un layout portant le même nom est créé dans le répertoire res/layout pour définir l’écran qui sera affiché en mode paysage (notre liste)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent" android:layout_height="match_parent">
    <fragment class="com.ehret.score.fragment.joueur.ListeJoueurFragment"
          android:id="@+id/titles"
          android:layout_width="match_parent" android:layout_height="match_parent" />
</FrameLayout>

Activité principale
Une activité est définie pour charger le layout fragment_joueurs.xml défini dans la première partie. Comme cette activité utilise des fragments, elle hérite de la classe FragmentActivity

public class JoueursActivity extends FragmentActivity {

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


Fragment affichant la liste
Ce nouveau fragment a plusieurs rôles

  • il scrupte les éléments chargés par l’activité pour savoir si la liste et le détail sont présents ensemble ou non
  • il sauvegarde le dernier élément sélectionné dans la liste pour que ce dernier ne soit pas perdu si l’activité est reconstruite (interruption par une autre application ou une rotation de l’écran…)
  • en fonction de l’orientation de l’écran il affiche le détail correspondant à l’élément sélectionné sur le même écran (mode paysage) ou ouvre une nouvelle activité (mode portrait)
public class ListeJoueurFragment extends ListFragment {
    /**
    * un premier état pour indiquer si on est en mode ListeDetail ou Liste simple
    */
    protected boolean mListeDetail = false;
    /**
    * Un second pour garder une référence sur l'élément sélectionné
    */
    protected int mJoueurSelectionneIndex = 0;
    /**
    * Les elements affciches sont figés dans notre exemple
    */

    public static String[] TEST = new String[]{"test", "test1"};
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        //Si on a un état sauvegardé on met a jour l'élément par défaut
        if(savedInstanceState != null) {
            mJoueurSelectionneIndex = savedInstanceState.getInt("joueurSelectionneIndex", 0);
        }

        //En fonction du layout affiché on connait le mode d'ouverture
        View joueurDetailFragment = getActivity().findViewById(R.id.joueurDetail);
        mListeDetail = joueurDetailFragment != null && joueurDetailFragment.getVisibility() == View.VISIBLE;

        //On specifie qu'el adapter sera rattaché à notre liste pour afficher les éléments
        setListAdapter(new ArrayAdapter<String>(getActivity(),
                android.R.layout.simple_list_item_activated_1, TEST));

        //Si on est en mode Liste/Detail on affiche le détail correspondant à l'
        if (mListeDetail) {
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            selectionJoueur(mJoueurSelectionneIndex);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        //a la suspension de l'écran on sauvegarde l'index courant
        outState.putInt("joueurSelectionneIndex", mJoueurSelectionneIndex);
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        //un clic permet d'affiche le détail
        selectionJoueur(position);
    }

    /**
    * Fonction Helper qui va se charger d’afficher les données correspodant à l’item  
    * sélectionné dans la liste
    */
    void selectionJoueur(int index) {
        mJoueurSelectionneIndex = index;

        if (mListeDetail) {
            //Mise a jour de la liste
            getListView().setItemChecked(index, true);

            DetailJoueurFragment detailJoueur = (DetailJoueurFragment) getFragmentManager().findFragmentById(R.id.joueurDetail);
            
            if (detailJoueur == null || detailJoueur.getIdJoueurEnCours() != index) {
                detailJoueur = DetailJoueurFragment.newInstance(index);
                //On remplace le fragment existant avec le nouveau
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                ft.replace(R.id.joueurDetail, detailJoueur);
                ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }
        }
        else {
            //En mode liste simple le detail s'affiche dans une nouvelle activité
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailJoueurActivity.class);
            //on passe à l'actvité l'id du joeur sélectionné
            intent.putExtra("idJoueur", index);
            startActivity(intent);
        }
    }

}

Activité affichant le détail en mode portrait
En mode portrait, l’affichage du détail est géré par une activité spécifique qui chargera le fragment correspondant.

public class DetailJoueurActivity extends FragmentActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState == null) {
            DetailJoueurFragment detailJoueur = DetailJoueurFragment.newInstance(getIntent().getIntExtra("idJoueur",0));
            getSupportFragmentManager().beginTransaction().add(android.R.id.content, detailJoueur).commit();
        }
    }
}

Fragment affichant le détail
Comme la zone de détail est toujours dépendante de l’appelant, le fragment lié sera créé par la méthode statique newInstance(int index). Cette manière d’instancier le fragment simplifie le passage des paramètres d’initialisation (ici l’index sélectionné dans la liste parente)

public class DetailJoueurFragment extends Fragment {

    /**
    * Permet de créer une nouvelle instance de notre fragment
    */
    public static DetailJoueurFragment newInstance(int index) {
        DetailJoueurFragment f = new DetailJoueurFragment();

        //Recupération de l’index à afficher dans les arguments d’appel
        Bundle args = new Bundle();
        args.putInt("idJoueur", index);
        f.setArguments(args);

        return f;
    }

    public int getIdJoueurEnCours() {
        return getArguments().getInt("idJoueur", 0);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        if (container == null) {
            // Si le ViewGroup n'est pas renseigné on ne cherche pas à en créer un
            // car sur cet écran l'appelant doit passer par newInstance...
            return null;
        }

        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        scroller.addView(text);
        text.setText(ListeJoueurFragment.TEST[getIdJoueurEnCours()]);
        return scroller;
    }
}

Cet exemple montre un exemple simple d’un fragment liste /détail.

1 commentaire:

Remarque : Seul un membre de ce blog est autorisé à enregistrer un commentaire.