corso programmazione android lezione 5

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:

corso programmazione android lezione 5

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

corso programmazione android lezione 5

Per oggi abbiamo finito.

Originariamente pubblicato su Be Geek My Friend

Autore dell'articolo: fabrizio

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *