Corso Programmazione Android Lezione 5: Abilitiamo il navigation Drawer e Fragment
Le altre lezioni del Corso di Programmazione Android sono reperibili a questo indirizzo
In questa lezione continueremo quanto iniziato nella precedente quindi renderemo operativo e funzionante il nostro Navigation Drawer per poi poterci concentrare dalla prossima lezione sulle funzionalità. I sorgenti mostrati in questa lezione sono reperibili al seguente indirizzo.
Per far funzionare il nostro Navigation Drawer avremo bisogno di introdurre i Fragment quindi partiamo con la teoria.
Teoria
I fragment
Come abbiamo descritto in precedenza un Navigation Drawer è un componente a scomparsa dell’interfaccia Android che in base alla scelta selezionata muta una porzione dell’interfaccia stessa. Quindi nel nostro caso se selezioniamo la voce Libri la parte centrale dell’interfaccia, cioè il FrameLayout che nella precedente lezione abbiamo definito con l’id flContent, verrà associato alla porzione di codice inerente i libri. Per fa ciò serve un Fragment.
Ecco la definizione ufficiale di Fragment:
Un Fragment rappresenta una funzione o una parte di interfaccia utente di una Activity. E’ possibile combinare più Fragment in una singola Activity così da realizzare una interfaccia utente multi-pannello e poter riutilizzare un Fragment in più Activity. Pensate al Fragment come ad una parte modulare di una Activity, ha il proprio ciclo di vita, riceve eventi in input e può essere aggiunto o rimossa mentre l’Activity è visibile.
La classe R
Rileggendo le precedenti lezioni mi sono accorto di aver dimenticato di trattare la classe R.
Quando all’interno del codice richiamiamo una risorsa lo facciamo come se fosse un oggetto (R.layout.activity_main), come è possibile tutto ciò?
Quando creiamo un risorsa tramite file xml e le associamo un id (con il parametro android:id=”@+id/….”) il nostro valente compilatore crea un’istanza all’interno della classe R. Come specificato all’inizio del codice la classe è autogenerata quindi ogni modifica verrà eliminata alla prima compilazione.
Per puro spirito di conoscenza possiamo andarla a vedere all’interno del nostro progetto (nel nostro caso è posizione nella cartella ~/AndroidStudiiProjects/app/build/generated/source/r/debug/com/begeekmyfriend/bereader). Avendo inserito delle librerie di supporto (come vedremo nelle prossime lezioni) il file è enorme, ne ho riportato una piccola porzione, quella relativa alle stringhe:
public static final class string { public static final int dischi=0x7f060018; public static final int fumetti=0x7f060019; public static final int homepage=0x7f06001a; public static final int libri=0x7f06001b; public static final int prestiti=0x7f06001c; }
Pratica
Creazione classi Fragment
Nella lezione precedente abbiamo definito 5 voci di menu:
Per non sporcare troppo il nostro codice ho creato le classi solo per Home e Libri, sono praticamente identiche quindi ne mostro soltanto una:
package com.begeekmyfriend.bereader; import android.app.Fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; /** * Created by fabrizio on 12/03/16. */ public class HomepageFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.homepage_fragment, container, false); return rootView; } }
La classe estende Fragment e non fa altro che associare un layout alla view.
Un fragment non è un’activity e, come possiamo vedere dall’esempio, associa il layout in modo differente. Per un fragment è necessario creare all’interno del metodo onCreateView un’istanza di oggetto di tipo View e restituirla.
Vediamo il layout (file homepage_fragment.xml) che per adesso non fa altro che creare un componente TextView che scrive il nome della sezione.
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="match_parent" android:text="@string/homepage"/> </ScrollView> </android.support.design.widget.CoordinatorLayout>
I layout sono identici, salvo l’attributo android:text che nel primo riporta @string/homepage mentre nel secondo @string/libr“. Il sorgente della classe Libri differisce solo nell’argomento al metodo utile alla creazione dell’oggetto di tipo View R.layout.homepage_fragment nel primo e R.layout.libri_fragment nel secondo.
La classe MainActivity
La classe MainActivity è cambiata radicalmente, riporto l’intero codice che poi commenteremo dividendolo in porzioni:
package com.begeekmyfriend.bereader; import android.app.Fragment; import android.app.FragmentManager; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem; public class MainActivity extends AppCompatActivity { private DrawerLayout mDrawer; //New private NavigationView nvDrawer; //New private Toolbar toolbar; //New Fragment fragment = null; //New Class fragmentClass; //New @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); //modificato istanziata su setSupportActionBar(toolbar); mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); //Inizializzo il drawer layout nvDrawer = (NavigationView) findViewById(R.id.nvView); //Inizzializzo il Navigation View setupDrawerContent(nvDrawer); //Collego il navigation view al drawer final ActionBar ab = getSupportActionBar(); //Inizializzo la ActionBar ab.setHomeAsUpIndicator(R.mipmap.ic_drawer); //Associo l'icona hamburger ab.setDisplayHomeAsUpEnabled(true); //Faccio si che la zona con l'icona sia clickabile fragmentClass = HomepageFragment.class; //All'avvio mi serve l'homepage fragment try { fragment = (Fragment) fragmentClass.newInstance(); //Creo una nuova istanza di Fragment } catch (Exception e) { e.printStackTrace(); } // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); //Creo un'istanza di fragmentmannager fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); //Inserisco il fragment nel nosto flContent /* FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); */ } @Override public boolean onOptionsItemSelected(MenuItem item) { // The action bar home/up action should open or close the drawer. switch (item.getItemId()) { case android.R.id.home: mDrawer.openDrawer(GravityCompat.START); //Quando si clicca sull'icona hamburger si apre il drawer return true; } return super.onOptionsItemSelected(item); //richiamo la superclasse } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); //richiamo semplicemente la siperclasse } private void setupDrawerContent(NavigationView navigationView) { //Creo un listener per il click sulla scelta del drawer navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { selectDrawerItem(menuItem); //richiamo il metodo giù return true; } }); } public void selectDrawerItem(MenuItem menuItem) { // Create a new fragment and specify the planet to show based on // position fragment = null; //setto a null il fragment //Class fragmentClass; switch(menuItem.getItemId()) { case R.id.nav_home: fragmentClass = HomepageFragment.class; //Hai selezionato homepage quindi apro il fragment della home break; case R.id.nav_libri: fragmentClass = LibriFragment.class; //Hai selezionato libri break; /* case R.id.nav_fumetti: fragmentClass = FumettiFragment.class; break; case R.id.nav_dischi: fragmentClass = DischiFragment.class; break; case R.id.nav_prestiti: //Cambiare fragmentClass = HomepageFragment.class; break; */ default: fragmentClass = HomepageFragment.class; //non abbiamo definito tutte le scelte quindi serve il default } try { fragment = (Fragment) fragmentClass.newInstance(); } catch (Exception e) { e.printStackTrace(); } // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); // Highlight the selected item, update the title, and close the drawer menuItem.setChecked(true); setTitle(menuItem.getTitle()); //Cambbia il titolo sulla actionbar mDrawer.closeDrawers(); //chiudo il drawer } }
Vediamola in dettaglio.
Prima di tutto sono lievitati gli import, più componenti utilizziamo più classi dobbiamo importare:
import android.app.Fragment; import android.app.FragmentManager; import android.os.Bundle; import android.support.design.widget.NavigationView; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBar; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.MenuItem;
Ci servono Fragment (non v4 ma fragment semplici), FragmentManager, NavigationView, DrawerLayout, ActionBar. Man mano che scriverete il vostro codice Android Studio vi aiuterà proponendovi gli import necessari (in caso di più classi con lo stesso nome vi proporrà tutte le possibilità, come nel caso dei Fragment).
Metto il punto sugli import poiché leggendo guide trovate in rete mi sono trovato in difficoltà visto che sono quasi tutte sprovviste della sezione contente le classi da importare.
All’esterno di ogni metodo instanziamo degli oggetti:
private DrawerLayout mDrawer; //New private NavigationView nvDrawer; //New private Toolbar toolbar; //New Fragment fragment = null; //New Class fragmentClass; //New
Così facendo potremo richiamare tali oggetti all’interno di ogni metodo della classe.
La nostra onCreate è cresciuta e si occupa di creare tutti gli aspetti e prepararli al primo avvio (scelta predefinita)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); toolbar = (Toolbar) findViewById(R.id.toolbar); //ho solo spostato la definizione dell'istanza setSupportActionBar(toolbar); mDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); //Inizializzo il drawer layout nvDrawer = (NavigationView) findViewById(R.id.nvView); //Inizializzo il Navigation View setupDrawerContent(nvDrawer); //Collego il navigation view al drawer final ActionBar ab = getSupportActionBar(); //Inizializzo la ActionBar ab.setHomeAsUpIndicator(R.mipmap.ic_drawer); //Associo l'icona hamburger ab.setDisplayHomeAsUpEnabled(true); //Faccio si che la zona con l'icona sia cliccabile fragmentClass = HomepageFragment.class; //All'avvio mi serve l'homepage fragment try { fragment = (Fragment) fragmentClass.newInstance(); //Creo una nuova istanza di Fragment } catch (Exception e) { //se non riesci a caricare esci un'eccezione e.printStackTrace(); } // Inserisci il fragment e sostituisci l'esistente FragmentManager fragmentManager = getFragmentManager(); //Creo un'istanza di fragmentmannager fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); //Inserisco il fragment nel nostro flContent /* commentato FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) .setAction("Action", null).show(); } }); */ fine commento }
Ho cercato di commentare in modo approfondito il codice quindi non mi dilungo con le spiegazioni (è un trucco di solito nel libri si tende a scorrere solo il codice, pur essendo la parte più importante del testo).
Ci servono altri metodi
@Override public boolean onOptionsItemSelected(MenuItem item) { // The action bar home/up action should open or close the drawer. switch (item.getItemId()) { //recupero l'id del parametro di tipo MenuItem case android.R.id.home: mDrawer.openDrawer(GravityCompat.START); //Quando si clicca sull'icona hamburger si apre il drawer return true; } return super.onOptionsItemSelected(item); //richiamo la superclasse } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); //richiamo semplicemente la superclasse } private void setupDrawerContent(NavigationView navigationView) { //Creo un listener per la gestione del drawer navigationView.setNavigationItemSelectedListener( new NavigationView.OnNavigationItemSelectedListener() { @Override public boolean onNavigationItemSelected(MenuItem menuItem) { selectDrawerItem(menuItem); //richiamo il metodo selectDrawerItem riportato in basso return true; } }); }
E alla fine ci serve il metodo che una volta scelta la voce di menu cambia il fragment scegliendo quello appropriato:
public void selectDrawerItem(MenuItem menuItem) { // Create a new fragment and specify the planet to show based on // position fragment = null; //setto a null il fragment switch(menuItem.getItemId()) { case R.id.nav_home: fragmentClass = HomepageFragment.class; //Hai selezionato homepage quindi apro il fragment della home break; case R.id.nav_libri: fragmentClass = LibriFragment.class; //Hai selezionato libri break; /* commentato case R.id.nav_fumetti: fragmentClass = FumettiFragment.class; break; case R.id.nav_dischi: fragmentClass = DischiFragment.class; break; case R.id.nav_prestiti: //Cambiare fragmentClass = HomepageFragment.class; break; */ fine commento default: fragmentClass = HomepageFragment.class; //non abbiamo definito tutte le scelte quindi serve il default } try { fragment = (Fragment) fragmentClass.newInstance(); //creo l'istanza del fragment } catch (Exception e) { e.printStackTrace(); } // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); //associo l'istanza al FrameLayout fragmentManager.beginTransaction().replace(R.id.flContent, fragment).commit(); // Highlight the selected item, update the title, and close the drawer menuItem.setChecked(true); setTitle(menuItem.getTitle()); //Cambbia il titolo sulla actionbar mDrawer.closeDrawers(); //chiudo il drawer }
Volendo semplificare il codice alcune voci non sono ancora state implementate ma puntano a default (in Java in uno switch il case default è obbligatorio).
Ecco il risultato finale
Per oggi abbiamo finito.
Originariamente pubblicato su Be Geek My Friend
1 commento