In questa lezione inizieremo a costruire un piccolo news reader, che utilizzeremo come esempio pratico per mostrare i più comuni elementi utilizzati oggigiorno nelle interfacce Android. Affronteremo per prima cosa l'architettura della UI su cui si basa l'applicazione, e ne vedremo una tipologia molto diffusa e particolarmente adatta al nostro scopo: Tabs sfogliabili mediante ViewPager.
Contenuti dell'applicazione
Prima di introdurre la struttura dell'applicazione, faremo una premessa sui contenuti che essa gestirà. Per fare in modo che il lettore possa concentrarsi sugli aspetti visuali ed interattivi della UI, i contenuti non verranno reperiti realmente in rete, ma saranno forniti da una classe di nome DataProvider. Quella che segue è la sua struttura:
public class DataProvider
{
public static final String CATEGORY="CATEGORY";
public static final String COOKING_CATEGORY="CUCINA";
public static final String TECH_CATEGORY="TECH";
public static final String GREEN_CATEGORY="GREEN";
private static final String[] CATEGORY_ORDER=new String[]{COOKING_CATEGORY,GREEN_CATEGORY,TECH_CATEGORY};
public static class Content
{
private String title;
private String description;
// OMISSIS: getter e setter dei metodi privati
}
private static HashMap<String,ArrayList<Content>> contents;
static
{
// OMISSIS: inizializzazione statica di contents
}
public static List<Content> getContentsByCategory(String category)
{
return contents.get(category);
}
public static String getCategoryByIndex(int index)
{
return CATEGORY_ORDER[index];
}
public static int getCatoriesCount()
{
return contents.entrySet().size();
}
}
La classe contiene e distribuisce oggetti di tipo Content
che rappresentano singole notizie. Content
, a sua volta, è annidata in DataProvider
ed al suo interno ha solo due membri privati: title
che rappresenta il titolo della notizia, e description
che contiene un breve testo che la descrive.
La struttura dati è una HashMap
che abbiamo chiamato contents
: essa utilizza il nome di una categoria di notizie (in forma di stringa) come chiave, mentre i valori sono ArrayList
di notizie.
Gli altri metodi servono di conseguenza, per l'estrazione degli oggetti Content
.
Struttura dell'applicazione
In base ai concetti illustrati in precedenza riguardo alla navigazione interna all'applicazione, consideriamo la nostra app suddivisa su tre livelli:
- a livello globale, ci sono le categorie delle notizie che verranno navigate lateralmente. Ogni categoria corrisponderà ad una Tab e, contemporaneamente, ad una pagina del ViewPager;
-
ogni categoria mostra le notizie che la riguardano. Tale flusso sarà un altro caso di navigazione laterale con Adapter e lo implementeremo con il
ListFragment
che vedremo tra poco; - ogni notizia elencata all'interno della categoria potrà essere selezionata, e la sua descrizione verrà mostrata in un'Activity a parte. Ciò significa che implementeremo una navigazione discendente, che approfondiremo nelle prossime lezioni.
I Fragment
Un Fragment può essere considerato come una "piccola Activity". È un modulo all'interno del quale possiamo specificare un layout e le funzionalità di gestione principali. Ha un suo ciclo di vita ma non è una componente di sistema indipendente, tanto che ha bisogno sempre di essere inserito in un'Activity.
Utilizzeremo i Fragment per rappresentare ogni scheda che vogliamo incorporare nell'interfaccia utente. Visto che la scheda dovrà contenere tutti gli articoli di una categoria, ci farebbe comodo assegnarle un layout dotato di ListView. Tuttavia ciò non sarà necessario poichè sfrutteremo una tipologia speciale di Fragment, il ListFragment, che nasce già dotato di una ListView al suo interno. Si consideri - come spunto di riflessione - che, al posto della ListView, si potrebbe usare una RecyclerView, che abbiamo conosciuto nelle scorse lezioni. Nell'esempio tuttavia si è preferito il widget più tradizionale, data la sua ampia diffusione di utilizzo.
public class ContentFragment extends ListFragment
{
@Override
public void onListItemClick(ListView lv, View view, int position, long id)
{
/*
* azione collegata al click sulla singola voce mostrata.
* Per il momento la lasciamo vuota
*/
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Bundle b = getArguments();
String category = (String) b.get(DataProvider.CATEGORY);
ArrayAdapter<Content> adapter = new ArrayAdapter<Content>(
inflater.getContext(), android.R.layout.simple_list_item_1,
DataProvider.getContentsByCategory(category));
setListAdapter(adapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
}
ViewPager e le Tab
ll ViewPager viene fissato direttamente nel layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/tablayout"/>
</android.support.design.widget.AppBarLayout>
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v4.view.ViewPager>
</LinearLayout>
La logica di gestione dei Fragment in questo esempio si basa sempre sul concetto di Adapter. Creeremo infatti una classe PageAdapter
, estensione di FragmentPagerAdapter
:
public class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(FragmentManager manager)
{
super(manager);
}
@Override
public Fragment getItem(int index) {
Bundle b=new Bundle();
ContentFragment fragment=null;
String category=DataProvider.getCategoryByIndex(index);
b.putString(DataProvider.CATEGORY,category);
fragment=new ContentFragment();
fragment.setArguments(b);
return fragment;
}
@Override
public int getCount()
{
return DataProvider.getCatoriesCount();
}
@Override
public CharSequence getPageTitle(int position) {
return DataProvider.getCategoryByIndex(position);
}
}
Nel metodo onCreate
dell'Activity verrà prima istanziato un PageAdapter
, che subito verrà assegnato al ViewPager con il metodo setAdapter
:
mAdapter = new PagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);
Affinchè il ViewPager e le Tab possano convivere nella stessa Activity e condividere gli stessi Fragment, è necessario che si scambino a vicenda le informazioni sul proprio stato. Il widget TabLayout, appartenente alla Android Design Support Library, tramite il metodo setupWithViewPager
, viene agganciato alla ViewPager. In questa maniera, potremo consultare le Tab cliccandone il titolo o sfogliandole con lo swipe.
public class MainActivity extends AppCompatActivity
{
private ViewPager mViewPager;
private PagerAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setSubtitle("Creazione di un NewsReader");
mViewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new PagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapter);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tablayout);
tabLayout.setupWithViewPager(mViewPager);
}
}
Il codice d'esempio visto fin qui è allegato a questa lezione, e liberamente scaricabile.