Как реализовать onBackPressed () во фрагментах?

460

Есть ли способ, которым мы можем реализовать onBackPressed()в Android Fragment аналогично тому, как мы реализуем в Android Activity?

Так как Фрагмент жизненного цикла не имеет onBackPressed(). Есть ли другой альтернативный способ перегрузки onBackPressed()фрагментов Android 3.0?

Android_programmer_camera
источник
13
ИМХО, фрагмент не должен ни знать, ни заботиться о кнопке НАЗАД. Действие может заботиться о кнопке BACK, хотя с фрагментами, которые обычно обрабатываются FragmentManager. Но поскольку фрагмент не знает о своей более обширной среде (например, является ли он одним из нескольких в действии), для фрагмента на самом деле небезопасно пытаться определить, какова надлежащая функциональность BACK.
CommonsWare
61
Как насчет того, когда весь фрагмент делает это для отображения WebView, и вы хотите, чтобы WebView «возвращался» на предыдущую страницу при нажатии кнопки «Назад»?
Mharper
Ответ Майкла Хербига был идеальным для меня. Но для тех, кто не может использовать getSupportFragmentManager (). Используйте взамен getFragmentManager ().
Данталян
Ответ Дмитрия Зайцева на [Аналогичный вопрос] [1] работает просто отлично. [1]: stackoverflow.com/a/13450668/1372866
ашакиров
6
Чтобы ответить на mharper, вы можете сделать что-то вроде webview.setOnKeyListener (new OnKeyListener () {@Override public boolean onKey (View v, int keyCode, KeyEvent event) {if (keyCode == KeyEvent.KEYCODE_BACK && webview.canGoBack ()) {webview.goBack (); вернуть true;} вернуть false;}});
Омид Аминива

Ответы:

307

Я решил таким образом переопределить onBackPressedв Activity. Все FragmentTransactionэто , addToBackStackпрежде чем совершить:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

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

}
Hw.Master
источник
та же проблема stackoverflow.com/questions/32132623/…
Адитья Вьяс-Лахан
4
count == 1 позволит закрыть первый фрагмент одним нажатием кнопки назад. Но в остальном лучшее решение
Kunalxigxag
2
Если вы используете библиотеку поддержки v7 и ваша активность выходит за пределы FragmentActivity (или подкласса, такого как AppCompatActivity), это произойдет по умолчанию. См. FragmentActivity # onBackPressed
Сяо,
3
но вы не можете использовать переменные, классы и функции из класса фрагмента в этом коде
user25
2
@Prabs, если вы используете фрагмент поддержки v4, убедитесь, что вы используете getSupportFragmentManager (). GetBackStackEntryCount ();
Veer3383,
152

На мой взгляд, лучшее решение:

JAVA SOLUTION

Создать простой интерфейс:

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 в решении Kotlin, смешивание безопасных вызовов ( .?) и принудительное использование ненулевых ( !!) может привести к NPE - приведение не всегда безопасно. Я лучше напишу if ((fragment as? IOnBackPressed)?.onBackPressed()?.not() == true) { ... }или еще котлиней(fragment as? IOnBackPressed)?.onBackPressed()?.not()?.let { ... }
Antek
1
@Antek Здравствуйте, спасибо за ваше возвращение, вы могли бы отредактировать сообщение, чтобы внести исправления. Не судите строго общее решение.
Максим Jallu
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с дочерними фрагментами (как в примере с SlidingMenu lib). Вот шаги:

Сначала нам нужно создать интерфейс и класс, который реализует его интерфейс, чтобы иметь универсальный метод

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

Вуаля, теперь, когда ты щелкаешь обратно фрагментом, ты должен поймать свой метод на спине.

мертвая рыба
источник
Если более одного фрагмента является слушателем, как определить onBackPressed(), какой фрагмент отображался при нажатии кнопки «Назад»?
Аль Лелопат
2
вы можете настроить прослушиватель кликов вместо нового baseBackPressedListener и вызвать его как анонимного, чтобы установить собственное поведение, примерно так:((BaseActivity)activity).setOnBackPressedListener(new OnBackpressedListener(){ public void doBack() { //...your stuff here }});
deadfish
Спасибо, я изменил свою позицию по этому вопросу, хотя, я бы порекомендовал теперь отделить сообщения местной трансляции. Я думаю, что это дало бы более качественные компоненты, которые сильно отделены.
HaMMeReD
здесь isue здесь stackoverflow.com/questions/32132623/…
Адитья Вьяс-Лахан
Это отлично сработало для меня, я перезапускаю Activity
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, если он был расширен. Итак, сначала я должен был пометить чек, если (панель
открыта
18
Проблема с этим решением состоит в том, что getView () возвращает только основное представление (а не подпредставление, если они получают фокус). Так что, если у вас есть EditText и введите какой-то текст, затем нажмите «назад» один раз, чтобы скрыть клавиатуру, второе нажатие на «назад» инициируется из «EditText» (само представление), и этот метод, похоже, не перехватывает « затем нажмите кнопку «назад» (так как она ловит только основной вид). Насколько я понимаю, вы можете отлавливать события нажатия клавиш в «EditText» так же, как и в любых других представлениях, но если у вас «сложная» форма, тогда она не так чиста и ремонтопригодна, как могла бы быть.
Pelpotronic
Это отличное простое решение для кошмара - фрагментов. Если у вас есть «сложная» форма редактирования текста, то вам, вероятно, следует удалить свой проект и начать все сначала. Верните false, если вы хотите использовать стандартное поведение. Верните истину, когда вы перехватили нажатие на спину и что-то с этим
сделали
65

Если вам нужна такая функциональность, вам нужно переопределить ее в своей деятельности, а затем добавить YourBackPressedинтерфейс для всех ваших фрагментов, который вы вызываете для соответствующего фрагмента при каждом нажатии кнопки «Назад».

Изменить: я хотел бы добавить свой предыдущий ответ.

Если бы я делал это сегодня, я бы использовал трансляцию или, возможно, заказанную трансляцию, если бы ожидал, что другие панели будут обновляться в унисон на панель основного / основного контента.

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

чеканный
источник
4
Довольно изящная идея! некоторые дополнительные мысли: + важно реализовать эту логику с вещанием, идущим от действий> фрагментов (не наоборот). OnBackPressed доступен только на уровне активности, поэтому ключевым моментом здесь является отслеживание события и передача его всем «прослушивающим» фрагментам. + Использование EventBus (как у Отто Square) делает реализацию для такого случая тривиальной!
Каушик Гопал
2
Я еще не использовал EventBus от Square, но буду в будущих продуктах. Я думаю, что это лучшее решение на основе чистой Java, и если вы можете отключить Android-разделы вашей архитектуры, тем лучше.
HaMMeReD
Как вы позволите только верхним фрагментам обрабатывать событие? Например, вы хотели бы уничтожить фрагмент, показанный в данный момент
Евгений
Приемник вещания должен быть привязан к жизненному циклу, если вы его используете, поэтому, когда он не виден, он не должен получать событие.
HaMMeReD
Также обратите внимание, что LocalBroadcastManagerне может делать заказанные трансляции
Xiao
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.

aaronmarino
источник
Если вы используете 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), он будет обрабатывать его сам, но если вы дадите тег, вы должны обработать его вручную.

Rudi
источник
Есть ли что-то еще, что нужно сделать, или достаточно добавить метод addToBackStack?
Сричаран Десабаттула
1
если вы просто добавите, что он должен работать, если он все еще не работает, в вашем коде должно быть что-то. Оставьте код где-нибудь, чтобы увидеть пожалуйста @SreecharanDesabattula
Руди
@Rudi Вы можете сказать stackoverflow.com/questions/32132623/…
Адитья Вьяс-Лахан
20

Поскольку этому вопросу и некоторым ответам уже более пяти лет, позвольте мне поделиться своим решением. Это продолжение и модернизация в ответ от @oyenigun

ОБНОВИТЬ: В нижней части этой статьи я добавил альтернативную реализацию, использующую абстрактное расширение Fragment, которое вообще не будет включать Activity, что было бы полезно для любого с более сложной иерархией фрагментов, включающей вложенные фрагменты, которые требуют различного обратного поведения.

Мне нужно было реализовать это, потому что некоторые из фрагментов, которые я использую, имеют меньшие представления, которые я хотел бы отклонить с помощью кнопки «Назад», такие как небольшие информационные представления, которые появляются, и т. Д., Но это хорошо для всех, кому нужно переопределить поведение кнопка возврата внутри фрагментов.

Сначала определите интерфейс

public interface Backable {
    boolean onBackPressed();
}

Этот интерфейс, который я называю Backable(я сторонник соглашений об именах), имеет единственный метод, onBackPressed()который должен возвращать booleanзначение. Нам нужно ввести логическое значение, потому что нам нужно будет знать, «нажата» ли кнопка «назад», «поглотила» ли она событие назад. Возврат trueозначает, что он есть, и никаких дальнейших действий не требуется, в противном случае он falseговорит, что обратное действие по умолчанию все еще должно иметь место. Этот интерфейс должен быть его собственным файлом (желательно в отдельном названном пакете interfaces). Помните, что разделение ваших классов на пакеты - это хорошая практика.

Во-вторых, найдите верхний фрагмент

Я создал метод, который возвращает последний Fragmentобъект в заднем стеке. Я использую теги ... если вы используете ID, внесите необходимые изменения. У меня есть этот статический метод в служебном классе, который имеет дело с состояниями навигации и т. Д., Но, конечно, поместите его там, где он вам больше подходит. Для назидания я поместил свой класс в класс 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, вернуть ноль. Мы проверим нулевое значение позже ...

В-третьих, реализовать в фрагменте

Реализуйте 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 , чтобы событие «Назад» было поглощено. В противном случае верните false.

Наконец, в вашей деятельности ...

Переопределите 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()и можем вернуться. В противном случае рассматривайте это как обычное нажатие назад и вызывайте метод super.

Второй вариант - не привлекать активность

Иногда вы не хотите, чтобы Activity обрабатывал это вообще, и вам нужно обрабатывать это непосредственно внутри фрагмента. Но кто сказал, что у вас не может быть фрагментов с API для обратной печати? Просто расширьте свой фрагмент до нового класса.

Создайте абстрактный класс, который расширяет Fragment и реализует 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()метод Activity, если все, что вы делаете с кнопкой «назад», не требует обработки, в противном случае кнопка «Назад» будет отключена ... и вам это не нужно!

Предостережения Как вы можете видеть, это устанавливает ключевой слушатель на корневой вид фрагмента, и нам нужно сфокусировать его. Если в вашем фрагменте, который расширяет этот класс (или в других внутренних фрагментах или представлениях, имеющих одинаковые значения), есть тексты редактирования (или любые другие представления для кражи фокуса), вам придется обрабатывать это отдельно. Есть хорошая статья о расширении EditText, чтобы потерять фокус на спине.

Я надеюсь, что кто-то найдет это полезным. Удачного кодирования.

mwieczorek
источник
Спасибо, это хорошее решение. Можете ли вы сказать, почему вы звоните super.onBackPressed();дважды?
CoolMind
Он вызывается только один раз за сценарий относительно нулевого состояния currentFragment. Если фрагмент не нулевой и фрагмент реализует Backableинтерфейс, никаких действий не предпринимается. Если фрагмент не нулевой и он НЕ реализуется Backable, мы вызываем метод super. Если фрагмент нулевой, он переходит к последнему супер вызову.
mwieczorek
Извините, я не поняла. В случае, когда a currentFragmentне равно NULL и является экземпляром 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) В вашем классе Activity переопределите onBackPressed следующим образом:

private BaseFragment _currentFragment;

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

3) В классе Your Fragment добавьте нужный код:

 @Override
 public void onBackPressed()
 {
    setUpTitle();
 }
Idot
источник
stackoverflow.com/questions/32132623/…
Адитья Вьяс-Лахан
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?
}

затем просто переопределить onBackPressed в активности

    @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;
}
Błażej
источник
Не работает ! super.onbackpressed () вызывается всегда :(
Animesh Mangla
Как пройти это мероприятие Внутренний фрагмент. У меня есть ViewPager в Activity, и этот ViewPager имеет фрагменты, теперь все эти фрагменты имеют дочерний фрагмент. Как передать событие кнопки назад этим дочерним фрагментам.
Кишан Вагела
@KishanVaghela хорошо, я не касался андроида с тех пор, но у меня есть одна идея. Дайте мне 24, чтобы обновить мой ответ.
Błażej
Хорошо, нет проблем. Один из вариантов - мне нужно передать событие слушателя каждому дочернему фрагменту из родительского фрагмента. но это не идеальный способ, потому что мне нужно делать это для каждого фрагмента.
Кишан Вагела
@KishanVaghela Я думал о менеджере в деятельности. Вы можете добавлять / удалять фрагменты с этим интерфейсом. Но при таком подходе вы должны внедрить менеджер в каждый фрагмент, имеющий подфрагменты. Поэтому, возможно, лучшим решением будет создание менеджера в виде синглтона. Затем вам нужно будет добавить / удалить фрагменты / объекты и в 'onBackPressed' вы вызываете метод 'onBackPressed' этого менеджера. Этот метод будет вызывать метод «onClick» в каждом объекте, который был добавлен в менеджер. Это более или менее ясно для вас?
Błażej
8

Вы должны добавить интерфейс к вашему проекту, как показано ниже;

public interface OnBackPressed {

     void onBackPressed();
}

И затем, вы должны реализовать этот интерфейс на вашем фрагменте;

public class SampleFragment extends Fragment implements OnBackPressed {

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

}

И вы можете вызвать это событие onBackPressed под вашим действием onBackPressed, как показано ниже;

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();
        }
}
oyenigun
источник
Это хороший метод, но будьте осторожны, не вызывайте getActivity().onBackPressed();его, так как он вызовет исключение: "java.lang.StackOverflowError: размер стека 8 МБ".
CoolMind
8

Это всего лишь небольшой код, который сделает свое дело:

 getActivity().onBackPressed();

Надеюсь, это поможет кому-то :)

Шахид Сарвар
источник
4
Он попросил переопределить поведение, а не просто вызывать метод деятельности.
mwieczorek
2
Я внимательно прочитал, спасибо. Ваше решение просто возвращает метод onBackPressed обратно в Activity, когда он спрашивает, как переопределить.
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));
}

BackPressedMessage.java это просто объект POJO

Это супер чистый и нет проблем с интерфейсом / реализацией.

Akshat
источник
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;
    }
});
Хамидреза Самади
источник
6

Используя компонент навигации, вы можете сделать это так :

Ява

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 () и перефокусировать на фрагмент.
Нгуен Тан Дат
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();
    }
}

в вашем фрагменте добавьте следующее, не забудьте реализовать интерфейс 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;
}

}
joelvarma
источник
4

В моем решении (Kotlin);

Я использую функцию 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();
Мухаммед Амир Али
источник
скажите мне stackoverflow.com/questions/32132623/…
Адитья Вьяс-Лахан
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.

Чтобы обработать backstack, нам не нужно обрабатывать его подсчет backstack или помечать что-либо, но мы должны сохранять фокус при добавлении или замене фрагмента. Пожалуйста, найдите следующие фрагменты, чтобы обработать кнопки возврата,

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

Теперь вы можете увидеть ранее добавленный фрагмент на acitvity и приложение выйдет при достижении HomeFragment. Вы также можете посмотреть на следующие фрагменты.

@Override
public void onBackPressed() {

    if (mDrawerLayout.isDrawerOpen(Gravity.LEFT)) {
        closeDrawer();
    } else {
        super.onBackPressed();
    }
}
Deva
источник
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();
}

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

Альесио Карвалью
источник
Сэр, это самый элегантный способ и прекрасно работает!
asozcan
3

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

Теперь вы можете зарегистрировать OnBackPressedCallbackканал addOnBackPressedCallbackдля получения onBackPressed()обратных вызовов без необходимости переопределять метод в вашей деятельности.

TonnyL
источник
Можете ли вы показать пример этого? Я не получаю этот метод для решения.
Брайан Брайс
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

Предоставление пользовательской обратной навигации с помощью onBackPressed теперь стало проще с обратными вызовами внутри фрагмента.

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();
krishnakumarcn
источник
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 работает, но теперь обратное действие не выполнялось
Сунил Чаудхари
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;
                }

            }
        }
    }
}
Шервин Гариб
источник