Przełączanie między obrazem szuflady nawigacji systemu Android a kursorem w górę podczas używania fragmentów

177

Podczas korzystania z szuflady nawigacji programiści Androida zalecają, aby na pasku akcji „tylko te ekrany, które są reprezentowane w szufladzie nawigacji, miały faktycznie obraz szuflady nawigacji” oraz aby „wszystkie inne ekrany miały tradycyjny up-carat”.

Szczegółowe informacje można znaleźć tutaj: http://youtu.be/F5COhlbpIbY

Używam jednego działania do kontrolowania wielu poziomów fragmentów i mogę sprawić, by obraz szuflady nawigacji był wyświetlany i działał na wszystkich poziomach.

Podczas tworzenia fragmentów niższego poziomu mogę wywołać, ActionBarDrawerToggle setDrawerIndicatorEnabled(false)aby ukryć obraz szuflady nawigacji i wyświetlić kursor Up

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout, 
lowFrag, "lowerFrag").addToBackStack(null).commit();

Problem, który mam, polega na tym, że przechodzę z powrotem do fragmentów najwyższego poziomu, które nadal pokazuje karat Up zamiast oryginalnego obrazu szuflady nawigacji. Jakieś sugestie, jak „odświeżyć” ActionBar we fragmentach najwyższego poziomu, aby ponownie wyświetlić obraz szuflady nawigacji?


Rozwiązanie

Sugestia Toma zadziałała dla mnie. Oto co zrobiłem:

Główna aktywność

Ta aktywność kontroluje wszystkie fragmenty w aplikacji.

Przygotowując nowe fragmenty do zastąpienia innych ustawiam DrawerToggle w setDrawerIndicatorEnabled(false)następujący sposób:

LowerLevelFragment lowFrag = new LowerLevelFragment();

//disable the toggle menu and show up carat
theDrawerToggle.setDrawerIndicatorEnabled(false);
getSupportFragmentManager().beginTransaction().replace(R.id.frag_layout,   
lowFrag).addToBackStack(null).commit();

Następnie, zastępując onBackPressed, cofnąłem powyższe, ustawiając DrawerToggle setDrawerIndicatorEnabled(true)tak, aby :

@Override
public void onBackPressed() {
    super.onBackPressed();
    // turn on the Navigation Drawer image; 
    // this is called in the LowerLevelFragments
    setDrawerIndicatorEnabled(true)
}

W LowerLevelFragments

We fragmentach zmodyfikowałem onCreatei onOptionsItemSelectedtak:

W onCreatedodany setHasOptionsMenu(true)w celu umożliwienia konfiguracji menu opcji. Ustaw również, setDisplayHomeAsUpEnabled(true)aby włączyć < na pasku akcji:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // needed to indicate that the fragment would 
    // like to add items to the Options Menu        
    setHasOptionsMenu(true);    
    // update the actionbar to show the up carat/affordance 
    getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
}

Następnie po onOptionsItemSelectedkażdym naciśnięciu < wywołuje onBackPressed()z działania, aby przejść o jeden poziom w górę w hierarchii i wyświetlić obraz szuflady nawigacji:

@Override
public boolean onOptionsItemSelected(MenuItem item) {   
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            getActivity().onBackPressed();
            return true;
         
    }
EvilAsh
źródło
2
Również w metodzie onBackPressed () możesz sprawdzić, ile wpisów znajduje się na stosie wstecznym za pomocą metody getFragmentManager (). GetBackStackEntryCount () i włączyć wskaźnik szuflady tylko wtedy, gdy wynik wynosi 0. W takim przypadku nie jest konieczne włączanie homeAsUpIndicator w każdym LowerLevelFragments.
pvshnik
2
To jest bardzo przydatne! Powinieneś przenieść część swojego posta zawierającą „rozwiązanie” i uczynić ją rzeczywistą „odpowiedzią”. Za pozytywne głosy dostaniesz więcej punktów i jest to w końcu odpowiedź
Oleksiy
Czemu zastępując fragment tutaj: .replace(R.id.frag_layout. Jeśli jest to jeszcze jeden poziom hierarchii, spodziewałbym się, że zostaniesz .addna zapleczu.
JJD
5
Stary, jak odnosisz się do theDrawerToggle.setDrawerIndicatorEnabled(false);wnętrza fragmentu? Myślę, że jest to zadeklarowane w pliku klasy Main Activity. Nie mogę znaleźć sposobu, żeby to odnieść. Jakieś wskazówki?
Skynet
1
Korzystając z paska narzędzi, musiałem zmienić opcje wyświetlania, aby w międzyczasie nie używać domu. W przeciwnym razie setDisplayOptions()metoda wewnątrz ToolbarWidgetWrapper(wewnętrznego pakietu android.support.v7.internal.widget) nie utworzyłaby ikony podczas ponownego wprowadzania tego samego fragmentu. Zostawiam to tutaj na wypadek, gdyby inni również napotkali ten problem.
Wolfram Rittmeyer

Odpowiedzi:

29

Napisałeś, że aby zaimplementować fragmenty niższego poziomu, zastępujesz istniejący fragment, w przeciwieństwie do implementacji fragmentu niższego poziomu w nowej aktywności.

Pomyślałbym, że musisz wtedy ręcznie zaimplementować funkcję wstecz: gdy użytkownik nacisnął z powrotem, masz kod, który wyskakuje ze stosu (np. W Activity::onBackPressednadpisaniu). Więc gdziekolwiek to zrobisz, możesz odwrócić setDrawerIndicatorEnabled.

Tomek
źródło
Dzięki Tom, zadziałało! Zaktualizowałem oryginalny post za pomocą rozwiązania, którego użyłem.
EvilAsh
6
Jak odwołać się do theDrawerToggle we fragmencie niższego poziomu? Zostało to zdefiniowane w głównym działaniu, nie mogę tego obejść!
Skynet
1
Możesz uzyskać aktywność z fragmentu. Po prostu dodaj getter do głównego działania, aby uzyskać dostęp do przełącznika szuflady.
Raphael Royer-Rivard
83

To proste jak 1-2-3.

Jeśli chcesz osiągnąć:

1) Wskaźnik szuflady - gdy w tylnym stosie nie ma żadnych fragmentów lub szuflada jest otwarta

2) Strzałka - gdy niektóre fragmenty są w stosie tylnym

private FragmentManager.OnBackStackChangedListener
        mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        syncActionBarArrowState();
    }
};

@Override
protected void onCreate(Bundle savedInstanceState) {
    getSupportActionBar().setDisplayShowHomeEnabled(true);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    mDrawerToggle = new ActionBarDrawerToggle(
            this,             
            mDrawerLayout,  
            R.drawable.ic_navigation_drawer, 
            0, 
            0  
    ) {

        public void onDrawerClosed(View view) {
            syncActionBarArrowState();
        }

        public void onDrawerOpened(View drawerView) {
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    };

    mDrawerLayout.setDrawerListener(mDrawerToggle);
    getSupportFragmentManager().addOnBackStackChangedListener(mOnBackStackChangedListener);
}

@Override
protected void onDestroy() {
    getSupportFragmentManager().removeOnBackStackChangedListener(mOnBackStackChangedListener);
    super.onDestroy();
}

private void syncActionBarArrowState() {
    int backStackEntryCount = 
        getSupportFragmentManager().getBackStackEntryCount();
    mDrawerToggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

3) Oba wskaźniki działają zgodnie z ich kształtem

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (mDrawerToggle.isDrawerIndicatorEnabled() && 
        mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    } else if (item.getItemId() == android.R.id.home && 
               getSupportFragmentManager().popBackStackImmediate()) {
        return true;
    } else {
        return super.onOptionsItemSelected(item);
    }
}

PS Zobacz Tworzenie szuflady nawigacji w Android Developers, aby poznać inne wskazówki dotyczące zachowania wskaźnika 3-liniowego.

riwnodennyk
źródło
3
Skończyło się na czymś podobnym do twojego. Myślę, że to rozwiązanie (użycie BackStackChangedListener do przełączania się między tym, co jest pokazane) jest najbardziej eleganckie. Jednak mam dwie zmiany: 1) Nie wywołuję / nie zmieniam wskaźnika szuflady w wywołaniach onDrawerClosed / onDrawerOpened - nie jest to potrzebne, i 2) Pozwalam fragmentom samodzielnie obsługiwać nawigację w górę, tworząc AbstractFragment, które wszystkie dziedziczą implementuje onOptionsItemSelected (..) i zawsze wywołuje metodę setHasOptionsMenu (true);
Espen Riskedal
2
Yout powinien również zablokować gest machnięcia dla szuflady nawigacji w trybie strzałek: mDrawerLayout.setDrawerLockMode (DrawerLayout.LOCK_MODE_LOCKED_CLOSED); i włącz go ponownie w trybie wskaźnika szuflady
artkoenig
5
Skończyło się na tym, że zrobiłem to wszystko w moim fragmencie NavigationDrawer. Przy okazji, działa jak urok, dzięki.
mroźny cudowny
1
setActionBarArrowDependingOnFragmentsBackStack()... co za długa nazwa: P
S.Thiongane,
2
Nie pokazuje mi to przycisku w górę, kiedy przechodzę z fragmentu A do B i dodaję A do backstacka. :(
aandis
14

Użyłem następnej rzeczy:

getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                if(getSupportFragmentManager().getBackStackEntryCount() > 0){
                    mDrawerToggle.setDrawerIndicatorEnabled(false);
                    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
                }
                else {
                    getSupportActionBar().setDisplayHomeAsUpEnabled(false);
                    mDrawerToggle.setDrawerIndicatorEnabled(true);
                }
            }
        });
Yuriy Sych
źródło
1
BARDZO DZIĘKUJĘ to jedyny, który faktycznie zadziałał. Kiedy to dodam, drawerToggle.setToolbarNavigationClickListener(ten słuchacz jest wywoływany po kliknięciu strzałki
EpicPandaForce
Dzięki @Yuriy, pomogło mi to rozwiązać mój problem
Muhammad Shoaib Murtaza
12

Jeśli przycisk paska akcji w górę nie działa, nie zapomnij dodać listenera:

// Navigation back icon listener
mDrawerToggle.setToolbarNavigationClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            onBackPressed();
        }
});

Mam problem z implementacją nawigacji w szufladzie za pomocą przycisku Home, wszystko działało oprócz przycisku akcji.

Brus
źródło
10

Spróbuj obsłużyć wybór elementu Home w MainActivity w zależności od stanu DrawerToggle. W ten sposób nie musisz dodawać tego samego kodu do każdego fragmentu.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            onBackPressed();
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}
dzeikei
źródło
+1 zgrabne rozwiązanie. Aby Twoja odpowiedź była w pełni użyteczna, powinieneś dodać czek na stosie. Gdy jest pusty, automatycznie ustawia wskaźnik szuflady na wartość true.
HpTerm
@HpTerm Obsługuję backstack, onBackPressed()ponieważ chciałem mieć takie samo zachowanie dla obu.
dzeikei
6

ZAGRYŹĆ

Rozwiązanie podane przez @dzeikei jest zgrabne, ale można je rozszerzyć, używając fragmentów, aby automatycznie obsługiwać cofanie wskaźnika szuflady, gdy backstack jest pusty.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Only handle with DrawerToggle if the drawer indicator is enabled.
    if (mDrawerToggle.isDrawerIndicatorEnabled() &&
            mDrawerToggle.onOptionsItemSelected(item)) {
        return true;
    }
    // Handle action buttons
    switch (item.getItemId()) {
        // Handle home button in non-drawer mode
        case android.R.id.home:
            // Use getSupportFragmentManager() to support older devices
            FragmentManager fragmentManager = getFragmentManager();
            fragmentManager.popBackStack();
            // Make sure transactions are finished before reading backstack count
            fragmentManager.executePendingTransactions();
            if (fragmentManager.getBackStackEntryCount() < 1){
                mDrawerToggle.setDrawerIndicatorEnabled(true);  
            }
            return true;

        default:
            return super.onOptionsItemSelected(item);
    }
}

EDYTOWAĆ

Na pytanie @JJD.

Fragmenty są przechowywane / zarządzane w działaniu. Powyższy kod jest zapisywany raz w tej czynności, ale obsługuje tylko kursor do góry dla onOptionsItemSelected.

W jednej z moich aplikacji musiałem również obsłużyć zachowanie kursora w górę po naciśnięciu przycisku Wstecz. Można to rozwiązać, zastępując onBackPressed.

@Override
public void onBackPressed() {
    // Use getSupportFragmentManager() to support older devices
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.executePendingTransactions();
    if (fragmentManager.getBackStackEntryCount() < 1){
        super.onBackPressed();
    } else {
        fragmentManager.executePendingTransactions();
        fragmentManager.popBackStack();
        fragmentManager.executePendingTransactions();
        if (fragmentManager.getBackStackEntryCount() < 1){
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        }
    }
};

Zwróć uwagę na powielanie kodu między onOptionsItemSelectedi, onBackPressedktórego można uniknąć, tworząc metodę i wywołując ją w obu miejscach.

Zauważ też, że dodaję jeszcze dwa razy, executePendingTransactionsco w moim przypadku było wymagane, albo czasami miałem dziwne zachowanie się opiekuna.

HpTerm
źródło
Czy to jest cały kod, który muszę dodać, aby zaimplementować zachowanie Up Caret, czy odnosisz się do jednego z pozostałych postów? Proszę to wyjaśnić.
JJD
Dzięki za edycję. Właściwie utrzymuję się mDrawerTogglew NavigationDrawerFragmentklasie. Aby go uruchomić muszę również przełączanie stanu przycisk Home / wskaźnika - patrz: NavigationDrawerFragment#toggleDrawerIndicator. Ponadto nie jestem pewien, czy potrzebujesz wstępnego zameldowania onOptionsItemSelected: odkomentowałem to. - Uproszczony przykład
JJD
Poprawiona implementacja : Masz rację co do wstępnego odprawy onOptionsItemSelected. Gwarantuje to, że szuflada nawigacji nadal otwiera się w hierarchii najwyższego poziomu. Jednak przeniosłem kod do NavigationDrawerFragment#onOptionsItemSelected. Pomaga mi to nie narażać się mDrawerTogglena działanie MainActivity.
JJD
@JJD Przypomniało mi się, jak trochę się męczyłem, mając wszystko, co działało na każdym poziomie hierarchii. Tak długo, jak to działa dla ciebie, to w porządku. Oczywiście, jak mówisz, możesz przenieść kod w inne miejsce, aby go nie ujawniać.
HpTerm
2

Stworzyłem interfejs dla działań hostingowych, aby zaktualizować stan widoku menu hamburgera. W przypadku fragmentów najwyższego poziomu ustawiam przełącznik na truei dla fragmentów, dla których chcę wyświetlić strzałkę w górę <, ustawiam przełącznik na false.

public class SomeFragment extends Fragment {

    public interface OnFragmentInteractionListener {
        public void showDrawerToggle(boolean showDrawerToggle);
    }

    private OnFragmentInteractionListener mListener;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            this.mListener = (OnFragmentInteractionListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mListener.showDrawerToggle(false);
    }
}

Następnie w mojej aktywności ...

public class MainActivity extends Activity implements SomeFragment.OnFragmentInteractionListener {

    private ActionBarDrawerToggle mDrawerToggle;

    public void showDrawerToggle(boolean showDrawerIndicator) {
        mDrawerToggle.setDrawerIndicatorEnabled(showDrawerIndicator);
    }

}
Bill Mote
źródło
2

Ta odpowiedź działała, ale był z nią mały problem. Nie getSupportActionBar().setDisplayHomeAsUpEnabled(false)został wywołany jawnie i powodował, że ikona szuflady była ukrywana, nawet jeśli nie było żadnych elementów w tle, więc zmiana setActionBarArrowDependingOnFragmentsBackStack()metody działała dla mnie.

private void setActionBarArrowDependingOnFragmentsBackStack() {
        int backStackEntryCount = getSupportFragmentManager()
                .getBackStackEntryCount();
        // If there are no items in the back stack
        if (backStackEntryCount == 0) {
            // Please make sure that UP CARAT is Hidden otherwise Drawer icon
            // wont display
            getSupportActionBar().setDisplayHomeAsUpEnabled(false);
            // Display the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(true);
        } else {
            // Show the Up carat
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);
            // Hide the Drawer Icon
            mDrawerToggle.setDrawerIndicatorEnabled(false);
        }

    }
Sheraz Ahmad Khilji
źródło
w moim przypadku sztywnym rozwiązaniem było użycie tylko actionBarDrawerToggle.setDrawerIndicatorEnabled (getSupportFragmentManager (). getBackStackEntryCount () <0);
Gorets,
1

Logika jest jasna. Pokaż przycisk wstecz, jeśli stos fragmentów jest czysty. Pokaż materiałową animację hamburgera, jeśli stos fragmentów nie jest czysty.

getSupportFragmentManager().addOnBackStackChangedListener(
    new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            syncActionBarArrowState();
        }
    }
);


private void syncActionBarArrowState() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    mNavigationDrawerFragment.setDrawerIndicatorEnabled(backStackEntryCount == 0);
}

//add these in Your NavigationDrawer fragment class

public void setDrawerIndicatorEnabled(boolean flag){
    ActionBar actionBar = getActionBar();
    if (!flag) {
        mDrawerToggle.setDrawerIndicatorEnabled(false);
        actionBar.setDisplayHomeAsUpEnabled(true);
        mDrawerToggle.setHomeAsUpIndicator(getColoredArrow());
    } else {
        mDrawerToggle.setDrawerIndicatorEnabled(true);
    }
    mDrawerToggle.syncState();
    getActivity().supportInvalidateOptionsMenu();
}

//download back button from this(https://www.google.com/design/icons/) website and add to your project

private Drawable getColoredArrow() {
    Drawable arrowDrawable = ContextCompat.getDrawable(getActivity(), R.drawable.ic_arrow_back_black_24dp);
    Drawable wrapped = DrawableCompat.wrap(arrowDrawable);

    if (arrowDrawable != null && wrapped != null) {
        // This should avoid tinting all the arrows
        arrowDrawable.mutate();
        DrawableCompat.setTint(wrapped, Color.GRAY);
    }
    return wrapped;
}
kml_ckr
źródło
1

Jeśli spojrzysz na aplikację GMAIL i podejdziesz tutaj, aby wyszukać ikonę Carret / Afordance ...

Chciałbym cię o to prosić, żadna z powyższych odpowiedzi nie była jasna. udało mi się zmodyfikować zaakceptowaną odpowiedź.

  • NavigationDrawer -> Listview zawiera podfragmenty.


  • podfragmenty zostaną wymienione w ten sposób

  • firstFragment == pozycja 0 ---> to będzie miało podfragmenty -> fragment

  • secondFragment
  • trzeci fragment i tak dalej ...

W firstFragment masz inny fragment.

Wywołaj to na DrawerActivity

getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            if (getFragmentManager().getBackStackEntryCount() > 0) {
                mDrawerToggle.setDrawerIndicatorEnabled(false);
            } else {
                mDrawerToggle.setDrawerIndicatorEnabled(true);
            }
        }
    });

i we fragmentach

    setHasOptionsMenu(true);    

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Get item selected and deal with it
    switch (item.getItemId()) {
        case android.R.id.home:
            //called when the up affordance/carat in actionbar is pressed
            activity.onBackPressed();
            return true;
    }
    return false;
}

W metodzie działania OnBackPressed Drawer ustaw przełącznik szuflady na wartość true, aby ponownie włączyć ikonę listy nawigacji.

Dzięki, Pusp

EngineSense
źródło
0

IMO, użycie onNavigateUp () (jak pokazano tutaj ) w rozwiązaniu riwnodennyk lub Toma jest czystsze i wydaje się działać lepiej. Po prostu zastąp kod onOptionsItemSelected tym:

@Override
public boolean onSupportNavigateUp() {
    if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
        // handle up navigation
        return true;
    } else {
        return super.onSupportNavigateUp();
    }
}
0101100101
źródło