Как реализовать onBackPressed () во фрагментах? - PullRequest
391 голосов
/ 27 марта 2011

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

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

Ответы [ 40 ]

276 голосов
/ 22 июля 2014

Я решил переопределить таким образом onBackPressed в Деятельности. Все FragmentTransaction addToBackStack перед коммитом:

@Override
public void onBackPressed() {

    int count = getSupportFragmentManager().getBackStackEntryCount();

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

}
109 голосов
/ 26 сентября 2017

Я считаю, что лучшим решением будет

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
        }
    }
}
92 голосов
/ 02 октября 2013

Согласно ответу @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;
    }
    

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

77 голосов
/ 20 марта 2015

Это сработало для меня: 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;
        }
    });
}
64 голосов
/ 10 августа 2011

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

Редактировать: я хотел бы добавить свой предыдущий ответ.

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

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

45 голосов
/ 14 апреля 2014

Ничто из этого не легко реализовать и не будет работать оптимальным образом.

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

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

ЭТО ДЕЛАЕТ РАБОТУ.

23 голосов
/ 20 октября 2013

Просто добавьте addToBackStack во время перехода между фрагментами, как показано ниже:

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

, если вы напишите addToBackStack(null), он будет обрабатывать его сам, но если вы дадите тег, вы должны обработатьэто вручную.

20 голосов
/ 19 января 2017

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

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

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

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

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, вернуть ноль. Мы проверим нулевое значение позже ...

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

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

Второй вариант, чтобы не задействовать Задание

Временами вы не хотите, чтобы 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. Убедитесь, что вы вызываете метод Activity onBackPressed(), если все, что вы делаете с кнопкой возврата, не требует обработки, в противном случае кнопка возврата будет отключена ... и вам не нужно что!

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

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

14 голосов
/ 08 июля 2014

Решение простое:

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();
 }
8 голосов
/ 24 ноября 2014

onBackPressed() вызывает отрыв фрагмента от действия.

Согласно ответу @Sterling Diaz, я думаю, что он прав.НО какая-то ситуация будет неправильной.(напр. Поворот экрана)

Итак, я думаю, мы могли бы определить, нужно ли isRemoving() достичь цели.

Вы можете написать это в onDetach() или onDestroyView().Это работа.

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

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