Android – Navigazione TabActivity

Tra i layout disponibili su Android esiste il TabActivity, questo permette di creare una navigazione a tab tra varie finestre. Il suo utilizzo di base non è complicato. I problemi iniziano quando dall’interno di un Tab  si va a richiamare un secondo o un terzo Activity e quindi poi a cercare di ritornare indietro sul primo.Questo post vuole fare chiarezza su questa problematica riportando vari esempi trovati su internet.

Definizione della struttura principale

Come si può vedere dall’immagine affianco, l’applicativo di esempio è molto semplice, consiste in una maschera con tre Tab, in ogni Tab verrà rappresentata una versione più performante per la gestione della navigazione. La creazione della classe che estende TabActivity è identica alla spiegazione sul sito ufficiale link,  e presente nella prima porzione di codice sotto.
La classe Esempio estende TabActivity, nell’evento onCreate, si definiscono via codice (è possibile farlo anche tramite xml), i Tab e l’associazione del Activity da aprire sull’evento di click.

[codesyntax lang=”java” lines=”no” blockstate=”collapsed”]

package it.vivido.android.esempi;
import it.vivido.android.esempi.testdue.TabActivityGroupUno;
import it.vivido.android.esempi.testtre.TabActivityGroupDue;
import it.vivido.android.esempi.testuno.TabActivityUno;
import android.app.TabActivity;import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.widget.TabHost;

public class Esempio extends TabActivity{

public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Resources res = getResources();
TabHost tabHost = getTabHost();
TabHost.TabSpec spec;
Intent intent = new Intent().setClass(this, TabActivityUno.class);

spec = tabHost.newTabSpec("TabActivity").setIndicator("Test Uno",res.getDrawable(R.drawable.home)).setContent(intent);
tabHost.addTab(spec);
intent = new Intent().setClass(this, TabActivityGroupUno.class);
spec = tabHost.newTabSpec("TabActivityGroupUno").setIndicator("Test Due",res.getDrawable(R.drawable.compass)).setContent(intent);	

tabHost.addTab(spec);
intent = new Intent().setClass(this, TabActivityGroupDue.class);
spec = tabHost.newTabSpec("TabActivityGroupDue").setIndicator("Test Tre",res.getDrawable(R.drawable.compass)).setContent(intent);
tabHost.addTab(spec);

tabHost.setCurrentTab(0);
}
}

[/codesyntax]

Test Uno

In questo esempio l’apertura di un Activity sugli eventi dei tasti è la classica chiamata :

Intent edit = new Intent(getApplicationContext(),TabActivityTre.class);
startActivity(edit);

E’ ovvio che il risultato è l’apertura dell Activity sull’intera maschera fuori dal contesto del TabActivity, questo esempio mi serve per sperimentare il funzionamento a pila della apertura e chiusura degli Activity. Questo comportamento è gestito in modo automatico in Android.

Come si può vedere dall’esempio via via che chiudo le varie maschere aperte si riapre quella chiamante.

[yframe url=’http://www.youtube.com/watch?v=Nhb_g05FUmM’]

[codesyntax lang=”java” lines=”no” strict=”no” blockstate=”collapsed”]

buttondue = (Button)findViewById(R.id.buttondue);
buttondue.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
Intent edit = new Intent(getApplicationContext(),TabActivityDue.class);
startActivity(edit);
}
});

[/codesyntax]

Quello che mi aspettavo, era un identico comportamento nella apertura di più activity all’interno del tab.

Test Due – Apertura degli activity nel contesto del Tab

Procediamo con un altro test, in tanto creiamo una classe che estende un ActivityGroup, che come descrive il link è un uno schermo che contiene e gestisce molteplici activity.

La funzione che vado a chiamare è  getLocalActivityManager() che restituisce un LocalActivityManager , il cui scopo è di aiutare la gestione di multipli activity all’interno del ActivityGroup.

In particolare io utilizzo comunque la proprietà startActivity, che restituisce una Window utilizzata per assegnare alla view della ActivityGroup.

[codesyntax lang=”java” lines=”no” strict=”no” blockstate=”collapsed”]

Intent intent = new Intent(getParent(),TabActivityUno.class);

Window window = getLocalActivityManager().startActivity(“TabActivityUno”,intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));

if (window != null)
{
setContentView(window.getDecorView());
}

[/codesyntax]

Mentre poi in tutti gli eventi dei vari pulsanti per l’apertura di un nuovo activity eseguo il seguente codice :

[codesyntax lang=”java” lines=”no” strict=”no” blockstate=”collapsed”]

TabActivityGroupUno padre =(TabActivityGroupUno)getParent();
Intent edit = new Intent(padre,TabActivityDue.class);
Window window =

padre.getLocalActivityManager().startActivity(“TabActivityDue”,

edit.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));

if (window != null)
{
setContentView(window.getDecorView());
}

[/codesyntax]

L’unica differenza tra le due porzioni di codice è nella chiamata getParent che mi restituisce il contenitore padre che in questo caso è proprio ActivityGroup. Questo mi permette di aprire il secondo Activity sempre al suo interno.

Ma come si può vedere dal filmato successivo appena cerco di tornare al primo Activity aperto tramite un evento di back o una chiamata della funzione finish() mi si chiude definitivamente l’applicativo.

[yframe url=’http://www.youtube.com/watch?v=OcSW0SE0e9U’]

E’ interessante capire il perche, infatti gli eventi che andiamo a chiamare di backpressed non sono dell’activity visualizzato, ma ben si dell ActivityGroup che lo contiene. Quindi è ovvio che l’applicativo si chiuda in quanto ActivityGroup è effettivamente il primo aperto.

Test Tre – Corretta navigazione nei Tab

L’ultima prova che vi presento è in realtà una soluzione accettabile. Tutto si basa sulla seguente estensione della ActivityGroup.

[yframe url=’http://www.youtube.com/watch?v=vVAla6IPEbk’]

[codesyntax lang=”java” lines=”no” blockstate=”collapsed”]

....

private ArrayList<String> ListIdActivity;

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (ListIdActivity == null) ListIdActivity = new ArrayList<String>();
}

@Override
public void finishFromChild(Activity child)
{
LocalActivityManager manager = getLocalActivityManager();
int index = ListIdActivity.size()-1;
Log.i("finishFromChild ",ListIdActivity.get(index));

if (index < 1)
{
finish();
return;
}

manager.destroyActivity(ListIdActivity.get(index), true);
ListIdActivity.remove(index); index--;
String Id = ListIdActivity.get(index);
Activity lastIntent = manager.getActivity(Id);
Window newWindow =lastIntent.getWindow();
setContentView(newWindow.getDecorView());
}

public void startChildActivity(String Id, Intent intent)
{
Window window = getLocalActivityManager().startActivity(Id,intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
if (window != null)
{
ListIdActivity.add(Id);
setContentView(window.getDecorView());
Log.i("Open Activity : ",Id);
}
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
return true;
}
return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK)
{
Log.i("onKeyUp ","");
onBackPressed();
return true;
}
return super.onKeyUp(keyCode, event);
}

@Override
public void  onBackPressed  ()
{
int length = ListIdActivity.size();
if ( length > 1)
{
Activity current = getLocalActivityManager().getActivity(ListIdActivity.get(length-1));
current.finish();
}
}

[/codesyntax]

In particolare sfruttando una lista di Stringhe si va a ricreare l’effettuto della gestione a pila di Android a mano. Come potete vedere nell’evento di finishFromChild viene recuperato l’activity precedentemente aperto per poi rivisualizzarlo, evitando di riaprire sempre un nuovo oggetto.

Dopo di che basta estendere questa classe invece dell’ ActivityGroup e il risultato è accettabile.

Abbiamo ancora un problema sempre legato alla gestione del landscape. Appena ruotiamo il cellulare si può notare che viene perso il riferimento all’ activity aperto. Io attualmente ho risolto solo bloccando la possibilità di ruotare l’applicativo.

Link da cui ho preso le informazioni:


5 commenti

  • Ciao,sarebbe possibile avere il codice completo di questa app di prova ?

    Matteo Reply
  • Grazie mille !!!

    Matteo Reply
  • Ciao, stavo studiando questa maledetta tabactivity e mi sono imbattuto in questo vostro “tutorial”.
    Volevo chiedere se avete idee in merito a quanto mi accade:
    Io mi trovo nella condizione di aver alimentato 4 tabs con icone personalizzate ecc ma il contenuto del primo tab, che contiene una sola activity e che dovrebbe visualizzare uno specifico layout, non viene visualizzato correttamente (gli altri tab vengono visualizzati correttamente). Il layout in questione contiene una expandablelistview ed un pulsante … Avete idee?

    Volevo poi segnalare che nel vostro tutorial, nel tab “Test Tre” non viene gestita la pressione del tasto chiudi, mi permetto di integrare con questa porzione di codice dopo aver aggiunto il pulsante [buttonchiudi] fra le altre variabili:

    buttonchiudi = (Button)findViewById(R.id.chiudi);
    buttonchiudi.setVisibility(View.VISIBLE); // anche se inutile
    buttonchiudi.setOnClickListener(new OnClickListener()
    {
    @Override
    public void onClick(View v)
    {
    onBackPressed(); // oppure finish();
    }
    });

    Alberto Reply
    • Ciao Alberto

      francamente cosi su due piedi non saprei, non mi è mai capitato nulla di simile.
      Grazie per l’integrazione.

      Andrea

      Andrea Serena Reply

Lascia un commento

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