Jak zaimplementować onBackPressed () we fragmentach?


459

Czy istnieje sposób, w jaki możemy wdrożyć onBackPressed()w Fragmentie Androida podobny do sposobu, w jaki wdrażamy w Aktywności Androida?

Ponieważ cykl życia fragmentu nie ma onBackPressed(). Czy jest jakaś inna alternatywna metoda przesadzania onBackPressed()w fragmentach Androida 3.0?


13
IMHO, fragment nie powinien wiedzieć ani przejmować się przyciskiem BACK. Aktywność może obchodzić przycisk WSTECZ, chociaż fragmenty są zwykle obsługiwane przez FragmentManager. Ale ponieważ fragment nie wie o swoim większym środowisku (np. Czy jest jednym z kilku w działaniu), nie jest naprawdę bezpieczne, aby fragment próbował ustalić, jaka jest właściwa funkcja POWRÓT.
CommonsWare

61
Co powiesz na to, kiedy cały fragment wyświetla WebView i chcesz, aby WebView „wrócił” do poprzedniej strony po naciśnięciu przycisku Wstecz?
mharper

Odpowiedź Michaela Herbiga była dla mnie idealna. Ale dla tych, którzy nie mogą użyć getSupportFragmentManager (). Zamiast tego użyj metody getFragmentManager ().
Dantalian

Odpowiedź Dmitrija Zaitseva na [Podobne pytanie] [1] działa dobrze. [1]: stackoverflow.com/a/13450668/1372866
ashakirov

6
aby odpowiedzieć mharper, możesz zrobić coś takiego jak webview.setOnKeyListener (nowy OnKeyListener () {@Override public boolean onKey (View v, int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack ()) {webview.goBack (); return true;} return false;}});
Omid Aminiva

Odpowiedzi:


307

Rozwiązałem w ten sposób przesłonięcie onBackPressedw działaniu. Wszystkie FragmentTransactionaddToBackStackprzed zatwierdzeniem:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

    if (count == 0) {
        super.onBackPressed();
        //additional code
    } else {
        getSupportFragmentManager().popBackStack();
    }

}


4
count == 1 pozwoli zamknąć pierwszy fragment po naciśnięciu jednego przycisku wstecz. Ale poza tym najlepsze rozwiązanie
Kunalxigxag,

2
Jeśli korzystasz z biblioteki obsługi v7, a Twoje działanie obejmuje FragmentActivity (lub podklasę, taką jak AppCompatActivity), stanie się to domyślnie. Zobacz FragmentActivity # onBackPressed
Xiao

3
ale w tym kodzie nie można używać zmiennych, klas i funkcji z klasy fragmentu
25

2
@Prabs, jeśli korzystasz z fragmentu obsługi v4, upewnij się, że korzystasz z getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383,

151

Moim zdaniem najlepszym rozwiązaniem jest:

ROZWIĄZANIE JAVA

Utwórz prosty interfejs:

public interface IOnBackPressed {
    /**
     * If you return true the back press will not be taken into account, otherwise the activity will act naturally
     * @return true if your processing has priority if not false
     */
    boolean onBackPressed();
}

I w twojej działalności

public class MyActivity extends Activity {
    @Override public void onBackPressed() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
       if (!(fragment instanceof IOnBackPressed) || !((IOnBackPressed) fragment).onBackPressed()) {
          super.onBackPressed();
       }
    } ...
}

Wreszcie w swoim fragmencie:

public class MyFragment extends Fragment implements IOnBackPressed{
   @Override
   public boolean onBackPressed() {
       if (myCondition) {
            //action not popBackStack
            return true; 
        } else {
            return false;
        }
    }
}

ROZWIĄZANIE KOTLIN

1 - Utwórz interfejs

interface IOnBackPressed {
    fun onBackPressed(): Boolean
}

2 - Przygotuj swoją aktywność

class MyActivity : AppCompatActivity() {
    override fun onBackPressed() {
        val fragment =
            this.supportFragmentManager.findFragmentById(R.id.main_container)
        (fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let {
            super.onBackPressed()
        }
    }
}

3 - Zaimplementuj w docelowym fragmencie

class MyFragment : Fragment(), IOnBackPressed {
    override fun onBackPressed(): Boolean {
        return if (myCondition) {
            //action not popBackStack
            true
        } else {
            false
        }
    }
}

1
Co to jest R.id.main_container? Czy to identyfikator FragmentPager?
Nathan F.

1
@MaximeJallu w rozwiązaniu Kotlin, mieszanie bezpiecznych wywołań ( .?) i egzekwowanie wartości innych niż null ( !!) może prowadzić do NPE - obsada nie zawsze jest bezpieczna. Wolałbym pisać if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }lub bardziej kotliney(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek

1
@Antek Witam, dziękuję za zwrot, mogłeś edytować post, aby dokonać korekty. Nie oceniaj surowo ogólnego rozwiązania.
Maxime Jallu,

4
Nie powinno tak być .onBackPressed()?.takeIf { !it }?.let{...}? .not()po prostu zwraca odwrotność.
David Miguel,

1
val fragment = this.supportFragmentManager.findFragmentById(R.id.flContainer) as? NavHostFragment val currentFragment = fragment?.childFragmentManager?.fragments?.get(0) as? IOnBackPressed currentFragment?.onBackPressed()?.takeIf { !it }?.let{ super.onBackPressed() }To jest dla osób, które używają Kotlin i NavigationController
Pavle Pavlov

97

Według @HaMMeRed odpowiedzią jest pseudokod, jak to powinno działać. Powiedzmy, że twoja główna aktywność jest wywoływana i BaseActivityzawiera fragmenty potomne (jak w przykładzie lib SlidingMenu). Oto kroki:

Najpierw musimy stworzyć interfejs i klasę, która implementuje jego interfejs, aby mieć ogólną metodę

  1. Utwórz interfejs klasy OnBackPressedListener

    public interface OnBackPressedListener {
        public void doBack();
    }
  2. Stwórz klasę, która wdraża umiejętności OnBackPressedListener

    public class BaseBackPressedListener implements OnBackPressedListener {
        private final FragmentActivity activity;
    
        public BaseBackPressedListener(FragmentActivity activity) {
            this.activity = activity;
        }
    
        @Override
        public void doBack() {
            activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }
  3. Odtąd będziemy pracować nad naszym kodem BaseActivityi jego fragmentami

  4. Utwórz prywatnego słuchacza na szczycie swojej klasy BaseActivity

    protected OnBackPressedListener onBackPressedListener;
  5. utwórz metodę ustawiania nasłuchiwacza BaseActivity

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }
  6. w przesłonięciu onBackPressedzaimplementuj coś takiego

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
  7. w swoim fragmencie onCreateViewnależy dodać naszego słuchacza

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        activity = getActivity();
    
        ((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
    
        View view = ... ;
    //stuff with view
    
        return view;
    }

Voila, teraz kiedy klikniesz z powrotem we fragmencie, powinieneś złapać swoją niestandardową metodę na plecach.


Jeśli nasłuchujący jest więcej niż jeden fragment, jak określić, w onBackPressed()którym fragmencie był wyświetlany po naciśnięciu przycisku Wstecz?
Al Lelopath,

2
zamiast tego możesz dostosować detektor kliknięć jako nowy baseBackPressedListener i zadzwonić tam jako anonimowy, aby ustawić własne zachowanie, coś takiego:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
Deadfish

Dzięki, zmieniłem jednak moje stanowisko w tej sprawie, ale teraz polecam odsprzęgnięcie od lokalnych wiadomości rozgłoszeniowych. Myślę, że przyniosłoby to komponenty wyższej jakości, które są wysoce odsprzężone.
HaMMeReD



86

To zadziałało dla mnie: https://stackoverflow.com/a/27145007/3934111

@Override
public void onResume() {
    super.onResume();

    if(getView() == null){
        return;
    }

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {

            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK){
                // handle back button's click listener
                return true;
            }
            return false;
        }
    });
}

2
Działa bezbłędnie! Ale wtedy musisz również obsłużyć domyślną akcję, pisząc prawdopodobnie jeszcze jeden warunek JEŻELI. Od tego czasu obniżałem mój slideUpPanel, jeśli był rozwinięty. Więc najpierw musiałem zaznaczyć, czy (panel jest podniesiony), a następnie .....
sud007

18
Problem z tym rozwiązaniem polega na tym, że getView () zwraca tylko widok główny (a nie widok podrzędny, jeśli zostaną zogniskowane). Więc jeśli masz EditText i wpiszesz tekst, a następnie naciśnij raz „wstecz”, aby ukryć klawiaturę, drugie naciśnięcie „wstecz” jest inicjowane z „EditText” (sam widok) i ta metoda nie wydaje się łapać „ przycisk Wstecz ”, a następnie naciśnij (ponieważ łapie tylko widok główny). Jak rozumiem, możesz przechwytywać zdarzenia naciśnięcia klawisza w „EditText”, a także w innych widokach, ale jeśli masz „złożoną” formę, nie jest to tak czyste i łatwe do utrzymania, jak mogłoby być.
Pelpotronic,

To świetne proste rozwiązanie koszmaru, jakim są fragmenty. Jeśli masz „złożony” formularz edycji, prawdopodobnie powinieneś usunąć swój projekt i zacząć od nowa. Zwróć false, jeśli chcesz użyć standardowego zachowania. Zwróć wartość true, gdy przechwyciłeś tylną prasę i coś z tym
zrobiłeś

65

Jeśli chcesz mieć taką funkcjonalność, musisz zastąpić ją w swojej aktywności, a następnie dodać YourBackPressedinterfejs do wszystkich swoich fragmentów, które wywołujesz w odpowiednim fragmencie, po każdym naciśnięciu przycisku Wstecz.

Edycja: Chciałbym dołączyć moją poprzednią odpowiedź.

Gdybym miał to zrobić dzisiaj, użyłbym transmisji lub ewentualnie transmisji zamówionej, jeśli spodziewałem się, że inne panele zaktualizują się zgodnie z panelem głównym / głównym.

LocalBroadcastManagerw Bibliotece pomocy może ci w tym pomóc, a ty po prostu wysyłasz transmisję onBackPressedi subskrybujesz swoje fragmenty, które są dla niej ważne. Wydaje mi się, że Messaging jest implementacją bardziej niezależną od produkcji i skalowałby się lepiej, więc byłoby to teraz moje oficjalne zalecenie dotyczące implementacji. Po prostu użyj Intentakcji jako filtru wiadomości. wyślij nowo utworzony ACTION_BACK_PRESSED, wyślij go ze swojej aktywności i nasłuchuj w odpowiednich fragmentach.


4
Całkiem niezły pomysł! kilka dodatkowych przemyśleń: + ważne jest, aby zrozumieć tę logikę, gdy transmisja rozpoczyna się od aktywności> fragmentów (a nie na odwrót). OnBackPressed jest dostępny tylko na poziomie aktywności, więc kluczem jest tutaj zatrzymanie zdarzenia i nadanie go do wszystkich „nasłuchujących” fragmentów. + Użycie EventBus (takiego jak Square's Otto) sprawia, że ​​implementacja dla takiego przypadku jest banalna!
Kaushik Gopal

2
Jeszcze nie korzystałem z EventBus Square, ale będę w przyszłych produktach. Myślę, że to lepsze rozwiązanie w czystej Javie, a jeśli możesz wyeliminować sekcje swojej architektury z Androida, tym lepiej.
HaMMeReD

Jak pozwalasz, aby tylko najlepsze fragmenty obsługiwały to wydarzenie? na przykład, chciałbyś zniszczyć obecnie pokazywany fragment
eugene

Odbiornik powinien być związany z cyklem życia, jeśli go używasz, więc jeśli nie jest widoczny, nie powinien odbierać zdarzenia.
HaMMeReD

Zauważ też, że LocalBroadcastManagernie można zrobić zamówionych transmisji
Xiao

46

Nic z tego nie jest łatwe do wdrożenia ani nie będzie działać w optymalny sposób.

Fragmenty mają wywołanie metody onDetach, które wykona zadanie.

@Override
    public void onDetach() {
        super.onDetach();
        PUT YOUR CODE HERE
    }

TO BĘDZIE PRACA.


20
ale problem polega na tym, że nie wiesz, czy fragment został odłączony z powodu cofnięcia lub z innego powodu, który doprowadził użytkownika do przejścia innej aktywności lub fragmentu.
Słoneczny dzień

7
Funkcja onDetach () jest wywoływana, gdy fragment nie jest już przyłączony do swojej aktywności. Nazywa się to po onDestroy (). Mimo że dostaniesz się do tego kodu po naciśnięciu przycisku Wstecz, dostaniesz ten kod również po zmianie fragmentu na fragment. Możesz zrobić coś fajnego, aby to zadziałało ...
apmartin1991

Działa dobrze dla DialogFragment.
CoolMind

2
Nie zapomnij dodać isRemoving()zgodnie z opisem w stackoverflow.com/a/27103891/2914140 .
CoolMind,

1
To jest źle. Można go nazwać z kilku powodów
Bali,

39

Jeśli używasz androidx.appcompat:appcompat:1.1.0lub więcej, możesz dodać OnBackPressedCallbackdo swojego fragmentu w następujący sposób

requireActivity()
    .onBackPressedDispatcher
    .addCallback(this, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            Log.d(TAG, "Fragment back pressed invoked")
            // Do custom work here    

            // if you want onBackPressed() to be called as normal afterwards
            if (isEnabled) {
                isEnabled = false
                requireActivity().onBackPressed()
            }
        }
    }
)

Zobacz https://developer.android.com/guide/navigation/navigation-custom-back


Jeśli używasz androidx-core-ktx, możesz użyćrequireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { /* code to be executed when back is pressed */ }
Max

Ważne jest, aby pamiętać, że LifecycleOwnerparametr należy dodać jak w tym przykładzie. Bez niego wszystkie fragmenty rozpoczęte później zadzwonią, handleBackPressed()jeśli zostanie naciśnięty przycisk Wstecz.
Eric B.

czy ta metoda musi być z biblioteką nawigacji?
Cisza

25

Po prostu dodaj addToBackStackpodczas przechodzenia między fragmentami, jak poniżej:

fragmentManager.beginTransaction().replace(R.id.content_frame,fragment).addToBackStack("tag").commit();

jeśli napiszesz addToBackStack(null), sam sobie z tym poradzi, ale jeśli podasz tag, powinieneś go obsłużyć ręcznie.


Czy jest coś jeszcze do zrobienia, czy wystarczy dodanie metody addToBackStack?
Sreecharan Desabattula

1
jeśli tylko dodasz, że to powinno działać, jeśli nadal nie działa, powinno być coś w twoim kodzie. Zostaw kod gdzieś do zobaczenia @SreecharanDesabattula
Rudi


20

ponieważ to pytanie i niektóre odpowiedzi mają ponad pięć lat, podzielę się moim rozwiązaniem. Jest to kontynuacja i modernizacja odpowiedzi @oyenigun

AKTUALIZACJA: Na dole tego artykułu dodałem alternatywną implementację przy użyciu abstrakcyjnego rozszerzenia fragmentu, które w ogóle nie będzie obejmować działania, co byłoby przydatne dla każdego, kto ma bardziej złożoną hierarchię fragmentów obejmującą zagnieżdżone fragmenty, które wymagają innego zachowania wstecznego.

Musiałem to zaimplementować, ponieważ niektóre fragmenty, których używam, mają mniejsze widoki, które chciałbym odrzucić za pomocą przycisku Wstecz, takie jak małe wyskakujące widoki informacji itp., Ale jest to dobre dla każdego, kto musi zastąpić zachowanie przycisk Wstecz wewnątrz fragmentów.

Najpierw zdefiniuj interfejs

public interface Backable {
    boolean onBackPressed();
}

Ten interfejs, który nazywam Backable(jestem zwolennikiem konwencji nazewnictwa), ma jedną metodę, onBackPressed()która musi zwrócić booleanwartość. Musimy wymusić wartość logiczną, ponieważ będziemy musieli wiedzieć, czy naciśnięcie przycisku Wstecz „pochłonęło” zdarzenie Wstecz. Zwrot trueoznacza, że ​​tak, i nie jest wymagane żadne dalsze działanie, w przeciwnym razie falsemówi, że nadal musi nastąpić domyślna akcja wstecz. Ten interfejs powinien być własnym plikiem (najlepiej w osobnym pakiecie o nazwie interfaces). Pamiętaj, że dzielenie klas na pakiety to dobra praktyka.

Po drugie, znajdź górny fragment

Stworzyłem metodę, która zwraca ostatni Fragmentobiekt z tylnego stosu. Używam tagów ... jeśli używasz identyfikatorów, wprowadź niezbędne zmiany. Mam tę statyczną metodę w klasie użytkowej, która zajmuje się stanami nawigacji itp., Ale oczywiście umieść ją tam, gdzie najbardziej Ci odpowiada. Dla budowania umieściłem mój w klasie o nazwie NavUtils.

public static Fragment getCurrentFragment(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    if (fragmentManager.getBackStackEntryCount() > 0) {
        String lastFragmentName = fragmentManager.getBackStackEntryAt(
                fragmentManager.getBackStackEntryCount() - 1).getName();
        return fragmentManager.findFragmentByTag(lastFragmentName);
    }
    return null;
}

Upewnij się, że liczba tylnych stosów jest większa niż 0, w przeciwnym razie ArrayOutOfBoundsExceptionmoże zostać wyrzucony w czasie wykonywania. Jeśli nie jest większa niż 0, zwróć null. Później sprawdzimy wartość zerową ...

Po trzecie, zaimplementuj we fragmencie

Zaimplementuj Backableinterfejs w dowolnym fragmencie, w którym chcesz zastąpić zachowanie przycisku Wstecz. Dodaj metodę implementacji.

public class SomeFragment extends Fragment implements 
        FragmentManager.OnBackStackChangedListener, Backable {

...

    @Override
    public boolean onBackPressed() {

        // Logic here...
        if (backButtonShouldNotGoBack) {
            whateverMethodYouNeed();
            return true;
        }
        return false;
    }

}

W onBackPressed()zastąpieniu wprowadź dowolną logikę. Jeśli chcesz, aby przycisk Wstecz nie wyskakiwał z tylnego stosu (zachowanie domyślne), zwróć wartość true , że zdarzenie Wstecz zostało zaabsorbowane. W przeciwnym razie zwróć false.

Wreszcie w Twojej działalności ...

Zastąp onBackPressed()metodę i dodaj do niej tę logikę:

@Override
public void onBackPressed() {

    // Get the current fragment using the method from the second step above...
    Fragment currentFragment = NavUtils.getCurrentFragment(this);

    // Determine whether or not this fragment implements Backable
    // Do a null check just to be safe
    if (currentFragment != null && currentFragment instanceof Backable) {

        if (((Backable) currentFragment).onBackPressed()) {
            // If the onBackPressed override in your fragment 
            // did absorb the back event (returned true), return
            return;
        } else {
            // Otherwise, call the super method for the default behavior
            super.onBackPressed();
        }
    }

    // Any other logic needed...
    // call super method to be sure the back button does its thing...
    super.onBackPressed();
}

Otrzymujemy bieżący fragment na tylnym stosie, a następnie sprawdzamy zero i sprawdzamy, czy implementuje on nasz Backableinterfejs. Jeśli tak, określ, czy zdarzenie zostało wchłonięte. Jeśli tak, to koniec onBackPressed()i możemy wrócić. W przeciwnym razie traktuj to jak normalne naciśnięcie wstecz i wywołaj metodę super.

Druga opcja nie angażowania działania

Czasami nie chcesz, aby Działanie w ogóle sobie z tym poradziło i musisz obsługiwać to bezpośrednio w tym fragmencie. Ale kto mówi, że nie możesz mieć fragmentów z interfejsem API prasy wstecznej? Po prostu rozszerz swój fragment na nową klasę.

Utwórz klasę abstrakcyjną, która rozszerza Fragment i implementuje View.OnKeyListnerinterfejs ...

import android.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;

public abstract class BackableFragment extends Fragment implements View.OnKeyListener {

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        view.setFocusableInTouchMode(true);
        view.requestFocus();
        view.setOnKeyListener(this);
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        if (event.getAction() == KeyEvent.ACTION_UP) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                onBackButtonPressed();
                return true;
            }
        }

        return false;
    }

    public abstract void onBackButtonPressed();
}

Jak widać, każdy rozciągający się fragment BackableFragmentautomatycznie przechwytuje kliknięcia wstecz przy użyciu View.OnKeyListenerinterfejsu. Wystarczy wywołać onBackButtonPressed()metodę abstrakcyjną z zaimplementowanej onKey()metody, używając standardowej logiki, aby rozpoznać naciśnięcie przycisku Wstecz. Jeśli musisz zarejestrować kliknięcia klawiszy inne niż przycisk Wstecz, pamiętaj, aby wywołać supermetodę podczas zastępowaniaonKey() fragmentu, w przeciwnym razie zastąpisz zachowanie w abstrakcji.

Prosty w użyciu, wystarczy rozszerzyć i wdrożyć:

public class FragmentChannels extends BackableFragment {

    ...

    @Override
    public void onBackButtonPressed() {
        if (doTheThingRequiringBackButtonOverride) {
            // do the thing
        } else {
            getActivity().onBackPressed();
        }
    }

    ...
}

Ponieważ onBackButtonPressed()metoda w superklasie jest abstrakcyjna, po rozszerzeniu należy ją zaimplementować onBackButtonPressed(). Zwraca, voidponieważ po prostu musi wykonać akcję w klasie fragmentów i nie musi przekazywać absorpcji prasy z powrotem do działania. Upewnij się, że wywołujesz onBackPressed()metodę Activity , jeśli cokolwiek robisz z przyciskiem Wstecz nie wymaga obsługi, w przeciwnym razie przycisk Wstecz zostanie wyłączony ... i nie chcesz tego!

Ostrzeżenia Jak widać, ustawia to kluczowy odbiornik w widoku głównym fragmentu i będziemy musieli go skoncentrować. Jeśli w twoim fragmencie, który rozszerza tę klasę (lub inne wewnętrzne fragmenty lub widoki, które mają to samo), zaangażujesz w tekst fragmenty (lub inne widoki kradnące fokus), musisz poradzić sobie z tym osobno. Jest dobry artykuł o rozszerzaniu EditText, aby stracić skupienie na prasie wstecznej.

Mam nadzieję, że ktoś uzna to za przydatne. Szczęśliwego kodowania.


Dzięki, to miłe rozwiązanie. Czy możesz powiedzieć, dlaczego dzwonisz super.onBackPressed();dwa razy?
CoolMind

Jest wywoływany tylko raz na scenariusz dotyczący stanu zerowego currentFragment. Jeśli fragment nie jest pusty, a fragment implementuje Backableinterfejs, nie są podejmowane żadne działania. Jeśli fragment nie ma wartości zerowej i NIE jest implementowany Backable, wywołujemy metodę super. Jeśli fragment ma wartość null, przechodzi do ostatniego super wywołania.
mwieczorek

Przepraszam, nie zrozumiałem W przypadku, gdy a currentFragmentnie jest zerowe i jest instancją Backable i jest odłączone (nacisnęliśmy backprzycisk i zamykamy fragment) super.onBackPressed();, wywoływane jest pierwsze wystąpienie , a następnie drugie.
CoolMind

Doskonałe rozwiązanie
David Toledo

Może „Zamykalne” brzmi trochę lepiej niż „Backable”
pumnao

15

Rozwiązanie jest proste:

1) Jeśli masz podstawową klasę fragmentów, którą rozszerzają wszystkie fragmenty, dodaj ten kod do jej klasy, w przeciwnym razie utwórz taką klasę fragmentów podstawowych

 /* 
 * called when on back pressed to the current fragment that is returned
 */
 public void onBackPressed()
 {
    // add code in super class when override
 }

2) W klasie Activity przesłoń onBackPressed w następujący sposób:

private BaseFragment _currentFragment;

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

3) W swojej klasie Fragment dodaj żądany kod:

 @Override
 public void onBackPressed()
 {
    setUpTitle();
 }


9

onBackPressed() powodują odłączenie Fragmentu od Aktywności.

Według odpowiedzi @Sterling Diaz myślę, że ma rację. ALE jakaś sytuacja będzie nie tak. (np. Obróć ekran)

Myślę, że moglibyśmy wykryć, czy isRemoving() osiągnąć cele.

Możesz to napisać pod adresem onDetach()lub onDestroyView(). To jest praca.

@Override
public void onDetach() {
    super.onDetach();
    if(isRemoving()){
        // onBackPressed()
    }
}

@Override
public void onDestroyView() {
    super.onDestroyView();
    if(isRemoving()){
        // onBackPressed()
    }
}

8

Cóż, zrobiłem to w ten sposób i działa na mnie

Prosty interfejs

FragmentOnBackClickInterface.java

public interface FragmentOnBackClickInterface {
    void onClick();
}

Przykładowa implementacja

MyFragment.java

public class MyFragment extends Fragment implements FragmentOnBackClickInterface {

// other stuff

public void onClick() {
       // what you want to call onBackPressed?
}

następnie po prostu przesłonisz onBackPressed w działaniu

    @Override
public void onBackPressed() {
    int count = getSupportFragmentManager().getBackStackEntryCount();
    List<Fragment> frags = getSupportFragmentManager().getFragments();
    Fragment lastFrag = getLastNotNull(frags);
    //nothing else in back stack || nothing in back stack is instance of our interface
    if (count == 0 || !(lastFrag instanceof FragmentOnBackClickInterface)) {
        super.onBackPressed();
    } else {
        ((FragmentOnBackClickInterface) lastFrag).onClick();
    }
}

private Fragment getLastNotNull(List<Fragment> list){
    for (int i= list.size()-1;i>=0;i--){
        Fragment frag = list.get(i);
        if (frag != null){
            return frag;
        }
    }
    return null;
}

Nie działa ! super.onbackpressed () jest wywoływany zawsze :(
Animesh Mangla

Jak przekazać to wydarzenie Fragment wewnętrzny. Mam ViewPager w działaniu i ten ViewPager ma fragmenty, teraz wszystkie te fragmenty mają fragment potomny. Jak przekazać zdarzenie przycisku z powrotem do fragmentów podrzędnych.
Kishan Vaghela

@KishanVaghela dobrze. Od tamtej pory nie dotknąłem Androida, ale mam jeden pomysł. Daj mi 24 miejsce, aby zaktualizować moją odpowiedź.
Błażej

Ok nie ma problemu, jedną z opcji jest to, że muszę przekazać zdarzenie detektora do każdego fragmentu podrzędnego z fragmentu nadrzędnego. ale nie jest to idealny sposób, ponieważ muszę to zrobić dla każdego fragmentu.
Kishan Vaghela

@KishanVaghela Myślałem o menedżerze w działaniu. Możesz dodawać / usuwać fragmenty za pomocą tego interfejsu. Ale przy takim podejściu musisz wdrożyć menedżera w każdym fragmencie, który ma podfragmenty. Więc może lepszym rozwiązaniem będzie stworzenie menedżera jako singletona. Następnie będziesz musiał dodać / usunąć fragmenty / obiekty, aw „onBackPressed” wywołasz metodę „onBackPressed” tego menedżera. Ta metoda wywoła metodę „onClick” w każdym obiekcie dodanym do menedżera. Czy to dla ciebie mniej więcej jasne?
Błażej

8

Powinieneś dodać interfejs do swojego projektu jak poniżej;

public interface OnBackPressed {

     void onBackPressed();
}

Następnie powinieneś wdrożyć ten interfejs na swoim fragmencie;

public class SampleFragment extends Fragment implements OnBackPressed {

    @Override
    public void onBackPressed() {
        //on Back Pressed
    }

}

Możesz aktywować to zdarzenie onBackPressed w ramach swoich działań w zdarzeniu onBackPressed, jak poniżej;

public class MainActivity extends AppCompatActivity {
       @Override
        public void onBackPressed() {
                Fragment currentFragment = getSupportFragmentManager().getFragments().get(getSupportFragmentManager().getBackStackEntryCount() - 1);
                if (currentFragment instanceof OnBackPressed) {  
                    ((OnBackPressed) currentFragment).onBackPressed();
                }
                super.onBackPressed();
        }
}

Jest to metoda agood, ale uważaj, nie wywoływaj, getActivity().onBackPressed();ponieważ wywoła wyjątek: „java.lang.StackOverflowError: rozmiar stosu 8 MB”.
CoolMind

8

To tylko mały kod, który załatwi sprawę:

 getActivity().onBackPressed();

Mam nadzieję, że to komuś pomoże :)


4
Poprosił o zastąpienie zachowania, a nie po prostu wywołanie metody działania.
mwieczorek,

2
Przeczytałem to uważnie, dzięki. Twoje rozwiązanie po prostu przekazuje metodę onBackPressed z powrotem do działania, gdy zapytał, jak zastąpić.
mwieczorek

7

Jeśli używasz EventBus, jest to prawdopodobnie znacznie prostsze rozwiązanie:

W twoim fragmencie:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    EventBus.getDefault().register(this);
}

@Override
public void onDetach() {
    super.onDetach();
    EventBus.getDefault().unregister(this);
}


// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    getSupportFragmentManager().popBackStack();
}

aw swojej klasie aktywności możesz zdefiniować:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    EventBus.getDefault().unregister(this);
    super.onStop();
}

// This method will be called when a MessageEvent is posted
public void onEvent(BackPressedMessage type){
    super.onBackPressed();
}

@Override
public void onBackPressed() {
    EventBus.getDefault().post(new BackPressedMessage(true));
}

BackPressedMessage.java jest tylko obiektem POJO

Jest to bardzo czyste i nie ma problemów z interfejsem / implementacją.


7

to jest moje rozwiązanie:

w MyActivity.java:

public interface OnBackClickListener {
        boolean onBackClick();
    }

    private OnBackClickListener onBackClickListener;

public void setOnBackClickListener(OnBackClickListener onBackClickListener) {
        this.onBackClickListener = onBackClickListener;
    }

@Override
    public void onBackPressed() {
        if (onBackClickListener != null && onBackClickListener.onBackClick()) {
            return;
        }
        super.onBackPressed();
    }

i we fragmencie:

((MyActivity) getActivity()).setOnBackClickListener(new MyActivity.OnBackClickListener() {
    @Override
    public boolean onBackClick() {
        if (condition) {
            return false;
        }

        // some codes

        return true;
    }
});

5

Za pomocą komponentu Nawigacja możesz to zrobić w następujący sposób :

Jawa

public class MyFragment extends Fragment {

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
        }
    });
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);

    // The callback can be enabled or disabled here or in handleOnBackPressed()
}
...
}

Kotlin

class MyFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // This callback will only be called when MyFragment is at least Started.
    val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
        // Handle the back button event
    }

    // The callback can be enabled or disabled here or in the lambda
}
...
}

4

Co powiesz na użycie onDestroyView ()?

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

5
co powiesz na przejście do innego fragmentu z rzeczywistego fragmentu, co stanie się przy użyciu twojej metody? :)
Choletski

Najbardziej bezużyteczna odpowiedź. To jest tak samo jak pisanie i = ilub if (1 < 0) {}.
CoolMind,

4
@Override
public void onResume() {
    super.onResume();

    getView().setFocusableInTouchMode(true);
    getView().requestFocus();
    getView().setOnKeyListener(new View.OnKeyListener() {
        @Override
        public boolean onKey(View v, int keyCode, KeyEvent event) {
            if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
                // handle back button
                replaceFragmentToBackStack(getActivity(), WelcomeFragment.newInstance(bundle), tags);

                return true;
            }

            return false;
        }
    });
}

Tylko jeśli fragmenty nie mają widoku, na którym można ustawić ostrość (np. Edytować tekst), pozwól fragmentom obsługiwać sam przycisk Wstecz, tak jak robi to odpowiedź. W przeciwnym razie pozwól, aby aktywność zawierająca fragmenty zrobiła to przez OnBackPress (), gdy zostanie zapisana pierwsza odpowiedź; ponieważ widok z możliwością skupienia ukradnie ostrość fragmentu. Możemy jednak odzyskać fokus dla fragmentu poprzez wyraźne fokusowanie wszystkich możliwych do ogniskowania widoków metodą clearFocus () i ponownie ustawić fokus na fragment.
Nguyen Tan Dat

4
public class MyActivity extends Activity {

    protected OnBackPressedListener onBackPressedListener;

    public interface OnBackPressedListener {
        void doBack();
    }

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
    } 

    @Override
    protected void onDestroy() {
        onBackPressedListener = null;
        super.onDestroy();
    }
}

we fragmencie dodaj: nie zapomnij zaimplementować interfejsu mainactivity.

public class MyFragment extends Framgent implements MyActivity.OnBackPressedListener {
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
         ((MyActivity) getActivity()).setOnBackPressedListener(this);
    }

@Override
public void doBack() {
    //BackPressed in activity will call this;
}

}

4

W moim rozwiązaniu (Kotlin);

Używam funkcji onBackAlternative jako parametru w BaseActivity .

BaseActivity

abstract class BaseActivity {

    var onBackPressAlternative: (() -> Unit)? = null

    override fun onBackPressed() {
        if (onBackPressAlternative != null) {
            onBackPressAlternative!!()
        } else {
            super.onBackPressed()
        }
    }
}

Mam funkcję ustawiania onBackPressAlternative na BaseFragment .

BaseFragment

abstract class BaseFragment {

     override fun onStart() {
        super.onStart()
        ...
        setOnBackPressed(null) // Add this
     }

      //Method must be declared as open, for overriding in child class
     open fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
         (activity as BaseActivity<*, *>).onBackPressAlternative = onBackAlternative
     }
}

Wtedy mój onBackPressAlternative jest gotowy do użycia na fragmentach.

Pod fragmenty

override fun setOnBackPressed(onBackAlternative: (() -> Unit)?) {
    (activity as BaseActivity<*, *>).onBackPressAlternative = {
        // TODO Your own custom onback function 
    }
}

3

Nie implementuj metody ft.addToBackStack (), aby po naciśnięciu przycisku Wstecz aktywność została zakończona.

proAddAccount = new ProfileAddAccount();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, proAddAccount);
//fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();


3

Wystarczy wykonać następujące kroki:

Zawsze podczas dodawania fragmentu

fragmentTransaction.add(R.id.fragment_container, detail_fragment, "Fragment_tag").addToBackStack(null).commit();

Następnie w głównej czynności zastąp onBackPressed()

if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
    getSupportFragmentManager().popBackStack();
} else {
    finish();
}

Aby obsłużyć przycisk Wstecz w aplikacji,

Fragment f = getActivity().getSupportFragmentManager().findFragmentByTag("Fragment_tag");
if (f instanceof FragmentName) {
    if (f != null) 
        getActivity().getSupportFragmentManager().beginTransaction().remove(f).commit()               
}

Otóż ​​to!


3

W cyklu życia aktywności zawsze przycisk Wstecz Androida obsługuje transakcje FragmentManager, gdy korzystaliśmy z FragmentActivity lub AppCompatActivity.

Aby obsłużyć backstacka, nie musimy obsługiwać jego liczby ani niczego tagować, ale powinniśmy się skupić podczas dodawania lub zastępowania fragmentu. Znajdź następujące fragmenty do obsługi przypadków przycisków Wstecz,

    public void replaceFragment(Fragment fragment) {

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
        }
        transaction.replace(R.id.activity_menu_fragment_container, fragment).commit();
    }

Tutaj nie dodam stosu wstecznego do mojego fragmentu głównego, ponieważ jest to strona główna mojej aplikacji. Jeśli dodasz addToBackStack do HomeFragment, aplikacja będzie czekać na usunięcie całej frakcji z aktywnością, a następnie pojawi się pusty ekran, więc utrzymuję następujący warunek,

if (!(fragment instanceof HomeFragment)) {
            transaction.addToBackStack(null);
}

Teraz możesz zobaczyć poprzednio dodany fragment w usłudze Acitvity, a aplikacja zakończy działanie po osiągnięciu HomeFragment. możesz także spojrzeć na następujące fragmenty.

@Override
public void onBackPressed() {

    if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
        closeDrawer();
    } else {
        super.onBackPressed();
    }
}

3

Fragment: Stwórz BaseFragment umieszczając metodę:

 public boolean onBackPressed();

Czynność:

@Override
public void onBackPressed() {
    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    if (fragments != null) {
        for (Fragment fragment : fragments) {
            if (!fragment.isVisible()) continue;

            if (fragment instanceof BaseFragment && ((BaseFragment) fragment).onBackPressed()) {
                return;
            }
        }
    }

    super.onBackPressed();
}

Twoja aktywność będzie przebiegać po dołączonych i widocznych fragmentach i wywoła onBackPressed () na każdym z nich i przerwie działanie, jeśli jedno z nich zwróci „true” (co oznacza, że ​​zostało to obsłużone, więc nie ma dalszych działań).


Sir, ten jest najbardziej elegancki i działa idealnie!
asozcan

3

Zgodnie z uwagami do wydania AndroidX , androidx.activity 1.0.0-alpha01został wydany i wprowadza ComponentActivitynową klasę podstawową istniejących FragmentActivityi AppCompatActivity. A ta wersja przynosi nam nową funkcję:

Możesz teraz zarejestrować OnBackPressedCallbackvia, addOnBackPressedCallbackaby otrzymywać połączenia onBackPressed()zwrotne bez konieczności zastępowania metody w swojej działalności.


Czy możesz podać przykład tego? Nie dostaję tej metody do rozwiązania.
Bryan Bryce

1
@BryanBryce Nie znalazłem przykładu, ale oficjalny dokument: developer.android.com/reference/androidx/activity/…
TonnyL

Metoda nie rozwiąże b / c AppCompatActivity w rzeczywistości rozszerza się z androidx.core.app.ComponentActivity zamiast androidx.activity.ComponentActivity.
wooldridgetm

3

Możesz zarejestrować fragment w działaniu, aby obsłużyć naciśnij wstecz:

interface BackPressRegistrar {
    fun registerHandler(handler: BackPressHandler)
    fun unregisterHandler(handler: BackPressHandler)
}

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

stosowanie:

We fragmencie:

private val backPressHandler = object : BackPressHandler {
    override fun onBackPressed(): Boolean {
        showClosingWarning()
        return false
    }
}

override fun onResume() {
    super.onResume()
    (activity as? BackPressRegistrar)?.registerHandler(backPressHandler)
}

override fun onStop() {
    (activity as? BackPressRegistrar)?.unregisterHandler(backPressHandler)
    super.onStop()
}

W działaniu:

class MainActivity : AppCompatActivity(), BackPressRegistrar {


    private var registeredHandler: BackPressHandler? = null
    override fun registerHandler(handler: BackPressHandler) { registeredHandler = handler }
    override fun unregisterHandler(handler: BackPressHandler) { registeredHandler = null }

    override fun onBackPressed() {
        if (registeredHandler?.onBackPressed() != false) super.onBackPressed()
    }
}

3

Zapewnienie niestandardowej nawigacji wstecz poprzez obsługę onBackPressed jest teraz łatwiejsze dzięki wywołaniom zwrotnym wewnątrz fragmentu.

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if (true == conditionForCustomAction) {
                    myCustomActionHere()
                } else  NavHostFragment.findNavController(this@MyFragment).navigateUp();    
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(
        this, onBackPressedCallback
    )
    ...
}

Jeśli chcesz domyślną akcję cofania w oparciu o jakiś warunek, możesz użyć:

 NavHostFragment.findNavController(this@MyFragment).navigateUp();

3

Wewnątrz metody onCreate tego fragmentu dodaj:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    OnBackPressedCallback callback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            //Handle the back pressed
        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}

po dodaniu tego backpress myFragment działa, ale teraz backpress aktywności nie zrobił
Sunil Chaudhary

2

Najlepsze rozwiązanie,

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class BaseActivity extends AppCompatActivity {
    @Override
    public void onBackPressed() {

        FragmentManager fm = getSupportFragmentManager();
        for (Fragment frag : fm.getFragments()) {
            if (frag == null) {
                super.onBackPressed();
                finish();
                return;
            }
            if (frag.isVisible()) {
                FragmentManager childFm = frag.getChildFragmentManager();
                if (childFm.getFragments() == null) {
                    super.onBackPressed();
                    finish();
                    return;
                }
                if (childFm.getBackStackEntryCount() > 0) {
                    childFm.popBackStack();
                    return;
                }
                else {

                    fm.popBackStack();
                    if (fm.getFragments().size() <= 1) {
                        finish();
                    }
                    return;
                }

            }
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.