ActionBar-Titel mit dem Fragment-Back-Stack behandeln?

74

Ich habe eine Stelle, Activityan der ich eine lade, ListFragmentund beim Klicken wird ein Level und eine neue Art von Drilldown ausgeführtListFragment angezeigt, die die ursprüngliche ersetzt (mithilfe der folgenden showFragmentMethode). Dies wird auf den hinteren Stapel gelegt.

Zu Beginn zeigt die Aktivität den Standardtitel in der Aktionsleiste an (dh er wird automatisch basierend auf den Anwendungen festgelegt android:label).

Wenn Sie die Liste für die nächste Ebene in der Hierarchie anzeigen, sollte der Name des angeklickten Elements zum Titel der Aktionsleiste werden.

Beim Drücken Backmöchte ich jedoch, dass der ursprüngliche Standardtitel wiederhergestellt wird. Das FragmentTransactionweiß nichts , daher wird der Titel nicht wiederhergestellt.

Ich habe vage darüber gelesen FragmentBreadCrumbs, aber dies scheint die Verwendung einer benutzerdefinierten Ansicht zu erfordern. Ich verwende ActionBarSherlock und möchte keine eigene benutzerdefinierte Titelansicht haben.

Was ist der beste Weg, dies zu tun? Ist es möglich, ohne viel Boilerplate-Code zu haben und die auf dem Weg angezeigten Titel im Auge zu behalten?


protected void showFragment(Fragment f) {
  FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
  ft.replace(R.id.fragment_container, f);
  ft.addToBackStack(null);
  ft.commit();
}
Christopher Orr
quelle

Antworten:

122

In jedem Fragment und jeder Aktivität ändere ich den Titel so. Auf diese Weise ist der aktive Titel immer korrekt:

@Override
public void onResume() {
    super.onResume();
    // Set title
    getActivity().getActionBar()
        .setTitle(R.string.thetitle);
}

In einigen Fällen wird onResume nicht in Fragmenten aufgerufen. In einigen dieser Fälle können wir verwenden:

public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisibleToUser) {
        // Set title
        getActivity().getActionBar()
            .setTitle(R.string.thetitle);
    }
}
Warpzit
quelle
2
Ich musste nicht besetzen oder die Aktionsleiste bekommen (dh ich rufe einfach an getActivity().setTitle(...)), aber dies ist ein vernünftiger Ansatz. Vielen Dank.
Christopher Orr
9
-1. Dieser Ansatz ist falsch! Gemäß den Richtlinien von Android Design sollte sich der Titel der Aktionsleiste beim Wechseln von Fragmenten mithilfe von Navigation Drawer nur im Callback von Navigation Drawer onClosed ändern. Ihr Ansatz ändert den Titel falsch, bevor die Navigationsleiste geschlossen wird.
Zyamys
6
@zyamys Wenn Sie mit einem besseren Vorschlag kommen, würde ich ihn gerne positiv bewerten. Dies war die beste Antwort, die ich damals finden konnte.
Warpzit
7
Das hat bei mir nicht funktioniert, also habe ich es geändert getActivity().setTitle(R.string.thetitle);und dann hat es funktioniert.
Simeg
4
Dies funktioniert nur, wenn Sie das Fragment während FragmentTransaction ersetzen. Was ist, wenn Sie ein Fragment hinzufügen, anstatt es zu ersetzen?
Kamlesh Karwande
28

Da die ursprüngliche Antwort ziemlich alt ist, könnte dies ebenfalls hilfreich sein. Wie in der Dokumentation angegeben, möchten Sie möglicherweise eine registrieren listener, um die Änderungen des Backstacks im Hosting abzuhören Activity:

getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });

Identifizieren Sie dann die Situation in der Rückrufmethode und legen Sie einen richtigen Titel fest, ohne auf den ActionBarvon zuzugreifenFragment .

Dies ist eine elegantere Lösung, da der Fragmentnicht über die ActionBarExistenz Bescheid wissen muss und Activitynormalerweise der Ort ist, an dem der Backstack verwaltet wird. Daher scheint es angemessener zu sein, ihn dort zu handhaben. Fragmentsollte zu jeder Zeit nur durch seinen eigenen Inhalt betrachtet werden, nicht durch die Umgebung.

Mehr zum Thema in der Dokumentation .

Maciej Pigulski
quelle
1
Die andere Antwort ist alt, aber ich finde sie immer noch die einfachste und pragmatischste Methode, um sicherzustellen, dass der Titel immer korrekt ist. Wie in meinem Kommentar erwähnt, müssen Sie nicht direkt auf die ActionBar zugreifen. Die Lösung hier erfordert mehr Code, hilft aber auch nicht für Ihr anfängliches Fragment, das normalerweise nicht zum Backstack hinzugefügt wird.
Christopher Orr
1
Es ist auch nichts Falsches daran, von Fragmenten aus auf die Aktionsleiste zuzugreifen. Google bietet auch eine API zum Ändern von Menüelementen in Fragmenten.
Warpzit
2
Nach einiger Zeit kam ich zu dem Schluss, dass beide Lösungen in Ordnung sind. Es hängt nur von Ihrem Anwendungsfall ab. Manchmal ist es praktikabler, onResume und manchmal einen Fragment Manager-Listener zu verwenden. Alles in allem ist es also gut, Ihre Werkzeuge zu kennen, damit Sie sie angemessen auf Ihre Bedürfnisse anwenden können.
Maciej Pigulski
2
@ Maciej Pigulski Und dann? Wir wissen nichts über den Kontext in diesem Rückruf.
Fralbo
2
@caBBAlainB, der Kontext wird im FragmentManager der Hosting-Aktivität gespeichert, sodass Sie den Titel bestimmen müssen, indem Sie einige umständliche Überprüfungen des Managers durchführen - daher ist er nicht großartig. Heute kann ich mich nicht erinnern, was ich hier argumentiert habe. Ich denke, diese Idee stammt hauptsächlich aus dem, was in Android-Dokumenten geschrieben ist, die in dieser Antwort verlinkt sind (Absatz über dem Listener-Snippet). Heute würde ich lieber die akzeptierte Antwort verwenden.
Maciej Pigulski
11

Lassen Sie die Controlling-Aktivität die gesamte Arbeit wie folgt ausführen:

Achten Sie auf Backstack-Ereignisse (in onCreate () der Aktivität):

// Change the title back when the fragment is changed
    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            Fragment fragment = getFragment();
            setTitleFromFragment(fragment);
        }
    });

Holen Sie sich das aktuelle Fragment aus dem Container:

/**
 * Returns the currently displayed fragment.
 * @return
 *      Fragment or null.
 */
private Fragment getFragment() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
    return fragment;
}

Legen Sie das Fragment in der Inhaltsansicht fest:

private void setFragment(Fragment fragment, boolean addToBackStack) {
    // Set the activity title
    setTitleFromFragment(fragment);
    .
    .
    .
}
Gemeiner Mann
quelle
Dies bedeutet nicht, dass die Aktivität die ganze Arbeit erledigt, wenn Sie in jedem einzelnen eine Schnittstelle implementiert haben Fragment, setTitleFromFragment()damit der Titel abgerufen werden kann. Nichts davon scheint in den Teil meiner Frage zu passen, der "ohne viel Boilerplate-Code" lautet.
Christopher Orr
In meiner Lösung wird keine Schnittstelle erwähnt. Ihre Aktivität kann die Instanz des Fragments überprüfen und den Titel basierend darauf festlegen, sodass Ihre Aktivität die gesamte Arbeit erledigen kann. Alle diese Methoden werden innerhalb der Aktivität definiert.
Meanman
Ja, das klingt aus Sicht des Designs / der Trennung von Bedenken / der Wiederverwendbarkeit wahrscheinlich noch schlimmer. Außerdem wird dadurch der Titel für jedes dem Backstack hinzugefügte Fragment redundant zweimal geändert, da die setTitleFromFragment-Methode in setFragment bedingungslos aufgerufen wird. Wie auch immer, danke, aber ich bleibe bei der pragmatischen und einfach akzeptierten Antwort :)
Christopher Orr
3
Wie klingt dies "wahrscheinlich noch schlechter unter dem Gesichtspunkt des Designs / der Trennung von Bedenken / der Wiederverwendbarkeit"? Bitte stützen Sie es mit einem gültigen Argument? Sie lassen die Aktivität ein "Controller" sein und legen basierend auf ihrem Inhalt einen eigenen Titel fest. Was ist, wenn ich ein Fragment an mehreren Stellen verwenden oder eine Aktivität mit mehreren Fragmenten ausführen möchte und alle versuchen, den Titel festzulegen?
Meanman
Sehr hilfreiche Antwort
VVB
7

Warpzit ist richtig. Dies löst auch das Titelproblem, wenn die Ausrichtung des Geräts geändert wird. Wenn Sie Support v7 für die Aktionsleiste verwenden, können Sie die Aktionsleiste auch aus einem Fragment wie dem folgenden abrufen:

@Override
public void onResume() {
    super.onResume();
    ((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}
Jemshit Iskenderov
quelle
+1 für Support v7. Ich denke, wann immer eine Android-Antwort gegeben wird, sollte die antwortende Person die Unterstützung berücksichtigen. Um eine Geräteabdeckung von 80% zu erreichen, MÜSSEN Sie ab sofort zu API 16 zurückkehren. In den meisten Fällen ist die Verwendung der kompatiblen Bibliotheken erforderlich.
Akousmata
1
Ich habe Ihren Code in public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)Methode gesetzt und gut funktioniert.
GFPF
7

Es ist am besten, das Betriebssystem so viel wie möglich arbeiten zu lassen. Angenommen, jedes Fragment wird mit .addToBackStack ("title") richtig benannt, dann können Sie onBackPressed wie folgt überschreiben, um das gewünschte Verhalten zu erzielen:

// this example uses the AppCompat support library
// and works for dynamic fragment titles
@Override
public void onBackPressed() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    int count = fragmentManager.getBackStackEntryCount();
    if (count <= 1) {
        finish();
    }
    else {
        String title = fragmentManager.getBackStackEntryAt(count-2).getName();
        if (count == 2) {
            // here I am using a NavigationDrawer and open it when transitioning to the initial fragment
            // a second back-press will result in finish() being called above.
            mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
        }
        super.onBackPressed();
        Log.v(TAG, "onBackPressed - title="+title);
        getSupportActionBar().setTitle(title);
    }
}
Lee Hounshell
quelle
5

Ich verwende eine ähnliche Lösung wie Lee , ersetze aber onBackStackChanged()stattdessen die Methode.

Zuerst habe ich den Fragmentnamen festgelegt, wenn ich die Transaktion zum Backstack hinzufüge.

getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_content, fragment)
                .addToBackStack(fragmentTitle)
                .commit();

Dann überschreibe ich die onBackStackChanged()Methode und rufe setTitle()mit dem letzten Backstack-Eintragsnamen auf.

@Override
public void onBackStackChanged() {
    int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
    FragmentManager.BackStackEntry lastBackStackEntry =
            getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);

    setTitle(lastBackStackEntry.getName());
}
Gnzlt
quelle
3
Das Problem bei der Verwendung des Backstack-Eintragsnamens besteht darin, dass Konfigurationsänderungen nicht verarbeitet werden. Eine interessante und geeignete Methode zur Behebung dieses Problems wäre die Verwendung von setBreadCrumbTitle (int res) zum Speichern einer Titelressourcen-ID, die Sie im onBackStackChanged zum Festlegen des Titels verwenden können.
David Liu
4

Verwenden Sie die Fragmentmethode:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)

Es wird bei jedem Fragment-Erscheinungsbild aufgerufen, onResume jedoch nicht.

Jurijs Turjanskis
quelle
ja .. keine der Antworten sind nicht gespeichert mein Problem .. das funktioniert gut. Es mag nicht professionell sein .. aber es ist ein bisschen hacky.
Ranjith Kumar
@Bevor damit dies funktioniert, benötigen Sie ein Optionsmenü. Sie können es für ein Fragment aktivieren, indem Sie "setHasOptionsMenu (true);" in der OnCreateView (...) -Methode Ihres Fragments.
New2HTML
1

Der beste Ansatz ist die Verwendung der von Android bereitgestellten Interface OnBackStackChangedListener-Methode onBackStackChanged ().

Nehmen wir an, wir haben eine Navigationsschublade mit 4 Optionen, zu denen der Benutzer navigieren kann. In diesem Fall haben wir 4 Fragmente. Sehen wir uns zuerst den Code an und dann erkläre ich die Arbeitsweise.

    private int mPreviousBackStackCount = 0;
    private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
    Stack<String> mFragPositionTitleDisplayed;

    public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ....
    ....
    ....
    getSupportFragmentManager().addOnBackStackChangedListener(this);
    mFragPositionTitleDisplayed = new Stack<>();
}

public void displayFragment() {
    Fragment fragment = null;
    String title = getResources().getString(R.string.app_name);
    switch (position) {
        case 0:
            fragment = new Fragment1();
            title = title_name[position];
            break;
        case 1:
            fragment = new Fragment2();
            title = title_name[position];
            break;
        case 2:
            fragment = new Fragment3();
            title = title_name[position];
            break;
        case 3:
            fragment = new Fragment4();
            title = title_name[position];
            break;
        default:
            break;
    }
    if (fragment != null) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.container_body, fragment)
                .addToBackStack(null)
                .commit();
        getSupportActionBar().setTitle(title);
    }
}

@Override
public void onBackStackChanged() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    if(mPreviousBackStackCount >= backStackEntryCount) {
        mFragPositionTitleDisplayed.pop();
        if (backStackEntryCount == 0)
            getSupportActionBar().setTitle(R.string.app_name);
        else if (backStackEntryCount > 0) {
            getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
        }
        mPreviousBackStackCount--;
    }
    else{
        mFragPositionTitleDisplayed.push(title_name[position]);
        mPreviousBackStackCount++;
    }

}   

In dem gezeigten Code haben wir die displayFragment () -Methode. Hier zeige ich das Fragment auf der Grundlage der in der Navigationsleiste ausgewählten Option an. Die variable Position entspricht der Position des Elements, auf das in der ListView oder RecyclerView in der Navigationsleiste geklickt wurde. Ich habe den Titel der Aktionsleiste mit getSupportActionBar.setTitle (title) entsprechend festgelegt, wobei der Titel den entsprechenden Titelnamen speichert.

Immer wenn wir auf das Element aus der Navigationsleiste klicken, wird ein Fragment angezeigt, abhängig von dem Element, auf das der Benutzer geklickt hat. Auf der Back-End-Seite wird dieses Fragment jedoch zum Backstack hinzugefügt und die Methode onBackStachChanged () wird getroffen. Ich habe eine Variable mPreviousBackStackCount erstellt und auf 0 initialisiert. Außerdem habe ich einen zusätzlichen Stapel erstellt, in dem die Titelnamen der Aktionsleiste gespeichert werden. Immer wenn ich dem Backstack ein neues Fragment hinzufüge, füge ich meinem erstellten Stack den entsprechenden Titelnamen hinzu. Auf der anderen Seite wird immer dann, wenn ich die Zurück-Taste drücke, onBackStackChanged () aufgerufen und der Nachname aus meinem Stapel entfernt und der Titel auf den Namen gesetzt, der von der peek () -Methode des Stapels abgeleitet wurde.

Beispiel:

Nehmen wir an, unser Android-Backstack ist leer:

Drücken Sie Auswahl 1 aus der Navigationsleiste: onBackStachChanged () wird aufgerufen und das Fragment 1 wird zum Android-Backstack hinzugefügt, backStackEntryCount wird auf 1 gesetzt und Frag1 wird auf meinen Stapel verschoben und die Größe von mFragPositionTitleDisplayed wird 1.

Drücken Sie Auswahl 2 aus der Navigationsleiste: onBackStachChanged () wird aufgerufen und das Fragment 2 wird zum Android-Backstack hinzugefügt, backStackEntryCount wird auf 2 gesetzt und Frag2 wird auf meinen Stapel verschoben und die Größe von mFragPositionTitleDisplayed wird 2.

Jetzt haben wir 2 Elemente sowohl im Android-Stack als auch in meinem Stack. Wenn Sie die Zurück-Taste drücken, wird onBackStackChanged () aufgerufen und der Wert von backStackEntryCount ist 1. Der Code gibt den if-Teil ein und löscht den letzten Eintrag aus meinem Stapel. Der Android-Backstack hat also nur 1 Fragment - "Fragment 1" und mein Stack hat nur 1 Titel - "Frag1". Jetzt schaue ich einfach () den Titel von meinem Stapel und setze die Aktionsleiste auf diesen Titel.

Denken Sie daran: Um den Titel des Aktionsschlägers festzulegen, verwenden Sie peek () und nicht pop (). Andernfalls stürzt Ihre Anwendung ab, wenn Sie mehr als 2 Fragmente öffnen und versuchen, durch Drücken der Zurück-Taste zurückzukehren.

Abhishek
quelle
Dies ist eine lächerliche Menge an Code (insbesondere im Vergleich zur akzeptierten Antwort) und ignoriert meine Anforderungen in der Frage, dh dies ohne eine Menge Code auf dem Boilerplate zu implementieren und ohne die angezeigten Titel im Auge behalten zu müssen.
Christopher Orr
Ja, es ist dein Problem, wenn du keinen übermäßigen Code willst. Sie müssen irgendwo Kompromisse eingehen. Darüber hinaus ist OnBackStackChanged () der einzige wichtige Teil des Codes.
Abhishek
1

Sie können mit onKeyDown lösen! Ich habe ein bool mainisopen = true <- MainFragment ist sichtbar anderes Fragment mainisopen = false

und hier ist mein Code:

public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
        mainisopen = true;
        HomeFrag fragment = new HomeFrag();
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragmet_cont, fragment);
        fragmentTransaction.commit();
        navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
        navigationView.setNavigationItemSelectedListener(this);
        this.setTitle("Digi - Home"); //Here set the Title back
        return true;
    } else {
        if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Wollen sie die App schliessen!");
            builder.setCancelable(true);

            builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(1);
                }
            });

            builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
                }
            });

            AlertDialog dialog = builder.create();
            dialog.show();

            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

}
BitDEVil2K16
quelle
0

Wie hier beschrieben , besteht meine Lösung darin, diesen Code zur MainActivity onCreate-Methode () hinzuzufügen: und den Titel der Aktionsleiste zu ändern

FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
        currentFragment.onResume();
    }
});

und Ändern des Titels der Aktionsleiste in der onResume () -Methode des Fragments

@Override
public void onResume() {
    super.onResume();
    AppCompatActivity activity = (AppCompatActivity) getActivity();
    ActionBar actionBar = activity.getSupportActionBar();
    if(actionBar!=null) {
        actionBar.setTitle("Fragment Title");
        actionBar.setSubtitle("Subtitle");
    }

}
mDonmez
quelle
-3

Um den Titel der Aktionsleiste auf der Rückseite zu aktualisieren, drücken Sie. Einfach gesagt

getActivity.setTitle ("title")

innerhalb der onCreateView-Methode.

Ashish Kumawat
quelle
2
onCreateViewwird nur aufgerufen, wenn ... eine Ansicht erstellt wird. Wenn Sie Zurück drücken und sich die vorherige Aktivität / das vorherige Fragment noch auf dem Stapel befindet, wird diese Methode nicht aufgerufen.
Christopher Orr