Abbiamo dovuto implementare esattamente lo stesso comportamento descritto di recente per un'app. Le schermate e il flusso generale dell'applicazione erano già definiti, quindi dovevamo attenerci (è un clone di app iOS ...). Fortunatamente, siamo riusciti a sbarazzarci dei pulsanti indietro sullo schermo :)
Abbiamo hackerato la soluzione usando una combinazione di TabActivity, FragmentActivities (stavamo usando la libreria di supporto per i frammenti) e Fragments. In retrospettiva, sono abbastanza sicuro che non sia stata la migliore decisione sull'architettura, ma siamo riusciti a far funzionare la cosa. Se dovessi farlo di nuovo, probabilmente proverei a fare una soluzione più basata sulle attività (senza frammenti), o provare ad avere solo un'attività per le schede e lasciare che tutte le altre siano viste (che trovo siano molto più riutilizzabile rispetto alle attività complessive).
Quindi i requisiti erano avere alcune schede e schermate annidabili in ciascuna scheda:
tab 1
screen 1 -> screen 2 -> screen 3
tab 2
screen 4
tab 3
screen 5 -> 6
eccetera...
Quindi dire: l'utente inizia nella scheda 1, passa dalla schermata 1 alla schermata 2 quindi alla schermata 3, quindi passa alla scheda 3 e naviga dalla schermata 4 alla 6; se è tornato alla scheda 1, dovrebbe vedere di nuovo la schermata 3 e se ha premuto Indietro dovrebbe tornare alla schermata 2; Torna di nuovo ed è nella schermata 1; passa alla scheda 3 ed è di nuovo nella schermata 6.
L'attività principale nell'applicazione è MainTabActivity, che estende TabActivity. Ogni scheda è associata a un'attività, diciamo ActivityInTab1, 2 e 3. E quindi ogni schermata sarà un frammento:
MainTabActivity
ActivityInTab1
Fragment1 -> Fragment2 -> Fragment3
ActivityInTab2
Fragment4
ActivityInTab3
Fragment5 -> Fragment6
Ogni ActivityInTab contiene solo un frammento alla volta e sa come sostituire un frammento con un altro (praticamente uguale a un ActvityGroup). La cosa bella è che è abbastanza facile mantenere stack posteriori separati per ogni scheda in questo modo.
Le funzionalità di ciascun ActivityInTab erano piuttosto le stesse: sapere come spostarsi da un frammento all'altro e mantenere uno stack posteriore, quindi lo inseriamo in una classe base. Chiamiamolo semplicemente ActivityInTab:
abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_tab);
}
/**
* Navigates to a new fragment, which is added in the fragment container
* view.
*
* @param newFragment
*/
protected void navigateTo(Fragment newFragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content, newFragment);
// Add this transaction to the back stack, so when the user presses back,
// it rollbacks.
ft.addToBackStack(null);
ft.commit();
}
}
Activity_in_tab.xml è proprio questo:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:isScrollContainer="true">
</RelativeLayout>
Come puoi vedere, il layout della vista per ogni scheda era lo stesso. Questo perché è solo un FrameLayout chiamato contenuto che conterrà ogni frammento. I frammenti sono quelli che hanno la vista di ogni schermo.
Solo per i punti bonus, abbiamo anche aggiunto un po 'di codice per mostrare una finestra di dialogo di conferma quando l'utente preme Indietro e non ci sono più frammenti su cui tornare:
// In ActivityInTab.java...
@Override
public void onBackPressed() {
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 0) {
// If there are back-stack entries, leave the FragmentActivity
// implementation take care of them.
super.onBackPressed();
} else {
// Otherwise, ask user if he wants to leave :)
showExitDialog();
}
}
Questo è praticamente il setup. Come puoi vedere, ogni FragmentActivity (o semplicemente semplicemente Activity in Android> 3) si occupa di tutto il back-stacking con il suo FragmentManager.
Un'attività come ActivityInTab1 sarà davvero semplice, mostrerà solo il suo primo frammento (cioè lo schermo):
public class ActivityInTab1 extends ActivityInTab {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
navigateTo(new Fragment1());
}
}
Quindi, se un frammento deve navigare verso un altro frammento, deve eseguire un casting un po 'brutto ... ma non è poi così male:
// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());
Quindi è praticamente tutto. Sono abbastanza sicuro che questa non sia una soluzione molto canonica (e per lo più sicuramente non molto buona), quindi vorrei chiedere agli sviluppatori Android esperti quale sarebbe un approccio migliore per raggiungere questa funzionalità, e se questo non è "come è fatto "in Android, ti sarei grato se potessi indicarmi qualche link o materiale che spieghi quale sia il modo Android di avvicinarsi a questo (schede, schermate nidificate nelle schede, ecc.). Sentiti libero di dividere questa risposta nei commenti :)
Come segno che questa soluzione non è molto buona è che recentemente ho dovuto aggiungere alcune funzionalità di navigazione all'applicazione. Qualche bizzarro pulsante che dovrebbe portare l'utente da una scheda all'altra e in una schermata nidificata. Fare questo a livello di programmazione è stato un dolore nel sedere, a causa di chissà chi e chi affronta quando sono frammenti e attività effettivamente istanziati e inizializzati. Penso che sarebbe stato molto più semplice se quelle schermate e schede fossero davvero tutte viste.
Infine, se devi sopravvivere ai cambiamenti di orientamento, è importante che i tuoi frammenti vengano creati usando setArguments / getArguments. Se imposti le variabili di istanza nei costruttori dei tuoi frammenti sarai fregato. Ma per fortuna è davvero facile da risolvere: basta salvare tutto in setArguments nel costruttore e quindi recuperare quelle cose con getArguments in onCreate per usarle.