Separater Back Stack für jede Registerkarte in Android mithilfe von Fragmenten

158

Ich versuche, Registerkarten für die Navigation in einer Android-App zu implementieren. Da TabActivity und ActivityGroup veraltet sind, möchte ich es stattdessen mit Fragmenten implementieren.

Ich weiß, wie man ein Fragment für jede Registerkarte einrichtet und dann die Fragmente wechselt, wenn auf eine Registerkarte geklickt wird. Aber wie kann ich für jede Registerkarte einen eigenen Backstack haben?

In einem Beispiel befinden sich Fragment A und B unter Tab 1 und Fragment C und D unter Tab 2. Wenn die App gestartet wird, wird Fragment A angezeigt und Tab 1 ausgewählt. Dann könnte Fragment A durch Fragment B ersetzt werden. Wenn Tab 2 ausgewählt ist, sollte Fragment C angezeigt werden. Wenn Tab 1 ausgewählt ist, sollte Fragment B erneut angezeigt werden. Zu diesem Zeitpunkt sollte es möglich sein, mit der Schaltfläche "Zurück" Fragment A anzuzeigen.

Es ist auch wichtig, dass der Status für jede Registerkarte beibehalten wird, wenn das Gerät gedreht wird.

BR Martin

mardah
quelle

Antworten:

23

Das Framework erledigt dies derzeit nicht automatisch für Sie. Sie müssen für jede Registerkarte Ihre eigenen Backstacks erstellen und verwalten.

Um ehrlich zu sein, scheint dies eine wirklich fragwürdige Sache zu sein. Ich kann mir nicht vorstellen, dass dies zu einer anständigen Benutzeroberfläche führt - wenn die Zurück-Taste je nach Registerkarte unterschiedliche Aufgaben ausführt, insbesondere wenn die Zurück-Taste auch das normale Verhalten hat, die gesamte Aktivität oben zu schließen Der Stapel ... klingt böse.

Wenn Sie versuchen, so etwas wie eine Webbrowser-Benutzeroberfläche zu erstellen, müssen Sie je nach Kontext viele subtile Verhaltensänderungen vornehmen, um eine für den Benutzer natürliche Benutzeroberfläche zu erhalten. Sie müssen also auf jeden Fall Ihren eigenen Backstack erstellen Management, anstatt sich auf eine Standardimplementierung im Framework zu verlassen. Versuchen Sie beispielsweise, darauf zu achten, wie die Zurück-Taste auf verschiedene Arten mit dem Standardbrowser interagiert. (Jedes "Fenster" im Browser ist im Wesentlichen eine Registerkarte.)

Hackbod
quelle
7
Tu das nicht. Und das Framework ist kaum nutzlos. Es gibt Ihnen keine automatische Unterstützung für diese Art von Dingen, was ich, wie gesagt, nicht vorstellen kann, zu einer anständigen Benutzererfahrung zu führen, außer in sehr speziellen Situationen, in denen Sie das Rückenverhalten ohnehin sorgfältig kontrollieren müssen.
Hackbod
9
Diese Art der Navigation, dann haben Sie Registerkarten und eine Hierarchie von Seiten auf jeder Registerkarte ist beispielsweise für iPhone-Anwendungen sehr verbreitet (Sie können App Store- und iPod-Apps überprüfen). Ich finde ihre Benutzererfahrung ziemlich anständig.
Dmitry Ryadnenko
13
Das ist verrückt. Das iPhone hat nicht einmal eine Zurück-Taste. Es gibt API-Demos, die sehr einfachen Code zum Implementieren von Fragmenten in Registerkarten zeigen. Die Frage, die gestellt wurde, betraf unterschiedliche Backstacks für jede Registerkarte, und meine Antwort lautet, dass das Framework dies nicht automatisch bereitstellt, da es semantisch für das, was die Zurück-Schaltfläche tut, höchstwahrscheinlich eine beschissene Benutzererfahrung wäre. Sie können die Back-Semantik ziemlich einfach selbst implementieren, wenn Sie möchten.
Hackbod
4
Auch hier verfügt das iPhone nicht über eine Zurück-Schaltfläche, sodass es semantisch kein Back-Stack-Verhalten wie Android aufweist. Auch "es ist besser, einfach bei Aktivitäten zu bleiben und mir viel Zeit zu sparen" macht hier keinen Sinn, da Sie bei Aktivitäten keine Wartungs-Tabs in eine Benutzeroberfläche mit ihren eigenen unterschiedlichen Backstacks einfügen können. Tatsächlich ist das Back-Stack-Management von Aktivitäten weniger flexibel als das Fragment-Framework.
Hackbod
22
@hackbod Ich versuche, Ihren Punkten zu folgen, habe jedoch Probleme beim Implementieren des benutzerdefinierten Backstack-Verhaltens. Mir ist klar, dass Sie, wenn Sie an der Gestaltung beteiligt waren, einen soliden Einblick haben würden, wie einfach es sein könnte. Ist es möglich, dass es eine Demo-App für den Anwendungsfall des OP gibt, da dies wirklich eine sehr häufige Situation ist, insbesondere für diejenigen von uns, die iOS-Apps für Kunden schreiben und portieren müssen, die diese Anforderungen stellen ... Verwaltung von separaten Fragment-Backstacks innerhalb jeder FragmentActivity.
Richard Le Mesurier
138

Ich bin furchtbar spät bei dieser Frage. Aber da dieser Thread für mich sehr informativ und hilfreich war, dachte ich, ich poste meine zwei Pence besser hier.

Ich brauchte einen solchen Bildschirmablauf (ein minimalistisches Design mit 2 Registerkarten und 2 Ansichten in jeder Registerkarte).

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2

Ich hatte in der Vergangenheit die gleichen Anforderungen und habe dies mit TabActivityGroup(was zu diesem Zeitpunkt ebenfalls veraltet war) und Aktivitäten durchgeführt. Diesmal wollte ich Fragmente verwenden.

So habe ich es gemacht.

1. Erstellen Sie eine Basisfragmentklasse

public class BaseFragment extends Fragment {
    AppMainTabActivity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = (AppMainTabActivity) this.getActivity();
    }

    public void onBackPressed(){
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data){
    }
}

Alle Fragmente in Ihrer App können diese Basisklasse erweitern. Wenn Sie spezielle Fragmente wie verwenden möchten, ListFragmentsollten Sie auch dafür eine Basisklasse erstellen. Sie werden über die Verwendung von klar sein onBackPressed()und onActivityResult()wenn Sie den Beitrag vollständig lesen ..

2. Erstellen Sie einige Registerkarten-IDs, auf die überall im Projekt zugegriffen werden kann

public class AppConstants{
    public static final String TAB_A  = "tab_a_identifier";
    public static final String TAB_B  = "tab_b_identifier";

    //Your other constants, if you have them..
}

hier gibt es nichts zu erklären ..

3. Ok, Hauptregisteraktivität - Bitte gehen Sie die Kommentare im Code durch.

public class AppMainFragmentActivity extends FragmentActivity{
    /* Your Tab host */
    private TabHost mTabHost;

    /* A HashMap of stacks, where we use tab identifier as keys..*/
    private HashMap<String, Stack<Fragment>> mStacks;

    /*Save current tabs identifier in this..*/
    private String mCurrentTab;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_main_tab_fragment_layout);

        /*  
         *  Navigation stacks for each tab gets created.. 
         *  tab identifier is used as key to get respective stack for each tab
         */
        mStacks             =   new HashMap<String, Stack<Fragment>>();
        mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
        mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

        mTabHost                =   (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(listener);
        mTabHost.setup();

        initializeTabs();
    }


    private View createTabView(final int id) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView =   (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        return view;
    }

    public void initializeTabs(){
        /* Setup your tab icons and content views.. Nothing special in this..*/
        TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
        mTabHost.setCurrentTab(-3);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
        mTabHost.addTab(spec);


        spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
        mTabHost.addTab(spec);
    }


    /*Comes here when user switch tab, or we do programmatically*/
    TabHost.OnTabChangeListener listener    =   new TabHost.OnTabChangeListener() {
      public void onTabChanged(String tabId) {
        /*Set current tab..*/
        mCurrentTab                     =   tabId;

        if(mStacks.get(tabId).size() == 0){
          /*
           *    First time this tab is selected. So add first fragment of that tab.
           *    Dont need animation, so that argument is false.
           *    We are adding a new fragment which is not present in stack. So add to stack is true.
           */
          if(tabId.equals(AppConstants.TAB_A)){
            pushFragments(tabId, new AppTabAFirstFragment(), false,true);
          }else if(tabId.equals(AppConstants.TAB_B)){
            pushFragments(tabId, new AppTabBFirstFragment(), false,true);
          }
        }else {
          /*
           *    We are switching tabs, and target tab is already has atleast one fragment. 
           *    No need of animation, no need of stack pushing. Just show the target fragment
           */
          pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
        }
      }
    };


    /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
    public void setCurrentTab(int val){
          mTabHost.setCurrentTab(val);
    }


    /* 
     *      To add fragment to a tab. 
     *  tag             ->  Tab identifier
     *  fragment        ->  Fragment to show, in tab identified by tag
     *  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
     *                      true when when we are pushing more fragment into navigation stack. 
     *  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
     *                      true in all other cases.
     */
    public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
      if(shouldAdd)
          mStacks.get(tag).push(fragment);
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      if(shouldAnimate)
          ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }


    public void popFragments(){
      /*    
       *    Select the second last fragment in current tab's stack.. 
       *    which will be shown after the fragment transaction given below 
       */
      Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

      /*pop current fragment from stack.. */
      mStacks.get(mCurrentTab).pop();

      /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }   


    @Override
    public void onBackPressed() {
        if(mStacks.get(mCurrentTab).size() == 1){
          // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
          finish();
          return;
        }

        /*  Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
         *  when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
         *  kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
         */
        ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

        /* Goto previous fragment in navigation stack of this tab */
            popFragments();
    }


    /*
     *   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
     *  in that fragment, and called it from the activity. But couldn't resist myself.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(mStacks.get(mCurrentTab).size() == 0){
            return;
        }

        /*Now current fragment on screen gets onActivityResult callback..*/
        mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
    }
}

4. app_main_tab_fragment_layout.xml (Falls jemand interessiert ist.)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

    </LinearLayout>
</TabHost>

5. AppTabAFirstFragment.java (Erstes Fragment in Tab A, ähnlich für alle Tabs)

public class AppTabAFragment extends BaseFragment {
    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.fragment_one_layout, container, false);

        mGoToButton =   (Button) view.findViewById(R.id.goto_button);
        mGoToButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
        }
    }
}

Dies ist möglicherweise nicht der polierteste und korrekteste Weg. Aber in meinem Fall hat es wunderbar funktioniert. Auch hatte ich diese Anforderung nur im Hochformat. Ich musste diesen Code nie in einem Projekt verwenden, das beide Orientierungen unterstützt. Ich kann also nicht sagen, welchen Herausforderungen ich dort gegenüberstehe.

EDIT:

Wenn jemand ein vollständiges Projekt haben möchte, habe ich ein Beispielprojekt an github gesendet .

Krishnabhadra
quelle
2
Daten für jedes Fragment speichern, jedes neu erstellen, die Stapel neu erstellen ... so viel Arbeit für eine einfache Orientierungsänderung.
Michael Eilers Smith
3
@omegatai stimmt Ihnen vollkommen zu. Das ganze Problem tritt auf, da Android den Stack für uns nicht verwaltet ( was iOS tut und eine Änderung der Ausrichtung oder ein Tab mit mehreren Fragmenten ein Kinderspiel ist ) und uns zu der ursprünglichen Diskussion in diesem Q / zurückführt. Ein Thread. Es ist nicht gut, jetzt darauf zurückzukommen.
Krishnabhadra
1
@Renjith Dies liegt daran, dass das Fragment jedes Mal neu erstellt wird, wenn Sie die Registerkarte wechseln. Denken Sie nicht einmal daran, dass Ihr Fragment über die Registerkarte hinweg wiederverwendet wird. Wenn ich von der Registerkarte A zu B wechsle, wird die Registerkarte A aus dem Speicher freigegeben. Speichern Sie also Ihre Daten in Aktivität und überprüfen Sie jedes Mal, ob die Aktivität Daten enthält, bevor Sie versuchen, sie vom Server abzurufen.
Krishnabhadra
2
@Krishnabhadra Ok, das klingt viel besser. Lassen Sie mich korrigieren, falls ich falsch liege. Gemäß Ihrem Beispiel gibt es nur eine Aktivität und daher ein Bündel. Erstellen Sie Adapterinstanzen im BaseFragment (unter Bezugnahme auf Ihr Projekt) und speichern Sie dort Daten. Verwenden Sie sie, wenn eine Ansicht erstellt werden soll.
Renjith
1
Ich habe es zum Laufen gebracht. Vielen Dank. Das gesamte Projekt hochzuladen war eine gute Idee! :-)
Vinay W
96

Wir mussten genau das Verhalten implementieren, das Sie kürzlich für eine App beschrieben haben. Die Bildschirme und der Gesamtfluss der Anwendung wurden bereits definiert, sodass wir uns daran halten mussten (es ist ein iOS-App-Klon ...). Zum Glück haben wir es geschafft, die Zurück-Schaltflächen auf dem Bildschirm loszuwerden :)

Wir haben die Lösung mit einer Mischung aus TabActivity, FragmentActivities (wir haben die Support-Bibliothek für Fragmente verwendet) und Fragmenten gehackt. Rückblickend bin ich mir ziemlich sicher, dass es nicht die beste Architekturentscheidung war, aber wir haben es geschafft, das Ding zum Laufen zu bringen. Wenn ich es noch einmal tun müsste, würde ich wahrscheinlich versuchen, eine aktivitätsbasierte Lösung (keine Fragmente) zu erstellen, oder versuchen, nur eine Aktivität für die Registerkarten zu haben und alle anderen Ansichten sein zu lassen (was meiner Meinung nach viel mehr ist wiederverwendbar als Aktivitäten insgesamt).

Die Anforderungen waren also, dass in jeder Registerkarte einige Registerkarten und verschachtelbare Bildschirme vorhanden waren:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6

etc...

Sagen wir also: Der Benutzer startet in Tab 1, navigiert von Bildschirm 1 zu Bildschirm 2 und dann zu Bildschirm 3, wechselt dann zu Tab 3 und navigiert von Bildschirm 4 zu 6; Wenn er zurück zu Tab 1 wechselt, sollte er Bildschirm 3 wieder sehen und wenn er Zurück drückt, sollte er zu Bildschirm 2 zurückkehren. Wieder zurück und er ist in Bildschirm 1; Wechseln Sie zu Tab 3 und er ist wieder in Bildschirm 6.

Die Hauptaktivität in der Anwendung ist MainTabActivity, wodurch TabActivity erweitert wird. Jede Registerkarte ist einer Aktivität zugeordnet, beispielsweise ActivityInTab1, 2 und 3. Und dann ist jeder Bildschirm ein Fragment:

MainTabActivity
  ActivityInTab1
    Fragment1 -> Fragment2 -> Fragment3
  ActivityInTab2
    Fragment4
  ActivityInTab3
    Fragment5 -> Fragment6

Jedes ActivityInTab enthält jeweils nur ein Fragment und weiß, wie ein Fragment durch ein anderes ersetzt wird (fast das gleiche wie bei einer ActvityGroup). Das Coole ist, dass es auf diese Weise ziemlich einfach ist, separate Rückenstapel für jede Lasche zu erhalten.

Die Funktionalität für jedes ActivityInTab war ziemlich gleich: Wissen, wie man von einem Fragment zum anderen navigiert und einen Backstack verwaltet, also haben wir das in eine Basisklasse eingefügt. Nennen wir es einfach 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();
    }

}

Die activity_in_tab.xml ist genau das:

<?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>

Wie Sie sehen können, war das Ansichtslayout für jede Registerkarte das gleiche. Das liegt daran, dass es sich nur um ein FrameLayout namens content handelt, das jedes Fragment enthält. Die Fragmente sind diejenigen, die die Ansicht jedes Bildschirms haben.

Nur für die Bonuspunkte haben wir auch einen kleinen Code hinzugefügt, um einen Bestätigungsdialog anzuzeigen, wenn der Benutzer Zurück drückt und es keine Fragmente mehr gibt, zu denen er zurückkehren kann:

// 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();
    }
}

Das ist so ziemlich das Setup. Wie Sie sehen können, kümmert sich jede FragmentActivity (oder einfach nur Aktivität in Android> 3) um das gesamte Backstacking mit einem eigenen FragmentManager.

Eine Aktivität wie ActivityInTab1 ist sehr einfach und zeigt nur das erste Fragment (dh den Bildschirm):

public class ActivityInTab1 extends ActivityInTab {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navigateTo(new Fragment1());
    }
}

Wenn ein Fragment dann zu einem anderen Fragment navigieren muss, muss es ein wenig böses Casting durchführen ... aber es ist nicht so schlimm:

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());

Das war's auch schon. Ich bin mir ziemlich sicher, dass dies keine sehr kanonische (und meistens sicher nicht sehr gute) Lösung ist. Daher möchte ich erfahrene Android-Entwickler fragen, was ein besserer Ansatz wäre, um diese Funktionalität zu erreichen, und ob dies nicht der Fall ist done“in Android, würde ich schätzen , wenn Sie mich zu einem gewissen Link oder Material zeigen könnten, der erklärt , was die Android Art und Weise diesen (Tabs, verschachtelte Bildschirme in Registerkarten usw.) zu nähern. Fühlen Sie sich frei, diese Antwort in den Kommentaren auseinander zu reißen :)

Als Zeichen dafür, dass diese Lösung nicht sehr gut ist, musste ich der Anwendung kürzlich einige Navigationsfunktionen hinzufügen. Eine bizarre Schaltfläche, die den Benutzer von einer Registerkarte in eine andere und in einen verschachtelten Bildschirm führen soll. Das programmgesteuert zu tun war ein Schmerz im Hintern, weil wer-weiß-wer-Probleme und der Umgang mit wann Fragmente und Aktivitäten tatsächlich instanziiert und initialisiert werden. Ich denke, es wäre viel einfacher gewesen, wenn diese Bildschirme und Registerkarten wirklich nur Ansichten gewesen wären.


Wenn Sie Orientierungsänderungen überleben müssen, ist es wichtig, dass Ihre Fragmente mit setArguments / getArguments erstellt werden. Wenn Sie Instanzvariablen in den Konstruktoren Ihrer Fragmente festlegen, werden Sie geschraubt. Aber zum Glück ist das wirklich einfach zu beheben: Speichern Sie einfach alles in setArguments im Konstruktor und rufen Sie diese Dinge dann mit getArguments in onCreate ab, um sie zu verwenden.

Epidemie
quelle
13
Tolle Antwort, aber ich denke, nur sehr wenige werden das sehen. Ich habe genau den gleichen Weg gewählt (wie Sie aus dem Gespräch in der vorherigen Antwort sehen können) und bin damit nicht so zufrieden wie Sie. Ich denke, Google hat diese Fragmente wirklich vermasselt, da diese API keine wichtigen Anwendungsfälle abdeckt. Ein weiteres Problem, auf das Sie möglicherweise stoßen, ist die Unmöglichkeit, ein Fragment in ein anderes Fragment einzubetten.
Dmitry Ryadnenko
Danke für den Kommentar Boulder. Ja, ich könnte nicht mehr über die Fragment-API zustimmen. Ich bin bereits auf das Problem verschachtelter Fragmente gestoßen (deshalb haben wir uns für den Ansatz "Ein Fragment durch ein anderes ersetzen" entschieden, hehe).
Epidemie
1
Ich habe dies über ALLE Aktivitäten implementiert. Mir hat nicht gefallen, was ich bekommen habe, und ich werde Fragmente ausprobieren. Das ist das Gegenteil Ihrer Erfahrung! Es gibt eine Menge Implementierungen mit Aktivitäten, um den Lebenszyklus von untergeordneten Ansichten in jeder Registerkarte zu verwalten und auch Ihre eigene Zurück-Schaltfläche zu implementieren. Außerdem können Sie nicht einfach auf alle Ansichten verweisen, da sonst der Speicher in die Luft gesprengt wird. Ich hoffe, dass Fragmente: 1) den Lebenszyklus der Fragmente mit einer klaren Speichertrennung unterstützen und 2) die Back-Button-Funktionalität implementieren. Wenn Sie Fragmente für diesen Prozess verwenden, ist es dann nicht einfacher, sie auf Tablets auszuführen?
Gregm
Was passiert, wenn der Benutzer die Registerkarte wechselt? Wird der Fragment-Backstack gelöscht? Wie kann sichergestellt werden, dass der Rückstapel erhalten bleibt?
Gregm
1
@gregm Wenn Sie wie ich 1 Tab <-> 1 Aktivität ausführen, bleibt der Backstack für jeden Tab erhalten, wenn die Tabs gewechselt werden, da die Aktivitäten tatsächlich am Leben bleiben. Sie werden nur angehalten und fortgesetzt. Ich weiß nicht, ob es eine Möglichkeit gibt, die Aktivitäten zu zerstören und neu zu erstellen, wenn Registerkarten in einer TabActivity gewechselt werden. Wenn Sie die Fragmente innerhalb der Aktivitäten machen werden jedoch ersetzt , wie ich vorgeschlagen, sie wird zerstört (und neu erstellt , wenn der Backstack umgestülpt worden ist). Sie haben also zu jeder Zeit höchstens ein Fragment pro Tab.
Epidemie
6

Das Speichern starker Verweise auf Fragmente ist nicht der richtige Weg.

FragmentManager bietet putFragment(Bundle, String, Fragment)und saveFragmentInstanceState(Fragment).

Beides reicht aus, um einen Backstack zu implementieren.


Unter Verwendung putFragmentanstelle eines Fragment zu ersetzen, lösen Sie das alte und fügen Sie den neuen. Dies ist, was das Framework für eine Ersetzungstransaktion tut, die dem Backstack hinzugefügt wird. putFragmentspeichert einen Index für die aktuelle Liste der aktiven Fragmente und diese Fragmente werden vom Framework bei Orientierungsänderungen gespeichert.

Bei der zweiten Methode wird saveFragmentInstanceStateder gesamte Fragmentstatus in einem Bundle gespeichert, sodass Sie ihn wirklich entfernen können, anstatt ihn zu trennen. Mit diesem Ansatz lässt sich der Backstack einfacher manipulieren, da Sie jederzeit ein Fragment einfügen können.


Ich habe die zweite Methode für diesen Anwendungsfall verwendet:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/

Ich möchte nicht, dass der Benutzer vom dritten zum Anmeldebildschirm zurückkehrt, indem er die Zurück-Taste drückt. Ich mache auch Flip-Animationen zwischen ihnen (mit onCreateAnimation), so dass hackige Lösungen nicht funktionieren, zumindest ohne dass der Benutzer klar bemerkt, dass etwas nicht stimmt.

Dies ist ein gültiger Anwendungsfall für einen benutzerdefinierten Backstack, der genau das tut, was der Benutzer erwartet ...

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";

private MyBackStack mBackStack;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);

    if (state == null) {
        mBackStack = new MyBackStack();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.add(R.id.act_base_frg_container, new SignInFragment());
        tr.commit();
    } else {
        mBackStack = state.getParcelable(STATE_BACKSTACK);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(STATE_BACKSTACK, mBackStack);
}

private void showFragment(Fragment frg, boolean addOldToBackStack) {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);

    FragmentTransaction tr = fm.beginTransaction();
    tr.replace(R.id.act_base_frg_container, frg);
    // This is async, the fragment will only be removed after this returns
    tr.commit();

    if (addOldToBackStack) {
        mBackStack.push(fm, oldFrg);
    }
}

@Override
public void onBackPressed() {
    MyBackStackEntry entry;
    if ((entry = mBackStack.pop()) != null) {
        Fragment frg = entry.recreate(this);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.replace(R.id.act_base_frg_container, frg);
        tr.commit();

        // Pop it now, like the framework implementation.
        fm.executePendingTransactions();
    } else {
        super.onBackPressed();
    }
}

public class MyBackStack implements Parcelable {

    private final List<MyBackStackEntry> mList;

    public MyBackStack() {
        mList = new ArrayList<MyBackStackEntry>(4);
    }

    public void push(FragmentManager fm, Fragment frg) {
        push(MyBackStackEntry.newEntry(fm, frg);
    }

    public void push(MyBackStackEntry entry) {
        if (entry == null) {
            throw new NullPointerException();
        }
        mList.add(entry);
    }

    public MyBackStackEntry pop() {
        int idx = mList.size() - 1;
        return (idx != -1) ? mList.remove(idx) : null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        final int len = mList.size();
        dest.writeInt(len);
        for (int i = 0; i < len; i++) {
            // MyBackStackEntry's class is final, theres no
            // need to use writeParcelable
            mList.get(i).writeToParcel(dest, flags);
        }
    }

    protected MyBackStack(Parcel in) {
        int len = in.readInt();
        List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
        for (int i = 0; i < len; i++) {
            list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
        }
        mList = list;
    }

    public static final Parcelable.Creator<MyBackStack> CREATOR =
        new Parcelable.Creator<MyBackStack>() {

            @Override
            public MyBackStack createFromParcel(Parcel in) {
                return new MyBackStack(in);
            }

            @Override
            public MyBackStack[] newArray(int size) {
                return new MyBackStack[size];
            }
    };
}

public final class MyBackStackEntry implements Parcelable {

    public final String fname;
    public final Fragment.SavedState state;
    public final Bundle arguments;

    public MyBackStackEntry(String clazz, 
            Fragment.SavedState state,
            Bundle args) {
        this.fname = clazz;
        this.state = state;
        this.arguments = args;
    }

    public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
        final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
        final String name = frg.getClass().getName();
        final Bundle args = frg.getArguments();
        return new MyBackStackEntry(name, state, args);
    }

    public Fragment recreate(Context ctx) {
        Fragment frg = Fragment.instantiate(ctx, fname);
        frg.setInitialSavedState(state);
        frg.setArguments(arguments);
        return frg;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(fname);
        dest.writeBundle(arguments);

        if (state == null) {
            dest.writeInt(-1);
        } else if (state.getClass() == Fragment.SavedState.class) {
            dest.writeInt(0);
            state.writeToParcel(dest, flags);
        } else {
            dest.writeInt(1);
            dest.writeParcelable(state, flags);
        }
    }

    protected MyBackStackEntry(Parcel in) {
        final ClassLoader loader = getClass().getClassLoader();
        fname = in.readString();
        arguments = in.readBundle(loader);

        switch (in.readInt()) {
            case -1:
                state = null;
                break;
            case 0:
                state = Fragment.SavedState.CREATOR.createFromParcel(in);
                break;
            case 1:
                state = in.readParcelable(loader);
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
        new Parcelable.Creator<MyBackStackEntry>() {

            @Override
            public MyBackStackEntry createFromParcel(Parcel in) {
                return new MyBackStackEntry(in);
            }

            @Override
            public MyBackStackEntry[] newArray(int size) {
                return new MyBackStackEntry[size];
            }
    };
}
sergio91pt
quelle
2

Haftungsausschluss:


Ich bin der Meinung, dass dies der beste Ort ist, um eine verwandte Lösung zu veröffentlichen, an der ich für eine ähnliche Art von Problem gearbeitet habe, die anscheinend ziemlich normal für Android ist. Es wird das Problem nicht für alle lösen, aber es kann einigen helfen.


Wenn der Hauptunterschied zwischen Ihren Fragmenten nur die Daten sind, die sie sichern (dh nicht viele große Layoutunterschiede), müssen Sie das Fragment möglicherweise nicht tatsächlich ersetzen, sondern lediglich die zugrunde liegenden Daten austauschen und die Ansicht aktualisieren.

Hier ist eine Beschreibung eines möglichen Beispiels für diesen Ansatz:

Ich habe eine App, die ListViews verwendet. Jedes Element in der Liste ist ein Elternteil mit einer bestimmten Anzahl von Kindern. Wenn Sie auf das Element tippen, muss eine neue Liste mit diesen untergeordneten Elementen auf derselben Registerkarte "Aktionsleiste" wie die ursprüngliche Liste geöffnet werden. Diese verschachtelten Listen haben ein sehr ähnliches Layout (einige bedingte Änderungen hier und da vielleicht), aber die Daten sind unterschiedlich.

Diese App hat mehrere Schichten von Nachkommen unterhalb der ursprünglichen Elternliste, und wir haben möglicherweise Daten vom Server, bis ein Benutzer versucht, auf eine bestimmte Tiefe über die erste hinaus zuzugreifen. Da die Liste aus einem Datenbankcursor erstellt wird und die Fragmente einen Cursorlader und einen Cursoradapter verwenden, um die Listenansicht mit Listenelementen zu füllen, müssen bei der Registrierung eines Klicks nur folgende Schritte ausgeführt werden:

1) Erstellen Sie einen neuen Adapter mit den entsprechenden Feldern "bis" und "von", die mit den neuen Elementansichten übereinstimmen, die der Liste hinzugefügt werden, sowie mit den vom neuen Cursor zurückgegebenen Spalten.

2) Legen Sie diesen Adapter als neuen Adapter für die ListView fest.

3) Erstellen Sie einen neuen URI basierend auf dem angeklickten Element und starten Sie den Cursorlader mit dem neuen URI (und der neuen Projektion) neu. In diesem Beispiel wird der URI bestimmten Abfragen zugeordnet, wobei die Auswahlargumente von der Benutzeroberfläche übergeben werden.

4) Wenn die neuen Daten aus dem URI geladen wurden, tauschen Sie den dem Adapter zugeordneten Cursor gegen den neuen Cursor. Die Liste wird dann aktualisiert.

Damit ist kein Backstack verbunden, da wir keine Transaktionen verwenden. Sie müssen also entweder Ihre eigenen erstellen oder die Abfragen beim Zurücksetzen aus der Hierarchie in umgekehrter Reihenfolge abspielen. Als ich dies versuchte, waren die Abfragen schnell genug, dass ich sie in oNBackPressed () einfach erneut ausführe, bis ich an der Spitze der Hierarchie stehe. Dann übernimmt das Framework wieder die Schaltfläche "Zurück".

Wenn Sie sich in einer ähnlichen Situation befinden, lesen Sie unbedingt die folgenden Dokumente: http://developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

Ich hoffe das hilft jemandem!

courtf
quelle
Wenn jemand dies tut und auch einen SectionIndexer (wie AlphabetIndexer) verwendet, stellen Sie möglicherweise fest, dass das schnelle Scrollen nach dem Ersetzen des Adapters nicht funktioniert. Ein unglücklicher Fehler, aber das Ersetzen des Adapters, selbst durch einen brandneuen Indexer, aktualisiert die Liste der von FastScroll verwendeten Abschnitte nicht. Es gibt eine Abhilfe finden Sie unter Beschreibung des Problems und Abhilfe
courtf
2

Ich hatte genau das gleiche Problem und implementierte ein Open-Source-Github-Projekt, das gestapelte Tabs, Back- und Up-Navigation abdeckt und gut getestet und dokumentiert ist:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Dies ist ein einfaches und kleines Framework für Navigationsregisterkarten und Fragmentwechsel sowie die Handhabung der Auf- und Rücknavigation. Jede Registerkarte hat einen eigenen Fragmentstapel. Es verwendet ActionBarSherlock und ist wieder mit API-Level 8 kompatibel.

Sebastian Baltes
quelle
2

Dies ist ein komplexes Problem, da Android nur 1 Backstack verarbeitet, dies ist jedoch möglich. Ich habe Tage gebraucht, um eine Bibliothek namens Tab Stacker zu erstellen, die genau das tut, wonach Sie suchen: einen Fragmentverlauf für jeden Tab. Es ist Open Source und vollständig dokumentiert und kann problemlos in gradle aufgenommen werden. Sie finden die Bibliothek auf github: https://github.com/smart-fun/TabStacker

Sie können auch die Beispiel-App herunterladen, um festzustellen, ob das Verhalten Ihren Anforderungen entspricht:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

Wenn Sie Fragen haben, zögern Sie nicht, eine Mail zu schreiben.

Arnaud SmartFun
quelle
2

Ich möchte meine eigene Lösung vorschlagen, falls jemand auf der Suche ist und versuchen möchte, die beste für seine Bedürfnisse auszuwählen.

https://github.com/drusak/tabactivity

Der Zweck der Erstellung der Bibliothek ist ziemlich banal - implementieren Sie sie wie das iPhone.

Die Hauptvorteile:

  • Verwenden Sie die Bibliothek android.support.design mit TabLayout.
  • Jede Registerkarte verfügt mit FragmentManager über einen eigenen Stapel (ohne die Referenzen der Fragmente zu speichern).
  • Unterstützung für Deep Linking (wenn Sie einen bestimmten Tab und eine bestimmte Fragmentebene darin öffnen müssen);
  • Speichern / Wiederherstellen von Registerkartenzuständen;
  • adaptive Lebenszyklusmethoden von Fragmenten in Registerkarten;
  • ganz einfach für Ihre Bedürfnisse zu implementieren.
Kasurd
quelle
Vielen Dank, das war sehr hilfreich. Ich muss ListFragments zusätzlich zu Fragments verwenden, also habe ich BaseTabFragment.java in BaseTabListFragment.java dupliziert und ListFragment erweitern lassen. Dann musste ich verschiedene Teile im Code ändern, wobei immer davon ausgegangen wurde, dass ein BaseTabFragment erwartet wird. Gibt es einen besseren Weg?
Primehalo
Ich habe leider nicht an ListFragment gedacht. Technisch gesehen ist es die richtige Lösung, erfordert jedoch zusätzliche Überprüfungen für TabFragment und dessen Instanz von BaseTabListFragment. Ein weiterer Ansatz zur Verwendung von Fragment mit ListView im Inneren (genau wie bei ListFragment implementiert). Ich werde darüber nachdenken. Danke, dass du mich darauf hingewiesen hast!
Kasurd
1

Eine einfache Lösung:

Jedes Mal, wenn Sie den Tab / Root-View-Aufruf ändern:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Der BackStack wird gelöscht. Denken Sie daran, dies aufzurufen, bevor Sie das Stammfragment ändern.

Und fügen Sie Fragmente hinzu:

FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

Beachten Sie das .addToBackStack(null)und das transaction.addkönnte zB mit geändert werden transaction.replace.

Morten Holmgaard
quelle
-1

Dieser Thread war sehr sehr interessant und nützlich.
Vielen Dank an Krishnabhadra für Ihre Erklärung und Ihren Code. Ich verwende Ihren Code und habe ihn ein wenig verbessert, damit die Stapel, das aktuelle Tab usw. von der Änderung der Konfiguration (hauptsächlich rotierend) erhalten bleiben.
Getestet auf einem echten 4.0.4 und 2.3.6 Gerät, nicht auf einem Emulator getestet

Ich ändere diesen Teil des Codes in "AppMainTabActivity.java", der Rest bleibt gleich. Vielleicht fügt Krishnabhadra dies seinem Code hinzu.

Daten wiederherstellen onCreate:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.app_main_tab_fragment_layout);

    /*  
     *  Navigation stacks for each tab gets created..
     *  tab identifier is used as key to get respective stack for each tab
     */

  //if we are recreating this activity...
    if (savedInstanceState!=null) {
         mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack");
         mCurrentTab = savedInstanceState.getString("currentTab");
    }
    else {
    mStacks = new HashMap<String, Stack<Fragment>>();
    mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
    mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

    }

    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    initializeTabs();

  //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab
    mTabHost.setOnTabChangedListener(listener);
}

Speichern Sie die Variablen und legen Sie sie in Bundle:

 //Save variables while recreating
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putSerializable("stack", mStacks);
    outState.putString("currentTab", mCurrentTab);
    //outState.putInt("tabHost",mTabHost);
}

Wenn ein vorheriges CurrentTab vorhanden ist, legen Sie dies fest, andernfalls erstellen Sie ein neues Tab_A:

public void initializeTabs(){
    /* Setup your tab icons and content views.. Nothing special in this..*/
    TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);

    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_a_state_btn));
    mTabHost.addTab(spec);


    spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
    spec.setContent(new TabHost.TabContentFactory() {
        public View createTabContent(String tag) {
            return findViewById(R.id.realtabcontent);
        }
    });
    spec.setIndicator(createTabView(R.drawable.tab_b_state_btn));
    mTabHost.addTab(spec);

//if we have non default Tab as current, change it
    if (mCurrentTab!=null) {
        mTabHost.setCurrentTabByTag(mCurrentTab);
    } else {
        mCurrentTab=AppConstants.TAB_A;
        pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true);
    }
}

Ich hoffe das hilft anderen Menschen.

Sulfkain
quelle
Das ist falsch. Wenn onCreate mit einem Bundle aufgerufen wird, sind diese Fragmente nicht die gleichen, die auf dem Bildschirm angezeigt werden, und Sie verlieren die alten, es sei denn, Sie verwenden setRetainInstance. Und wenn der ActivityManager Ihre Aktivität "speichert", stürzt es ab, da ein Fragment weder serialisierbar noch paketierbar ist, wenn der Benutzer zu Ihrer Aktivität zurückkehrt.
Sergio91pt
-1

Ich würde empfehlen, keinen Backstack zu verwenden, der auf HashMap basiert.> Es gibt viele Fehler im Modus "Aktivitäten nicht beibehalten". Der Status wird nicht korrekt wiederhergestellt, falls Sie sich tief im Fragmentstapel befinden. Und wird auch in verschachtelte Kartenfragmente eingeteilt (mit Ausnahme: Fragment keine Ansicht für ID gefunden). Coz HashMap> nach Hintergrund \ Vordergrund App ist null

Ich optimiere den obigen Code für die Arbeit mit dem Backstack des Fragments

Es ist unteres TabView

Hauptaktivitätsklasse

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.TabHost;
import android.widget.TextView;

import com.strikersoft.nida.R;
import com.strikersoft.nida.abstractActivity.BaseActivity;
import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment;
import com.strikersoft.nida.screens.tags.searchTab.SearchFragment;
import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment;

public class TagsActivity extends BaseActivity {
    public static final String M_CURRENT_TAB = "M_CURRENT_TAB";
    private TabHost mTabHost;
    private String mCurrentTab;

    public static final String TAB_TAGS = "TAB_TAGS";
    public static final String TAB_MAP = "TAB_MAP";
    public static final String TAB_SETTINGS = "TAB_SETTINGS";

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
        getActionBar().hide();
        setContentView(R.layout.tags_activity);

        mTabHost = (TabHost) findViewById(android.R.id.tabhost);

        mTabHost.setup();

        if (savedInstanceState != null) {
            mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB);
            initializeTabs();
            mTabHost.setCurrentTabByTag(mCurrentTab);
            /*
            when resume state it's important to set listener after initializeTabs
            */
            mTabHost.setOnTabChangedListener(listener);
        } else {
            mTabHost.setOnTabChangedListener(listener);
            initializeTabs();
        }
    }

    private View createTabView(final int id, final String text) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView = (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        TextView textView = (TextView) view.findViewById(R.id.tab_text);
        textView.setText(text);
        return view;
    }

    /*
    create 3 tabs with name and image
    and add it to TabHost
     */
    public void initializeTabs() {

        TabHost.TabSpec spec;

        spec = mTabHost.newTabSpec(TAB_TAGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags)));
        mTabHost.addTab(spec);

        spec = mTabHost.newTabSpec(TAB_MAP);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map)));
        mTabHost.addTab(spec);


        spec = mTabHost.newTabSpec(TAB_SETTINGS);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings)));
        mTabHost.addTab(spec);

    }

    /*
    first time listener will be trigered immediatelly after first: mTabHost.addTab(spec);
    for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener
    */
    TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() {
        public void onTabChanged(String tabId) {

            mCurrentTab = tabId;

            if (tabId.equals(TAB_TAGS)) {
                pushFragments(SearchFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_MAP)) {
                pushFragments(MapContainerFragment.getInstance(), false,
                        false, null);
            } else if (tabId.equals(TAB_SETTINGS)) {
                pushFragments(SettingsFragment.getInstance(), false,
                        false, null);
            }

        }
    };

/*
Example of starting nested fragment from another fragment:

Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac());
                TagsActivity tAct = (TagsActivity)getActivity();
                tAct.pushFragments(newFragment, true, true, null);
 */
    public void pushFragments(Fragment fragment,
                              boolean shouldAnimate, boolean shouldAdd, String tag) {
        FragmentManager manager = getFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        if (shouldAnimate) {
            ft.setCustomAnimations(R.animator.fragment_slide_left_enter,
                    R.animator.fragment_slide_left_exit,
                    R.animator.fragment_slide_right_enter,
                    R.animator.fragment_slide_right_exit);
        }
        ft.replace(R.id.realtabcontent, fragment, tag);

        if (shouldAdd) {
            /*
            here you can create named backstack for realize another logic.
            ft.addToBackStack("name of your backstack");
             */
            ft.addToBackStack(null);
        } else {
            /*
            and remove named backstack:
            manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE);
            or remove whole:
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
             */
            manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
        ft.commit();
    }

    /*
    If you want to start this activity from another
     */
    public static void startUrself(Activity context) {
        Intent newActivity = new Intent(context, TagsActivity.class);
        newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(newActivity);
        context.finish();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(M_CURRENT_TAB, mCurrentTab);
        super.onSaveInstanceState(outState);
    }

    @Override
    public void onBackPressed(){
        super.onBackPressed();
    }
}

tags_activity.xml

<

?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>
        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:background="@drawable/bg_main_app_gradient"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>
        <TabWidget
            android:id="@android:id/tabs"
            android:background="#EAE7E1"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>
    </LinearLayout>
</TabHost>

tags_icon.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/tabsLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/bg_tab_gradient"
    android:gravity="center"
    android:orientation="vertical"
    tools:ignore="contentDescription" >

    <ImageView
        android:id="@+id/tab_icon"
        android:layout_marginTop="4dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView 
        android:id="@+id/tab_text"
        android:layout_marginBottom="3dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/tab_text_color"/>

</LinearLayout>

Geben Sie hier die Bildbeschreibung ein

Flinbor
quelle