• Programmazione Android
  • CORSI ONLINE
  • Web Agency

Logo

Corsi di programmazione web e mobile online
Navigation
  • Home
  • CORSI ONLINE
  • Tutorial Pratici
  • GUIDE COMPLETE
    • Corso completo di C
    • Corso videogame con Cocos2d
    • Programmazione Cocoa Touch
  • Sezioni
    • Libri e manuali
    • Tips & Tricks
    • Risorse utili
    • Strumenti di Sviluppo
    • Materiale OpenSource
    • Framework
    • Guide Teoriche
    • Guide varie
    • Grafica e Design
    • iPad
    • News
    • Video Tutorial
    • Windows Phone
  • Pubblicità
  • About
    • Chi siamo
    • Pubblicazioni
    • Collabora
    • Sostieni devAPP

Android: capire il funzionamento dei Fragment

By Giuseppe Maggi | on 5 Ottobre 2017 | 0 Comment
Android
Android Tutorial

Quando si cerca di approfondire i propri studi sulle interfacce Android, il primo scoglio con cui ci si scontra, spesso, sono i Fragment. Il loro utilizzo non è così proibitivo ma ciò che li rende ostici è la necessità di comprenderne a fondo il ciclo di vita e le basi dell’interazione con l’ecosistema Android. Con questo articolo ed i successivi, ci interesseremo proprio a questi aspetti.

Cosa sono i Fragment?

Un Fragment può essere considerato una porzione dell’interfaccia utente, completa non solo di layout ma anche di tutto il codice necessario a rappresentarne la logica di gestione. La loro nascita – risalente ad Android 3.0 – ha offerto la possibilità di creare interfacce utente composte da porzioni riutilizzabili e alternabili senza la necessità di distruggere l’Activity. Un Fragment ha bisogno di un’Activity che gli faccia da contenitore pertanto l’uso di questi componenti facilita, da un lato, la gestione del ciclo di vita di un’Activity (visto che questa non cambia ma si alternano solo i Fragment) ma dall’altro aggiunge ulteriore complessità considerando che i Fragment  hanno un proprio ciclo di vita piuttosto articolato.
Per prendere confidenza con la tematica e studiarne gli aspetti più delicati affronteremo il seguente esempio.

L’esempio

Il progetto (il cui codice è disponibile qui) è costituito da una sola Activity con due Fragment che si alternano costituendone l’interfaccia utente.
Nel primo troviamo tre pulsanti, ognuno dei quali etichettato con il nome di una città italiana.

Al click di uno di questi, il Fragment viene sostituito con un secondo che mostra una foto ritraente un luogo simbolo della città in questione accompagnato da un piccolo testo descrittivo (per il quale abbiamo attinto da Wikipedia).

Android gestire l'interfaccia utente dell'Activity con i Fragment

Dai nostri Framgment vogliamo un comportamento completo ed efficiente che non trascuri alcun aspetto utile all’interazione utente.
I nostri obiettivi sono:

  • quando appare il secondo Fragment (quello con la foto della città, per intenderci) vogliamo che premendo il tasto Back si torni al primo Fragment (non è il comportamento di default) e che appaia la freccia a sinistra per la navigazione Up sull’Action Bar: quest’ultima deve scomparire al ritorno al primo Fragment;
  • se ruotiamo il dispositivo mentre visualizziamo il secondo Fragment, vogliamo che l’Activity continui a visualizzarlo a rotazione conclusa in modo che il cambio di configurazione abbia il minimo impatto possibile sulla user experience.

Activity e primo Fragment

Il layout dell’Activity è composto da un FrameLayout:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

Questo è un tipo di layout che serve a mostrare un singolo elemento nell’interfaccia. In questo FrameLayout, verranno collocati i nostri Fragment e la loro alternanza sarà gestita dal FragmentManager, messo a disposizione dall’Activity stessa.

I dati utilizzati saranno forniti dalla classe ElencoCitta che simula una sorgente dati che nella realtà sarebbe rappresentata da un database, un ContentProvider o un’altra componente che dialoga con la Rete:

public class ElencoCitta {

    private HashMap<String, Citta> citta=new HashMap<String, Citta>();

    public static class  Citta
    {
        public String nome;
        public String presentazione;
        public int immagine;

        public Citta(String nome, String presentazione, int immagine) {
            this.nome = nome;
            this.presentazione = presentazione;
            this.immagine=immagine;
        }
    }

    public ElencoCitta()
    {
        citta.put("milano", new Citta(
                                "Milano",
                                "....",
                                R.drawable.milano
                        ));

        citta.put("roma",  new Citta(
                "Roma",
                "....",
                R.drawable.roma
        ));

        citta.put("palermo", new Citta(
                "Palermo",
                 "....",
                R.drawable.palermo
        ));
    }

    public Citta trovaCitta(String nomeCitta)
    {
        return citta.get(nomeCitta);
    }



}

Nell’Activity, gestiremo il passaggio tra un Fragment e l’altro mediante una FragmentTransaction. Questa è costituita dalle seguenti fasi:

  • viene creata una nuova FragmentTransaction dal FragmentManager;
  • viene eseguita l’operazione che si vuole svolgere che consiste tipicamente nell’aggiunta di un Fragment ad un FrameLayout (metodo add), nella sua sostituzione (metodo replace) o nella sua rimozione (metodo remove);
  • chiediamo di rendere operativa la modifica con l’invocazione del metodo commit.

Ciò lo faremo nel metodo cambiaFragment che risponde al click su uno dei tre pulsanti presenti nel primo Fragment. Questo è il codice completo dell’Activity.

public class MainActivity extends AppCompatActivity {

    private ElencoCitta elenco;

    @Override
    public void onBackPressed() {
        getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        super.onBackPressed();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        elenco=new ElencoCitta();

        FragmentManager fm = getSupportFragmentManager();
        Fragment trovato=fm.findFragmentByTag(CittaFragment.CITTAFRAGMENT_TAG);

        FragmentTransaction ft = fm.beginTransaction();
        if (trovato!=null) {
            ft.replace(R.id.fragment, trovato);
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
        else
            ft.replace(R.id.fragment, new MainFragment());
        ft.commit();
    }


    public void cambiaFragment(View v)
    {
        CittaFragment fragment=null;
        switch(v.getId())
        {
            case R.id.roma:
                fragment=CittaFragment.newInstance(elenco.trovaCitta("roma"));
                break;
            case R.id.milano:
                fragment=CittaFragment.newInstance(elenco.trovaCitta("milano"));
                break;
            case R.id.palermo:
                fragment=CittaFragment.newInstance(elenco.trovaCitta("palermo"));
        }
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction ft = fm.beginTransaction();
        ft.addToBackStack(null);
        ft.replace(R.id.fragment, fragment,CittaFragment.CITTAFRAGMENT_TAG);
        ft.commit();

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                getSupportFragmentManager().popBackStack();
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                break;
        }
        return false;
    }

}

Il primo Fragment, classe MainFragment, ha un codice molto semplice:

public class MainFragment extends Fragment {


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
 Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

}

La classe è un’estensione di Fragment e al suo interno implementa il metodo onCreateView, facente parte del ciclo di vita di un Fragment. Come approccio iniziale, preferiamo non mostrare subito tutte le fasi del ciclo di vita ma affrontare i metodi man mano che ne abbiamo bisogno lasciando ad articoli successivi una visione più d’insieme.

Nel metodo onCreateView deve essere caricato il layout del Fragment con l’inizializzazione dell’interfaccia. In questo caso, carichiamo tramite LayoutInflater il layout che include i tre pulsanti.

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

    <LinearLayout
        android:layout_width="300dp"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_centerInParent="true">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Roma"
            android:onClick="cambiaFragment"
            android:id="@+id/roma"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Milano"
            android:id="@+id/milano"
            android:onClick="cambiaFragment"
            android:layout_marginTop="20dp"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Palermo"
            android:id="@+id/palermo"
            android:onClick="cambiaFragment"
            android:layout_marginTop="20dp"/>
    </LinearLayout>

</RelativeLayout>

Per quanto riguarda la gestione del loro evento di pressione avremmo potuto gestirlo internamente al Fragment con gli appositi Listener ma visto che le operazioni richiamate riguardano l’Activity nel suo complesso abbiamo preferito utilizzare l’attributo onClick che punta al metodo cambiaFragment dell’Activity. A seconda dei casi, ci si potrà regolare personalmente se si vuole gestire gli eventi all’interno del Fragment o meno ma questo dipenderà spesso dal tipo di operazioni che si devono affrontare.

L’Activity, aspetti rilevanti

Dopo aver introdotto il codice dell’Activity, notiamo alcuni aspetti particolari. Al momento di eseguire la FragmentTransaction nel metodo cambiaFragment, invochiamo il metodo addToBackStack. Questo serve a fare in modo che il Fragment sostituito venga aggiunto in una sorta di storico chiamato BackStack cosicchè, al momento della pressione del pulsante Back mentre il secondo Fragment è visualizzato, l’Activity non venga chiusa ma si inverta l’ultima transazione eseguita riportando alla luce il primo Fragment. Senza l’uso di addToBackStack, al momento della pressione del tasto Back, l’Activity verrebbe chiusa.

Notiamo ancora l’invocazione del metodo replace:

ft.replace(R.id.fragment, fragment,CittaFragment.CITTAFRAGMENT_TAG);

I tre parametri passati sono l’id del FrameLayout che ospiterà il Fragment, l’oggetto Fragment che subentrerà ed infine – cosa che ci interessa in questo discorso – una costante stringa che svolgerà il ruolo di tag del Fragment. Il tag permetterà in seguito di identificare il Fragment chiedendo al FragmentManager il suo ripristino e ciò tornerà utile nel metodo onCreate al momento della ricostruzione dell’Activity dopo la rotazione del dispositivo:

 FragmentManager fm = getSupportFragmentManager();
        Fragment trovato=fm.findFragmentByTag(CittaFragment.CITTAFRAGMENT_TAG);

        FragmentTransaction ft = fm.beginTransaction();
        if (trovato!=null) {
            ft.replace(R.id.fragment, trovato);
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        }
        else
            ft.replace(R.id.fragment, new MainFragment());
        ft.commit();

Senza il precedente codice, ruotando il dispositivo durante la visualizzazione del secondo Fragment, alla ricostruzione dell’Activity verrebbe mostrato il primo Fragment costringendo l’utente a dover ripristinare il contenuto che stava leggendo un attimo prima.

Android gestire l'interfaccia utente dell'Activity con i Fragment

Altro aspetto che gestiamo è l’apparizione dell’icona a forma di freccia verso sinistra sull’ActionBar che si utilizza per la navigazione Up. Essa apparirà solo mentre il secondo Fragment è sullo schermo e gestiremo la sua apparizione con l’invocazione:

getSupportActionBar().setDisplayHomeAsUpEnabled(true);

mentre l’evento di click su di essa andrà gestito come una normale action:

 public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                getSupportFragmentManager().popBackStack();
                getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                break;
        }
        return false;
    }

Il metodo popBackStack esegue l’inverso di addToBackStack visto prima: estrae dal BackStack l’ultimo Fragment archiviato.

Inoltre aggiungiamo l’overrride del metodo onBackPressed che permette di personalizzare la pressione del pulsante Back: quello che vi inseriremo noi sarà la disabilitazione della frecciolina di navigazione Up:

public void onBackPressed() {
        getSupportActionBar().setDisplayHomeAsUpEnabled(false);
        super.onBackPressed();
    }

Il secondo Fragment

Il secondo Fragment mostra ancora il metodo onCreateView in cui viene preparata l’interfaccia ma troviamo anche un altro metodo del ciclo di vita, onCreate, che -analogamente a quello utilizzabile nelle Activity – gestisce la creazione del Fragment e viene invocato prima di onCreateView.

L’aspetto interessante di questo Fragment consiste nell’utilizzo del metodo newInstance. Vediamo il codice completo:

public class CittaFragment extends Fragment {

    public static final String CITTAFRAGMENT_TAG="CITTAFRAGMENT_TAG";

    private static final String ARG_ID_IMG = "img";
    private static final String ARG_NOME_CITTA = "nome";
    private static final String ARG_DESCR_CITTA = "descr";

    private String nomeCitta;
    private String descrizioneCitta;
    private int idImg;


    public static CittaFragment newInstance(ElencoCitta.Citta citta) {
        CittaFragment fragment = new CittaFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_ID_IMG, citta.immagine);
        args.putString(ARG_NOME_CITTA, citta.nome);
        args.putString(ARG_DESCR_CITTA, citta.presentazione);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            idImg = getArguments().getInt(ARG_ID_IMG);
            nomeCitta = getArguments().getString(ARG_NOME_CITTA);
            descrizioneCitta = getArguments().getString(ARG_DESCR_CITTA);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View v=inflater.inflate(R.layout.fragment_citta, container, false);

        ImageView imageView= (ImageView) v.findViewById(R.id.imageView);
        TextView txt_nome= (TextView) v.findViewById(R.id.citta);
        TextView txt_descr= (TextView) v.findViewById(R.id.descrizione);

        imageView.setImageResource(idImg);
        txt_nome.setText(nomeCitta);
        txt_descr.setText(descrizioneCitta);
        return v;
    }

}

Quando creiamo il Fragment vogliamo passargli dall’Activity i dati da mostrare che consistono nell’id dell’immagine da visualizzare, il nome della città e la breve descrizione. Normalmente, lo faremmo con un costruttore con parametri ma questa pratica è sconsigliata e scoraggiata. Piuttosto si preferisce creare un metodo statico – per convenzione chiamato newInstance – che riceva i parametri necessari e restituisca un Fragment secondo il pattern Factory. Prima di restituire il Fragment il metodo newInstance avrà cura di inserirvi i dati necessari con setArguments. Questi potranno essere recuperati con getArguments nel metodo onCreate.

I parametri passati vengono inseriti in un Bundle. Nel nostro caso dovevamo passare un oggetto Citta che però non può diventare parte di un Bundle a meno che non venga reso Serializable o Parcelable. Quest’ultima opzione soprattutto sarebbe una buona prassi ma per non complicare troppo l’esempio abbiamo preferito estrarre dall’oggetto il numero intero e le due stringhe ed inserirle nel Bundle. In alternativa, avremmo potuto rinunciare all’uso degli arguments di un Fragment creando un metodo – tipo setter – cui passare direttamente l’oggetto di classe Citta.

Conclusioni

Benchè l’esempio mostrato sia piuttosto semplice ha il pregio di illustrare l’uso dei Fragment in una modalità che non si accontenta di presentare solo gli aspetti più essenziali ma di indagare le vere questioni legate alla navigazione attraverso l’applicazione che vanno affrontate necessariamente in casi reali. I Fragment non appaiono spesso una tematica molto intuitiva e proprio per questo abbiamo deciso di dedicarvi alcuni articoli di approfondimento: questo che vi abbiamo offerto è il primo ma non ne mancheranno altri.

Share this story:
  • tweet

Tags: androidcreare app mobiledesign user interfacedesign user interface androidfragmentTutorial Pratici

Recent Posts

  • Parte il percorso programmatori iOS in Swift su devACADEMY.it

    20 Dicembre 2017 - 0 Comment
  • Android, crittografare dati velocemente con Encryption

    24 Settembre 2018 - 0 Comment
  • Sql2o, accesso immediato ai database tramite Java

    3 Settembre 2018 - 0 Comment
  • Okio, libreria per ottimizzare l’input/output in Java

    27 Agosto 2018 - 0 Comment

Related Posts

  • IntelliJ IDEA: IDE per programmare in Java e Kotlin

    25 Luglio 2018 - 0 Comment
  • Android: usare Connect Pattern

    10 Luglio 2018 - 0 Comment
  • Android: il metodo findViewById() non richiede più il cast

    21 Giugno 2018 - 0 Comment

Author Description

No Responses to “Android: capire il funzionamento dei Fragment”

Leave a Reply

Your email address will not be published. Required fields are marked *


*
*

Corso online di programmazione android e java

SEZIONI

  • Android
  • Comunicazioni
  • Contest
  • Corsi ed Eventi
  • Corso completo di C
  • Corso programmazione videogiochi
  • Framework
  • Grafica e Design
  • Guida rapida alla programmazione Cocoa Touch
  • Guide Teoriche
  • Guide varie
  • iPad
  • Le nostre applicazioni
  • Libri e manuali
  • Materiale OpenSource
  • News
  • Pillole di C++
  • Progetti completi
  • Risorse utili
  • Strumenti di Sviluppo
  • Swift
  • Tips & Tricks
  • Tutorial Pratici
  • Video Tutorial
  • Windows Phone

Siti Amici

  • Adrirobot
  • Allmobileworld
  • Apple Notizie
  • Apple Tribù
  • Avvocato360
  • Blog informatico 360°
  • bubi devs
  • fotogriPhone
  • GiovaTech
  • iApp-Mac
  • iOS Developer Program
  • iPodMania
  • MelaRumors
  • Meritocracy
  • SoloTablet
  • TecnoUser
  • Privacy & Cookie Policy
©2009-2018 devAPP - All Rights Reserved | Contattaci
devAPP.it è un progetto di DEVAPP S.R.L. - Web & Mobile Agency di Torino
Str. Volpiano, 54 - 10040 Leini (TO) - C.F. e P.IVA 11263180017 - REA TO1199665 - Cap. Soc. € 10.000,00 i.v.

devACADEMY.it

Vuoi imparare a programmare?

Iscriviti e accedi a TUTTI i corsi con un’unica iscrizione.
Oltre 70 corsi e migliaia di videolezioni online e in italiano a tua disposizione.

ISCRIVITI SUBITO