Corso Programmazione Android Lezione 10: DialogFragment e CoordinatorLayout
Le altre lezioni del Corso di Programmazione Android sono reperibili a questo indirizzo
In questa lezione vedremo la creazione di un Dialog Fragment e alcuni piccolo effetti offerti dal CoordinatorLayout.
Il codice sorgente mostrato in questa lezione è reperibile qui.
Pratica
DialogFragment
Per la modifica delle nostre registrazioni utilizzeremo il componente Dialog e nello specifico un DialogFragment per avere un’app più moderna.
Modifichiamo il nostro layout di visualizzazione
Ho previsto la modifica tramite click sull’elemento, quindi, come sappiamo, dobbiamo associare un id al componente per richiamarlo da codice Java. Il file interessato è content_visualizza.xml, ho modificato leggermente il layout non mi piacevano il titolo e l’autore affiancati. Il file è piuttosto lungo quindi ve lo risparmio vediamone solo un pezzo:
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/paper_tile" android:paddingLeft="10dp" android:paddingRight="10dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:id="@+id/ll_tit" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" android:gravity="center" android:orientation="vertical" android:background="@drawable/border_layout"> <TextView android:id="@+id/titolo_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/titolo"/> <TextView android:id="@+id/titolo_tv" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout>
Avrete sicuramente notato la presenza di un nuovo elemento NestedScrollView lo vedremo più avanti quando analizzeremo CoordinatorLayout, Ogni LinearLayout rappresentante un quadratino adesso ha il suo id che potremo utilizzare all’interno del codice Java. Per rinfrescarci la memoria vi mostro l’aspetto del layout (visto che l’ho modificato):
Vediamo adesso l’intero codice Java della classe VislibriTab1 (leggermente ingigantito) opportunamente commentato:
package com.begeekmyfriend.bereader; import android.database.Cursor; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; /** * Created by fabrizio on 17/04/16. */ public class VisLibriTab1 extends Fragment { private static final String ARG_SECTION_NUMBER = "section_number"; static DatabaseBeReader db; Cursor c, c_new; static String id_db; static TextView titolo_tv, autore_tv, formato_tv, genere_tv, posizione_tv, prestato_tv, prestato_chi_tv, prestato_quando_tv; FloatingActionButton fab_del; LinearLayout ll_tit, ll_aut, ll_for, ll_gen, ll_pos, ll_pres, ll_pres_c, ll_pres_q; public VisLibriTab1() { //Inutile costruttore di default } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); View rootView = inflater.inflate(R.layout.fragment_vis_tab1, container, false); Bundle args = getArguments(); id_db = args.getString("ID"); //Recuperiamo l'id passatoci dall'Activity //Inizializzo tutti i TetxVew adibiti al contenimento dei dati titolo_tv = (TextView) rootView.findViewById(R.id.titolo_tv); autore_tv = (TextView) rootView.findViewById(R.id.autore_tv); formato_tv = (TextView) rootView.findViewById(R.id.formato_tv); genere_tv = (TextView) rootView.findViewById(R.id.genere_tv); posizione_tv = (TextView) rootView.findViewById(R.id.posizione_tv); prestato_tv = (TextView) rootView.findViewById(R.id.prestato_tv); prestato_chi_tv = (TextView) rootView.findViewById(R.id.prestato_chi_tv); prestato_quando_tv = (TextView) rootView.findViewById(R.id.prestato_quando_tv); db = new DatabaseBeReader(getActivity()); //Istanzio l'oggetto db.open(); //Apro la connesione al db c = db.fetchById(id_db); //Lancio la query By id if ( c.getCount() > 0) //Dopo aver aperto il db inizializza i componenti { while (c.moveToNext()) { //anche se ho sicuramente un solo recordo mi serve un ciclo //scrivo il valore del campo del recordo all'interno della TextView titolo_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.TITOLO))); autore_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.AUTORE))); formato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.FORMATO))); genere_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.GENERE))); posizione_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.POSIZIONE))); prestato_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO))); prestato_chi_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI))); prestato_quando_tv.setText(c.getString(c.getColumnIndex(DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO))); } } // Inizializzo i componenti per la modifica e richiamo metodo creato appositamente ll_tit = (LinearLayout) rootView.findViewById(R.id.ll_tit); ll_tit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.TITOLO, titolo_tv.getText()); } }); ll_aut = (LinearLayout) rootView.findViewById(R.id.ll_aut); ll_aut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.AUTORE, autore_tv.getText()); } }); ll_for = (LinearLayout) rootView.findViewById(R.id.ll_for); ll_for.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(1,DatabaseBeReader.BeReaderMetaData.FORMATO, formato_tv.getText()); } }); ll_gen = (LinearLayout) rootView.findViewById(R.id.ll_gen); ll_gen.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.GENERE, genere_tv.getText()); } }); ll_pos = (LinearLayout) rootView.findViewById(R.id.ll_pos); ll_pos.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.POSIZIONE, posizione_tv.getText()); } }); ll_pres = (LinearLayout) rootView.findViewById(R.id.ll_pres); ll_pres.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(1,DatabaseBeReader.BeReaderMetaData.PRESTATO, prestato_tv.getText()); } }); ll_pres_c = (LinearLayout) rootView.findViewById(R.id.ll_pres_c); ll_pres_c.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI, prestato_chi_tv.getText()); } }); ll_pres_q = (LinearLayout) rootView.findViewById(R.id.ll_pres_q); ll_pres_q.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { modificaRegistrazioni(0,DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO, prestato_quando_tv.getText()); } }); db.close(); //chiudo la connessione return rootView; } private void modificaRegistrazioni(int tipo, final String campo, CharSequence oldt1) { Bundle args = new Bundle(); FragmentManager fm = getActivity().getSupportFragmentManager(); //Istanzio il fragment manager args.putInt("TIPOL", tipo); //Ci serve per sapere se ci serve o l'EditText o Lo Spinner args.putString("CAMPO", campo); //Campo da modificare args.putCharSequence("STRINGA", oldt1); DialogoModifica dialogo = new DialogoModifica(); //Istanzio l'oggetto della classe del Dialog dialogo.setArguments(args); //Passo gli argomenti dialogo.setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragment); //Setto uno stile personalizzato dialogo.show(fm, null); //Apro il Dialog } public static class DialogoModifica extends DialogFragment { String campo; CharSequence oldt1; TextView old_val; ImageButton bottone_mod; int tipo_layout; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Recupero gli argomenti Bundle args = getArguments(); oldt1 = args.getCharSequence("STRINGA"); campo = args.getString("CAMPO"); tipo_layout = args.getInt("TIPOL"); View rootView = null; //In base al tipo di layout decido se mi serve il layout con dell'EditText o lo spinner switch (tipo_layout) { case 0: //EditText rootView = inflater.inflate(R.layout.dialog_modifica, container, false); TextView label = (TextView) rootView.findViewById(R.id.label_modifica); label.setText("Modifica ".concat(campo.toLowerCase())); old_val = (TextView) rootView.findViewById(R.id.et_oval); old_val.setText(oldt1); final EditText new_val = (EditText) rootView.findViewById(R.id.ed_nval); new_val.setHint(oldt1); //associo un Hint (suggerimento) bottone_mod = (ImageButton) rootView.findViewById(R.id.bottone_modifica); bottone_mod.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { db.open(); db.editById(id_db, campo, new_val.getText().toString()); //Modifico la registrazione db.close(); //Mi preparo ad aggiornare il layout dopo la modifica switch (campo) { case DatabaseBeReader.BeReaderMetaData.TITOLO: titolo_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.AUTORE: autore_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.GENERE: genere_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.POSIZIONE: posizione_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI: prestato_chi_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO: prestato_quando_tv.setText(new_val.getText().toString()); break; } getDialog().dismiss(); //Chiudo il Dialog } }); //getDialog().setTitle(getString(R.string.modifica)); return rootView; case 1: //Spinner rootView = inflater.inflate(R.layout.dialog_modifica2, container, false); label = (TextView) rootView.findViewById(R.id.label_modifica); label.setText("Modifica ".concat(campo.toLowerCase())); old_val = (TextView) rootView.findViewById(R.id.et_oval); old_val.setText(oldt1); final Spinner sp_nval = (Spinner) rootView.findViewById(R.id.sp_nval); ArrayAdapter aa = null; //In base al campo setto l'ArrayAdapter e lo spinner con i Giusti valori switch (campo) { case DatabaseBeReader.BeReaderMetaData.FORMATO: //Anche il formato è diverso if (getActivity().getClass() == VisualizzaDischi.class) { aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_formato_dischi, R.layout.spinner); } else { aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_formato_libri, R.layout.spinner); } break; case DatabaseBeReader.BeReaderMetaData.PRESTATO: aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_si_no, R.layout.spinner); break; } sp_nval.setAdapter(aa); bottone_mod = (ImageButton) rootView.findViewById(R.id.bottone_modifica); bottone_mod.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { db.open(); db.editById(id_db, campo, sp_nval.getSelectedItem().toString()); //Modifico la registrazione db.close(); //Aggiornamenti del layout post modifica switch (campo) { case DatabaseBeReader.BeReaderMetaData.FORMATO: formato_tv.setText(sp_nval.getSelectedItem().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO: prestato_tv.setText(sp_nval.getSelectedItem().toString()); break; default: break; } getDialog().dismiss(); //Chiudo il Dialog } }); default : //Inutile ma il compilatore non lo sa, se non lo metti da errore di compilazione return rootView; } } } }
Ho voluto creare una sola classe per la visualizzazione di tutte le tipologie quindi ho dovuto fare dei giochetti per rendela riutilizzabile al massimo. Il codice mi sembra ben commentato quindi vediamo solo la classe interna che estende DialogFragment e il metodo che apre il Dialog.
public static class DialogoModifica extends DialogFragment { String campo; CharSequence oldt1; TextView old_val; ImageButton bottone_mod; int tipo_layout; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Recupero gli argomenti Bundle args = getArguments(); oldt1 = args.getCharSequence("STRINGA"); campo = args.getString("CAMPO"); tipo_layout = args.getInt("TIPOL"); View rootView = null; //In base al tipo di layout decido se mi serve il layout con dell'EditText o lo spinner switch (tipo_layout) { case 0: //EditText rootView = inflater.inflate(R.layout.dialog_modifica, container, false); TextView label = (TextView) rootView.findViewById(R.id.label_modifica); label.setText("Modifica ".concat(campo.toLowerCase())); old_val = (TextView) rootView.findViewById(R.id.et_oval); old_val.setText(oldt1); final EditText new_val = (EditText) rootView.findViewById(R.id.ed_nval); new_val.setHint(oldt1); //associo un Hint (suggerimento) bottone_mod = (ImageButton) rootView.findViewById(R.id.bottone_modifica); bottone_mod.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { db.open(); db.editById(id_db, campo, new_val.getText().toString()); //Modifico la registrazione db.close(); //Mi preparo ad aggiornare il layput dopo la modifica switch (campo) { case DatabaseBeReader.BeReaderMetaData.TITOLO: titolo_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.AUTORE: autore_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.GENERE: genere_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.POSIZIONE: posizione_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO_CHI: prestato_chi_tv.setText(new_val.getText().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO_QUANDO: prestato_quando_tv.setText(new_val.getText().toString()); break; } getDialog().dismiss(); //Chiudo il Dialog } }); //getDialog().setTitle(getString(R.string.modifica)); return rootView; case 1: //Spinner rootView = inflater.inflate(R.layout.dialog_modifica2, container, false); label = (TextView) rootView.findViewById(R.id.label_modifica); label.setText("Modifica ".concat(campo.toLowerCase())); old_val = (TextView) rootView.findViewById(R.id.et_oval); old_val.setText(oldt1); final Spinner sp_nval = (Spinner) rootView.findViewById(R.id.sp_nval); ArrayAdapter aa = null; //In base al campo setto l'ArrayAdapter e lo spinner con i Giusti valori switch (campo) { case DatabaseBeReader.BeReaderMetaData.FORMATO: //Anche il formato è diverso if (getActivity().getClass() == VisualizzaDischi.class) { aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_formato_dischi, R.layout.spinner); } else { aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_formato_libri, R.layout.spinner); } break; case DatabaseBeReader.BeReaderMetaData.PRESTATO: aa = ArrayAdapter.createFromResource(getActivity(), R.array.ar_si_no, R.layout.spinner); break; } sp_nval.setAdapter(aa); bottone_mod = (ImageButton) rootView.findViewById(R.id.bottone_modifica); bottone_mod.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { db.open(); db.editById(id_db, campo, sp_nval.getSelectedItem().toString()); //Modifico la registrazione db.close(); //Aggiornamenti del layout post modifica switch (campo) { case DatabaseBeReader.BeReaderMetaData.FORMATO: formato_tv.setText(sp_nval.getSelectedItem().toString()); break; case DatabaseBeReader.BeReaderMetaData.PRESTATO: prestato_tv.setText(sp_nval.getSelectedItem().toString()); break; default: break; } getDialog().dismiss(); //Chiudo il Dialog } }); default : //Inutile ma il compilatore non lo sa, se non lo metti da errore di compilazione return rootView; } }
Anche questa classe è un pò gigante e ha dei trucchetti per rendere il tutto riutilizzabile, non ve la descrivo in quanto, come potete vedere, non è altro che un semplice Fragment. Vengono richiamati solo dei metodi che non conosciamo ma dal significato abbastanza intuibile. Il metodo getDialog().dismiss() infatti si usa per chiudere il Dialog quando non ne abbiamo più bisogno.
Dopo la classe il metodo che apre il Dialog:
private void modificaRegistrazioni(int tipo, final String campo, CharSequence oldt1) { Bundle args = new Bundle(); FragmentManager fm = getActivity().getSupportFragmentManager(); //Istanzio il fragment manager args.putInt("TIPOL", tipo); //Ci serve per sapere se ci serve o l'EditText o Lo Spinner args.putString("CAMPO", campo); //Campo da modificare args.putCharSequence("STRINGA", oldt1); DialogoModifica dialogo = new DialogoModifica(); //Istanzio l'oggetto della classe del Dialog dialogo.setArguments(args); //Passo gli argomenti dialogo.setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogFragment); //Setto uno stile personalizzato dialogo.show(fm, null); //Apro il Dialog }
Come dicevamo il DialogFragment è trattato come un fragment quindi passiamo dati allo stesso modo. Per settare lo stile usiamo il metodo setStyle() e per aprirlo chiamiamo show().
Vediamo il risultato di quanto abbiamo creato:
Vi risparmio il layout essendo composto solo da componenti noti.
CoordinatorLayout
Il CoordinatorLayout è un layout particolare che ha come scopo la coordinazione dei componenti al suo interno.
Quindi avendo un layout con all’interno, ad esempio, un FloatingActionButton questo sale se un particolare evento richiama una SnackBar. Nel nostro caso abbiamo deciso di utilizzare l’animazione più eccitante (mi sono emozionato quando sono riuscito a farla per la prima volta, sembravo un bambino al lunapark), la Toolbar a scomparsa.
Per farlo abbiamo dovuto inserire nel Layout dell’Activity il già citato NestedScrollView (ne nostro caso nel content_visualizza.xml). Possiamo notare che il FloatingActionButton resta fermo quando scorriamo
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/paper_tile" android:paddingLeft="10dp" android:paddingRight="10dp" app:layout_behavior="@string/appbar_scrolling_view_behavior">
Il responsabile dell’effetto è l’attributo layout_behavior. Nelle prossime lezioni vedremo i Behavior più usati.
Se siete curiosi consiglio lo studio dei sorgenti, ho aggiunto funzionalità che descriverò a breve.
Originariamente pubblicato su Be Geek My Friend