Як реалізувати onBackPress () у фрагментах?


459

Чи існує спосіб, яким ми можемо реалізувати onBackPressed()в Android Fragment подібний до способу, який ми реалізуємо в Android Activity?

Так як життєвого циклу Фрагмента немає onBackPressed(). Чи є інший альтернативний метод перевершити їзду onBackPressed()на фрагментах Android 3.0?


13
IMHO, фрагмент не повинен ні знати, ні дбати про кнопку НАЗАД. Діяльність може стосуватися кнопки НАЗАД, хоча з фрагментами, якими зазвичай користується FragmentManager. Але оскільки фрагмент не знає про його більш широке середовище (наприклад, чи є він чи не одним із кількох в діяльності), фрагмент насправді не є безпечним для того, щоб намагатися визначити, що таке правильна функція BACK.
CommonsWare

61
Як щодо того, коли весь фрагмент робить його для відображення WebView, і ви хочете, щоб WebView "повернувся" на попередню сторінку, коли натиснута кнопка "Назад"?
mharper

Відповідь Майкла Гербіга була для мене ідеальною. Але для тих, хто не може використовувати getSupportFragmentManager (). Використовуйте натомість getFragmentManager ().
Данталіан

Відповідь Дмитра Зайцева на [Подібне запитання] [1] працює просто чудово. [1]: stackoverflow.com/a/13450668/1372866
ashakirov

6
щоб відповісти на mharper, ви можете зробити щось на зразок webview.setOnKeyListener (новий OnKeyListener () {@Override public boolean onKey (Переглянути v, int keyCode, подія KeyEvent) {if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack {webview.goBack (); return true;} return false;}});
Омід Амініва

Відповіді:


307

Я вирішив таким чином перекрити onBackPressedдіяльність. Усі FragmentTransactionє addToBackStackперед фіксацією:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

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

}


4
count == 1 дозволить закрити перший фрагмент одним натисканням кнопки назад. Але в іншому випадку найкраще рішення
Kunalxigxag

2
Якщо ви використовуєте бібліотеку підтримки v7 і ваша активність поширюється на FragmentActivity (або підклас, наприклад, AppCompatActivity), це станеться за замовчуванням. Дивіться FragmentActivity # onBackPress
Сяо

3
але ви не можете використовувати змінні, класи та функції класу фрагмента в цьому коді
користувач25

2
@Prabs, якщо ви використовуєте фрагмент підтримки v4, тоді переконайтеся, що ви використовуєте getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383

151

На мою думку, найкраще рішення:

РІШЕННЯ ДЖАВА

Створіть простий інтерфейс:

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

І у вашій діяльності

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

Нарешті у вашому фрагменті:

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

КОТЛІН РІШЕННЯ

1 - Створити інтерфейс

interface IOnBackPressed {
    fun onBackPressed(): Boolean
}

2 - Підготуйте свою діяльність

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

3 - реалізуйте у своєму цільовому фрагменті

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

1
Що таке R.id.main_container? Це ідентифікатор для FragmentPager?
Натан Ф.

1
@MaximeJallu в рішенні Котліна, змішування безпечних викликів ( .?) та виконання ненульових значень ( !!) може призвести до створення NPE, амплуа не завжди є безпечним. Я краще напишу if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }або більше котліней(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek

1
@Antek Привіт! Дякую за повернення, ви могли відредагувати публікацію, щоб внести свої виправлення. Не судіть суворо рішення.
Максим Джаллу

4
Чи не повинно бути .onBackPressed()?.takeIf { !it }?.let{...}? .not()просто повертає обернене.
Девід Мігель

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() }Це для людей, які використовують Kotlin та NavigationController
Павло Павлов

97

Відповідно до відповіді @HaMMeRed, тут є псевдокод, як це має працювати. Скажімо, що ваша основна діяльність називається, BaseActivityяка має дочірні фрагменти (наприклад, у прикладі lib SlidingMenu). Ось такі кроки:

Спочатку нам потрібно створити інтерфейс і клас, який реалізує свій інтерфейс, щоб мати загальний метод

  1. Створення інтерфейсу класу OnBackPressedListener

    public interface OnBackPressedListener {
        public void doBack();
    }
  2. Створіть клас, який реалізує навички роботи 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. Відтепер ми будемо працювати над своїм кодом BaseActivityта його фрагментами

  4. Створіть приватного слухача над своїм класом BaseActivity

    protected OnBackPressedListener onBackPressedListener;
  5. створити метод для встановлення слухача в BaseActivity

    public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
        this.onBackPressedListener = onBackPressedListener;
    }
  6. перекрити onBackPressedреалізувати щось подібне

    @Override
    public void onBackPressed() {
        if (onBackPressedListener != null)
            onBackPressedListener.doBack();
        else
            super.onBackPressed();
  7. у своєму фрагменті onCreateViewви повинні додати нашого слухача

    @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, тепер, коли ти клацнеш назад у фрагменті, ти повинен зафіксувати свій власний метод назад.


Якщо більш ніж один фрагмент є слухачем, то як визначити onBackPressed(), який фрагмент відображався, коли натискали кнопку назад?
Аль Лелопат

2
ви можете налаштувати слухача кліків замість нового baseBackPressListener і зателефонувати туди як анонім, щоб встановити власну поведінку, приблизно так:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
мертвий риб

Дякую, я змінив свою позицію щодо цього, хоч зараз рекомендую роз'єднатись із місцевими широкомовними повідомленнями. Я думаю, що це дозволить отримати більш якісні компоненти, які сильно відокремлюються.
HaMMeReD


Це прекрасно працювало для мене, я перезапускаю Діяльність
raphaelbgr

86

Це працювало для мене: 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
Працювали бездоганно! Але тоді вам також доведеться обробити дію за замовчуванням, написавши ще одне умова IF. З тих пір я опускав свій slideUpPanel, якщо він розширювався. Отже, я спершу повинен був позначити чек, якщо (панель вгору), то .....
sud007

18
Проблема цього рішення полягає в тому, що getView () повертає лише основний вид (а не підвід, якщо вони отримують фокус). Отже, якщо у вас є EditText і введіть якийсь текст, потім один раз натисніть "назад", щоб приховати клавіатуру, друге натискання на "назад" ініціюється з "EditText" (сам вигляд), і цей метод, схоже, не сприймає " назад ", натисніть кнопку потім (оскільки це лише для головного виду). Як я розумію, ви можете зафіксувати події натискання клавіш у вашому "EditText", а також будь-які інші перегляди, але якщо у вас "складна" форма, то це не так чисто і бездоганно, як це могло б бути.
Пелпотронік

Це чудове просте рішення для кошмару, що є фрагментами. Якщо у вас є "складна" форма редагування тексту, то, ймовірно, слід видалити проект і почати все спочатку. Поверніть помилкове, коли ви хочете використовувати стандартну поведінку. Повернення справжнє, коли ви перехопили задній прес і щось зробили з ним
behelit

65

Якщо ви хотіли такого типу функціональності, вам потрібно буде змінити його у своїй діяльності, а потім додати YourBackPressedінтерфейс до всіх ваших фрагментів, до якого ви натискаєте відповідний фрагмент щоразу, коли натискаєте кнопку назад.

Редагувати: Я хотів би додати свою попередню відповідь.

Якби я сьогодні це робив, я би використовував трансляцію або, можливо, упорядковану трансляцію, якби я очікував, що інші панелі в один раз поновлюються на головну / основну панель вмісту.

LocalBroadcastManagerв Бібліотеці підтримки може допомогти у цьому, і ви просто надішліть трансляцію в onBackPressedі підпишіться на фрагменти, які вас цікавлять. Я думаю, що обмін повідомленнями - це більш відокремлена реалізація, і вона буде масштабнішою, тому це було б моєю офіційною рекомендацією щодо впровадження зараз. Просто використовуйте Intentдію як фільтр для свого повідомлення. відправте новоствореногоACTION_BACK_PRESSED , надішліть її від своєї діяльності та прослухайте її у відповідних фрагментах.


4
Дуже витончена ідея! кілька додаткових думок: + важливо усвідомити цю логіку, коли трансляція переходить від діяльності> фрагменти (не навпаки). OnBackPress доступний лише на рівні активності, тому ключовим тут є захоплення події та трансляція її всіх фрагментів, що слухають. + Використання EventBus (на зразок Отто Square) робить реалізацію для такого випадку тривіальним!
Каушик Гопал

2
Я ще не використовував EventBus Square, але буду в майбутньому. Я думаю, що це краще рішення з чистого Java, і якщо ви зможете вибити Android з розділів своєї архітектури, тим краще.
HaMMeReD

Як ви даєте лише верхні фрагменти для обробки події? наприклад, ви хочете знищити показаний на даний момент фрагмент
eugene

Якщо ви користуєтесь цим, приймач широкомовної передачі повинен бути прив'язаний до життєвого циклу, тому коли він не видно, він не повинен отримувати подію.
HaMMeReD

Також зауважте, що LocalBroadcastManagerне можна робити замовлені трансляції
Сяо

46

Нічого з цього не легко здійснити, і він не буде функціонувати оптимально.

Фрагменти мають метод виклику onDetach, який зробить роботу.

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

ЦЕ БУДЕ РОБОТУ.


20
але проблема тут полягає в тому, що ви не знаєте, чи був фрагмент від'єднаний через зворотний натиск або через якусь іншу дію, яка призвела до того, що користувач перейшов на деяку іншу діяльність чи фрагмент.
Сонячно

7
onDetach () називається, коли фрагмент більше не прив’язаний до його активності. Це викликається після onDestroy (). Хоча ви отримаєте цей код при натисканні кнопки "назад", ви також отримаєте цей код при зміні фрагмента на фрагмент. Ви можете зробити щось вишукане, щоб зробити цю роботу хоч ...
apmartin1991

Добре працює для DialogFragment.
CoolMind

2
Не забудьте додати, isRemoving()як описано в stackoverflow.com/a/27103891/2914140 .
CoolMind

1
це неправильно. Його можна назвати з кількох причин
Балі

40

Якщо ви використовуєте androidx.appcompat:appcompat:1.1.0або вище, тоді ви можете додати OnBackPressedCallbackфрагмент до свого фрагмента наступним чином

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

Дивіться https://developer.android.com/guide/navigation/navigation-custom-back


Якщо ви користуєтесь androidx-core-ktx, можете скористатисяrequireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { /* code to be executed when back is pressed */ }
Макс

Важливо зазначити, що LifecycleOwnerпарам слід додати, як у цьому прикладі. Без нього будь-які фрагменти, розпочаті згодом, дзвонять, handleBackPressed()якщо натиснути кнопку назад.
Ерік Б.

чи цей метод повинен бути з бібліотекою навігації?
Тиша

25

Просто додайте addToBackStackпід час переходу між фрагментами, як показано нижче:

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

якщо ви пишете addToBackStack(null), він буде обробляти його сам, але якщо ви даєте тег, слід обробити його вручну.


Чи є ще щось, що потрібно зробити або достатньо додати метод addToBackStack?
Sreecharan Desabattula

1
якщо ви просто додасте, що він повинен працювати, якщо він все ще не працює, у вашому коді має бути щось. Залиште код десь, щоб побачити, будь ласка, @SreecharanDesabattula
Rudi


20

оскільки це питання та деякі відповіді мають більше п'яти років, дозвольте мені поділитися своїм рішенням. Це супроводження та модернізація відповіді від @oyenigun

ОНОВЛЕННЯ: У нижній частині цієї статті я додав альтернативну реалізацію, використовуючи абстрактне розширення фрагмента, яке взагалі не буде задіяти активність, що було б корисно для всіх, хто має більш складну ієрархію фрагментів, що включає вкладені фрагменти, які вимагають різної поведінки ззаду.

Мені потрібно було це здійснити, оскільки деякі фрагменти, які я використовую, мають менші погляди, які я хотів би відхилити за допомогою кнопки "назад", наприклад, невеликі інформаційні перегляди, що спливають тощо, але це добре для тих, кому потрібно змінити поведінку задня кнопка всередині фрагментів.

Спочатку визначте інтерфейс

public interface Backable {
    boolean onBackPressed();
}

Цей інтерфейс, який я називаю Backable(я стикер для іменування конвенцій), має єдиний метод, onBackPressed()який повинен повернути booleanзначення. Нам потрібно застосувати булеве значення, тому що нам потрібно буде знати, чи натиснення кнопки "назад" "поглинуло" задню подію. Повернення trueозначає, що воно має, і подальша дія не потрібна, інакше falseговорить про те, що зворотна дія за замовчуванням все ж має відбутися. Цей інтерфейс повинен бути власним файлом (бажано, в окремому пакеті з назвою interfaces). Пам'ятайте, розділення класів на пакети - це хороша практика.

По-друге, знайдіть верхній фрагмент

Я створив метод, який повертає останній Fragmentоб’єкт у задній стек. Я використовую теги ... якщо ви використовуєте ідентифікатори, внесіть необхідні зміни. Цей статичний метод у мене є в класі утиліт, який займається умовами навігації тощо ... але, звичайно, розміщуйте його там, де він найкраще підходить вам. Для зведення я поставив моє в клас під назвою 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;
}

Переконайтеся, що кількість зворотних стеків перевищує 0, інакше ArrayOutOfBoundsExceptionпід час виконання може бути кинуто зображення. Якщо вона не перевищує 0, поверніть null. Пізніше ми перевіримо нульове значення ...

По-третє, реалізація у фрагменті

Реалізуйте Backableінтерфейс у будь-якому фрагменті, де потрібно змінити поведінку кнопки "назад". Додайте спосіб реалізації.

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

...

    @Override
    public boolean onBackPressed() {

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

}

У onBackPressed()виправданому порядку введіть необхідну логіку. Якщо ви хочете, щоб кнопка "назад" не відображала задній стек (поведінка за замовчуванням), поверніть true , щоб ваша подія назад була поглинена. В іншому випадку поверніть хибне.

І нарешті, у вашій діяльності ...

Замініть onBackPressed()метод і додайте до нього цю логіку:

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

Ми отримуємо поточний фрагмент у задній стеці, потім робимо нульову перевірку та визначаємо, чи реалізує він наш Backableінтерфейс. Якщо так, визначте, чи подія була поглинена. Якщо так, ми закінчили onBackPressed()і можемо повернутися. В іншому випадку, ставтеся до цього як до звичайного зворотного преса і зателефонуйте до супер методу.

Другий варіант не включати діяльність

Часом ви взагалі не хочете, щоб Діяльність обробляла це, і вам потрібно обробляти його безпосередньо у фрагменті. Але хто каже, що ви не можете мати фрагменти з API заднього натискання? Просто розкладіть свій фрагмент до нового класу.

Створіть абстрактний клас, який розширює фрагмент та реалізує View.OnKeyListnerінтерфейс ...

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

Як бачимо, будь-який фрагмент, який розширюється BackableFragment, автоматично фіксує кліки назад за допомогою View.OnKeyListenerінтерфейсу. Просто зателефонуйте абстрактному onBackButtonPressed()методу зсередини реалізованого onKey()методу, використовуючи стандартну логіку для виявлення натискання кнопки "назад". Якщо вам потрібно зареєструвати натискання клавіш, окрім кнопки "назад", просто переконайтеся, що зателефонуйте superметоду, коли onKey()перейдете на його фрагмент, інакше ви перекриєте поведінку в абстракції.

Простий у використанні, просто розширити та впровадити:

public class FragmentChannels extends BackableFragment {

    ...

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

    ...
}

Оскільки onBackButtonPressed()метод у суперкласі абстрактний, після його розширення необхідно реалізувати onBackButtonPressed(). Він повертається, voidтому що йому просто потрібно виконати дію в класі фрагменту, і не потрібно ретрансляцію поглинання преси назад до Activity. Переконайтеся, що ви зателефонували до onBackPressed()методу " Активність", якщо все, що ви робите за допомогою кнопки "Назад", не потребує обробки, інакше кнопка "назад" буде відключена ... і ви цього не хочете!

Застереження Як ви бачите, це встановлює ключового слухача на кореневий вигляд фрагмента, і нам потрібно буде зосередити його. Якщо у вашому фрагменті є тексти редагування (або будь-які інші погляди на крадіжку фокусу), які розширюють цей клас (або інші внутрішні фрагменти чи представлення, які мають однакові), вам потрібно буде обробити це окремо. Існує хороша стаття про розширення EditText, щоб втратити фокус на зворотному натисканні.

Сподіваюся, хтось вважає це корисним. Щасливе кодування.


Дякую, це приємне рішення. Чи можете ви сказати, чому ви телефонуєте super.onBackPressed();двічі?
CoolMind

Це викликається лише один раз за сценарій щодо нульового стану currentFragment. Якщо фрагмент не є нульовим, а фрагмент реалізує Backableінтерфейс, не вживаються дії. Якщо фрагмент не є нульовим і він НЕ реалізується Backable, ми називаємо метод super. Якщо фрагмент недійсний, він переходить до останнього супервиклику.
mwieczorek

Вибачте, я не зрозумів. У випадку, коли a currentFragmentне є нульовим і є екземпляром Backable і він від'єднується (ми натискаємо backкнопку і закриваємо фрагмент), super.onBackPressed();викликається перша поява , а потім друга.
CoolMind

Відмінне рішення
Девід Толедо

Можливо, "Closeable" звучить трохи краще, ніж "Backable"
pumnao

15

Рішення просте:

1) Якщо у вас клас базового фрагмента, який розширюються всі фрагменти, додайте цей код до свого класу, інакше створіть такий клас базового фрагмента

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

2) У класі активності перейдіть на onBackPress так:

private BaseFragment _currentFragment;

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

3) У класі «Фрагмент» додайте бажаний код:

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


9

onBackPressed() призвести до відриву фрагмента від діяльності.

Відповідно до відповіді @Sterling Diaz, я думаю, він має рацію. АЛЕ якась ситуація буде неправильною. (наприклад, екран обертання)

Отже, я думаю, ми могли б виявити, чи isRemoving()потрібно досягти цілей.

Ви можете написати його в onDetach()або onDestroyView(). Це робота.

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

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

8

Ну я це зробив так, і це працює на мене

Простий інтерфейс

FragmentOnBackClickInterface.java

public interface FragmentOnBackClickInterface {
    void onClick();
}

Приклад реалізації

MyFragment.java

public class MyFragment extends Fragment implements FragmentOnBackClickInterface {

// other stuff

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

то просто перекрийте onBackPress у діяльності

    @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;
}

Не працює ! super.onbackpression () називається завжди :(
Animesh Mangla

Як пройти цю подію Внутрішній фрагмент. У мене є ViewPager у діяльності, і у ViewPager є фрагменти, тепер усі ці фрагменти мають дочірній фрагмент. Як передати подію кнопки "назад" фрагментам цієї дитини.
Кішань Вагела

@KishanVaghela добре, що я відтоді не торкався андроїда, але в мене є одна ідея. Дайте мені 24-ту, щоб оновити свою відповідь.
Błażej

Ок, немає проблем. Один варіант - мені потрібно передати події слухача кожному дочірньому фрагменту з батьківського фрагмента. але це не ідеальний спосіб, тому що мені потрібно зробити це для кожного фрагмента.
Кішань Вагела

@KishanVaghela Я думав про менеджера в діяльності. Ви можете додати / видалити фрагменти за допомогою цього інтерфейсу. Але при такому підході вам належить реалізувати менеджер у кожному фрагменті, який має підфрагменти. Тож можливо кращим рішенням буде створення менеджера як одиночного. Тоді вам доведеться додати / вилучити фрагменти / об’єкти і в 'onBackPress' ви викликаєте метод 'onBackPress' цього менеджера. Цей метод викличе метод "onClick" у кожному об'єкті, який було додано до менеджера. Вам це більш-менш зрозуміло?
Błażej

8

Ви повинні додати інтерфейс до свого проекту, як показано нижче;

public interface OnBackPressed {

     void onBackPressed();
}

І тоді ви повинні реалізувати цей інтерфейс на своєму фрагменті;

public class SampleFragment extends Fragment implements OnBackPressed {

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

}

І ви можете запустити цю подію onBackPress під час своєї події onBackPress, як показано нижче;

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

Це метод agood, але будьте обережні, не дзвоніть, getActivity().onBackPressed();оскільки він викликатиме виняток: "java.lang.StackOverflowError: розмір стека 8MB".
CoolMind

8

Це лише невеликий код, який зробить трюк:

 getActivity().onBackPressed();

Сподіваюся, це комусь допоможе :)


4
Він попросив перекрити поведінку, а не просто посилатися на метод діяльності.
mwieczorek

2
Я прочитав це уважно, дякую. Ваше рішення просто повертає метод onBackPress назад до Діяльності, коли він запитав, як перевизначити.
mwieczorek

7

Якщо ви використовуєте EventBus, це, мабуть, набагато простіше рішення:

У вашому фрагменті:

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

і у вашому класі активності ви можете визначити:

@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));
}

BackPressMessage.java - це лише об'єкт POJO

Це дуже чисто і немає ніяких проблем з інтерфейсом / реалізацією.


7

це моє рішення:

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

та у фрагменті:

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

        // some codes

        return true;
    }
});

5

Використовуючи навігаційний компонент, ви можете це зробити так :

Java

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()
}
...
}

Котлін

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

Як щодо використання onDestroyView ()?

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

5
як щодо переходу на інший фрагмент із фактичного фрагмента, що буде з використанням вашого методу? :)
Чолецькі

Найкорисніша відповідь. Це те саме, що писати i = iабо 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;
        }
    });
}

Тільки якщо фрагменти не мають зосередженого перегляду (наприклад, редагування тексту), нехай фрагменти обробляють саму кнопку назад, як це відповідає. В іншому випадку нехай активність, що містить фрагменти, робить це OnBackPress (), коли написана перша відповідь; оскільки фокусируемый вигляд вкраде фокус фрагмента. Однак ми можемо відновити фокус для фрагмента, чітко фокусуючи всі фокусирувані погляди методом clearFocus () і переорієнтуватися на фрагмент.
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();
    }
}

у свій фрагмент додайте наступне, не забудьте реалізувати інтерфейс основної діяльності.

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

У моєму рішенні (Котлін);

Я використовую функцію onBackAlternative як параметр BaseActivity .

BaseActivity

abstract class BaseActivity {

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

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

У мене є функція для встановлення onBackPressAlternative на 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
     }
}

Тоді мій onBackPressAlternative готовий до використання на фрагментах.

Підфрагменти

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

3

Не застосовуйте метод ft.addToBackStack (), щоб після натискання кнопки назад ваша діяльність закінчилася.

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


3

Просто виконайте наступні дії:

Завжди додаючи фрагмент,

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

Тоді в основній діяльності переосмислюйте onBackPressed()

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

Щоб обробити кнопку "назад" у вашому додатку,

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

Це воно!


3

У життєвому циклі діяльності завжди кнопка "android" повертається до транзакцій FragmentManager, коли ми використовували FragmentActivity або AppCompatActivity.

Для обробки рюкзака нам не потрібно обробляти його кількість заздалегідь чи мітки, але нам слід тримати фокус під час додавання чи заміни фрагмента. Знайдіть такі фрагменти для обробки кнопок назад,

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

Тут я не додаю зворотний стек для свого домашнього фрагмента, оскільки це домашня сторінка моєї програми. Якщо додати addToBackStack до HomeFragment, програма чекатиме, щоб видалити весь фрагмент в активності, тоді ми отримаємо порожній екран, тому я зберігаю наступну умову,

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

Тепер ви можете бачити раніше доданий фрагмент про активність, і додаток вийде, коли дійшли до HomeFragment. ви також можете переглянути наступні фрагменти.

@Override
public void onBackPressed() {

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

3

Фрагмент: Зробіть метод розміщення BaseFragment:

 public boolean onBackPressed();

Діяльність:

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

Ваша діяльність буде перебігати на додані та видимі фрагменти та зателефонувати на onBackPress () на кожному з них та перервати, якщо один із них поверне «true» (це означає, що він оброблявся, тому подальших дій не буде).


Сер, це найелегантніший спосіб і працює ідеально!
asozcan

3

Згідно з нотатками до випуску AndroidX , androidx.activity 1.0.0-alpha01випущений і представлений ComponentActivityновий базовий клас існуючих FragmentActivityі AppCompatActivity. І цей випуск приносить нам нову функцію:

Тепер ви можете зареєструвати послугу OnBackPressedCallbackvia, addOnBackPressedCallbackщоб отримувати onBackPressed()зворотні дзвінки, не потребуючи перекриття методу у вашій діяльності.


Чи можете ви показати приклад цього? Я не отримую цього методу для вирішення.
Брайан Брайс

1
@BryanBryce Я не знайшов прикладу, але офіційного документа: developer.android.com/reference/androidx/activity/…
TonnyL

Метод не вирішить b / c AppCompatActivity насправді поширюється на androidx.core.app.ComponentActivity замість androidx.activity.ComponentActivity.
wooldridgetm

3

Ви можете зареєструвати фрагмент у діяльності для обробки зворотного натискання:

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

interface BackPressHandler {
    fun onBackPressed(): Boolean
}

використання:

У фрагменті:

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

У діяльності:

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

Забезпечити користувальницьку зворотну навігацію за допомогою OnBackPress тепер простіше з зворотними зворотами всередині фрагмента.

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
    )
    ...
}

Якщо ви хочете, щоб дія за замовчуванням була заснована на певній умові, ви можете використовувати:

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

3

Всередині методу onCreate фрагмента додайте наступне:

@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);
}

після додавання цього myFragment backpress працює, але тепер активність backpress didnt
Sunil Chaudhary

2

Найкраще рішення,

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;
                }

            }
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.