Отдельный Back Stack для каждой вкладки в Android с использованием фрагментов - PullRequest
154 голосов
/ 08 августа 2011

Я пытаюсь реализовать вкладки для навигации в приложении для Android.Так как TabActivity и ActivityGroup устарели, я хотел бы реализовать это с использованием фрагментов.

Я знаю, как настроить один фрагмент для каждой вкладки, а затем переключать фрагменты при нажатии на вкладку.Но как я могу иметь отдельный задний стек для каждой вкладки?

Например, фрагменты A и B будут находиться под вкладкой 1, а фрагменты C и D - под вкладкой 2. Когда приложение запускается, отображается фрагмент A иВкладка 1 выбрана.Затем фрагмент A можно заменить фрагментом B. Если выбрана вкладка 2, должен отображаться фрагмент C.Если выбрана вкладка 1, фрагмент B должен снова отображаться.На этом этапе должна быть возможность использовать кнопку «Назад» для отображения фрагмента А.

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

BR Martin

Ответы [ 12 ]

134 голосов
/ 17 октября 2012

Я ужасно опоздал на этот вопрос.Но так как эта тема была очень информативной и полезной для меня, я подумал, что лучше разместить здесь свои два пенса

Мне нужен был такой экран (минималистичный дизайн с 2 вкладками и 2 представлениями на каждой вкладке),

tabA
    ->  ScreenA1, ScreenA2
tabB
    ->  ScreenB1, ScreenB2

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

Так вот как я это сделал.

1.Создайте базовый класс фрагментов

public class BaseFragment extends Fragment {
    AppMainTabActivity mActivity;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mActivity = (AppMainTabActivity) this.getActivity();
    }

    public void onBackPressed(){
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data){
    }
}

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

2. Создайте несколько идентификаторов табуляции, доступных везде в проекте

public class AppConstants{
    public static final String TAB_A  = "tab_a_identifier";
    public static final String TAB_B  = "tab_b_identifier";

    //Your other constants, if you have them..
}

тут нечего объяснять ..

3.Хорошо, главная вкладка. Пожалуйста, просмотрите комментарии в коде ..

public class AppMainFragmentActivity extends FragmentActivity{
    /* Your Tab host */
    private TabHost mTabHost;

    /* A HashMap of stacks, where we use tab identifier as keys..*/
    private HashMap<String, Stack<Fragment>> mStacks;

    /*Save current tabs identifier in this..*/
    private String mCurrentTab;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_main_tab_fragment_layout);

        /*  
         *  Navigation stacks for each tab gets created.. 
         *  tab identifier is used as key to get respective stack for each tab
         */
        mStacks             =   new HashMap<String, Stack<Fragment>>();
        mStacks.put(AppConstants.TAB_A, new Stack<Fragment>());
        mStacks.put(AppConstants.TAB_B, new Stack<Fragment>());

        mTabHost                =   (TabHost)findViewById(android.R.id.tabhost);
        mTabHost.setOnTabChangedListener(listener);
        mTabHost.setup();

        initializeTabs();
    }


    private View createTabView(final int id) {
        View view = LayoutInflater.from(this).inflate(R.layout.tabs_icon, null);
        ImageView imageView =   (ImageView) view.findViewById(R.id.tab_icon);
        imageView.setImageDrawable(getResources().getDrawable(id));
        return view;
    }

    public void initializeTabs(){
        /* Setup your tab icons and content views.. Nothing special in this..*/
        TabHost.TabSpec spec    =   mTabHost.newTabSpec(AppConstants.TAB_A);
        mTabHost.setCurrentTab(-3);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_home_state_btn));
        mTabHost.addTab(spec);


        spec                    =   mTabHost.newTabSpec(AppConstants.TAB_B);
        spec.setContent(new TabHost.TabContentFactory() {
            public View createTabContent(String tag) {
                return findViewById(R.id.realtabcontent);
            }
        });
        spec.setIndicator(createTabView(R.drawable.tab_status_state_btn));
        mTabHost.addTab(spec);
    }


    /*Comes here when user switch tab, or we do programmatically*/
    TabHost.OnTabChangeListener listener    =   new TabHost.OnTabChangeListener() {
      public void onTabChanged(String tabId) {
        /*Set current tab..*/
        mCurrentTab                     =   tabId;

        if(mStacks.get(tabId).size() == 0){
          /*
           *    First time this tab is selected. So add first fragment of that tab.
           *    Dont need animation, so that argument is false.
           *    We are adding a new fragment which is not present in stack. So add to stack is true.
           */
          if(tabId.equals(AppConstants.TAB_A)){
            pushFragments(tabId, new AppTabAFirstFragment(), false,true);
          }else if(tabId.equals(AppConstants.TAB_B)){
            pushFragments(tabId, new AppTabBFirstFragment(), false,true);
          }
        }else {
          /*
           *    We are switching tabs, and target tab is already has atleast one fragment. 
           *    No need of animation, no need of stack pushing. Just show the target fragment
           */
          pushFragments(tabId, mStacks.get(tabId).lastElement(), false,false);
        }
      }
    };


    /* Might be useful if we want to switch tab programmatically, from inside any of the fragment.*/
    public void setCurrentTab(int val){
          mTabHost.setCurrentTab(val);
    }


    /* 
     *      To add fragment to a tab. 
     *  tag             ->  Tab identifier
     *  fragment        ->  Fragment to show, in tab identified by tag
     *  shouldAnimate   ->  should animate transaction. false when we switch tabs, or adding first fragment to a tab
     *                      true when when we are pushing more fragment into navigation stack. 
     *  shouldAdd       ->  Should add to fragment navigation stack (mStacks.get(tag)). false when we are switching tabs (except for the first time)
     *                      true in all other cases.
     */
    public void pushFragments(String tag, Fragment fragment,boolean shouldAnimate, boolean shouldAdd){
      if(shouldAdd)
          mStacks.get(tag).push(fragment);
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      if(shouldAnimate)
          ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }


    public void popFragments(){
      /*    
       *    Select the second last fragment in current tab's stack.. 
       *    which will be shown after the fragment transaction given below 
       */
      Fragment fragment             =   mStacks.get(mCurrentTab).elementAt(mStacks.get(mCurrentTab).size() - 2);

      /*pop current fragment from stack.. */
      mStacks.get(mCurrentTab).pop();

      /* We have the target fragment in hand.. Just show it.. Show a standard navigation animation*/
      FragmentManager   manager         =   getSupportFragmentManager();
      FragmentTransaction ft            =   manager.beginTransaction();
      ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
      ft.replace(R.id.realtabcontent, fragment);
      ft.commit();
    }   


    @Override
    public void onBackPressed() {
        if(mStacks.get(mCurrentTab).size() == 1){
          // We are already showing first fragment of current tab, so when back pressed, we will finish this activity..
          finish();
          return;
        }

        /*  Each fragment represent a screen in application (at least in my requirement, just like an activity used to represent a screen). So if I want to do any particular action
         *  when back button is pressed, I can do that inside the fragment itself. For this I used AppBaseFragment, so that each fragment can override onBackPressed() or onActivityResult()
         *  kind of events, and activity can pass it to them. Make sure just do your non navigation (popping) logic in fragment, since popping of fragment is done here itself.
         */
        ((AppBaseFragment)mStacks.get(mCurrentTab).lastElement()).onBackPressed();

        /* Goto previous fragment in navigation stack of this tab */
            popFragments();
    }


    /*
     *   Imagine if you wanted to get an image selected using ImagePicker intent to the fragment. Ofcourse I could have created a public function
     *  in that fragment, and called it from the activity. But couldn't resist myself.
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(mStacks.get(mCurrentTab).size() == 0){
            return;
        }

        /*Now current fragment on screen gets onActivityResult callback..*/
        mStacks.get(mCurrentTab).lastElement().onActivityResult(requestCode, resultCode, data);
    }
}

4. app_main_tab_fragment_layout.xml (на случай, если кому-то интересно.)

<?xml version="1.0" encoding="utf-8"?>
<TabHost
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/tabhost"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+android:id/realtabcontent"
            android:layout_width="fill_parent"
            android:layout_height="0dp"
            android:layout_weight="1"/>

        <TabWidget
            android:id="@android:id/tabs"
            android:orientation="horizontal"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_weight="0"/>

    </LinearLayout>
</TabHost>

5.AppTabAFirstFragment.java (первый фрагмент на вкладке A, аналогично для всех вкладок)

public class AppTabAFragment extends BaseFragment {
    private Button mGotoButton;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view       =   inflater.inflate(R.layout.fragment_one_layout, container, false);

        mGoToButton =   (Button) view.findViewById(R.id.goto_button);
        mGoToButton.setOnClickListener(listener);

        return view;
    }

    private OnClickListener listener        =   new View.OnClickListener(){
        @Override
        public void onClick(View v){
            /* Go to next fragment in navigation stack*/
            mActivity.pushFragments(AppConstants.TAB_A, new AppTabAFragment2(),true,true);
        }
    }
}

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

РЕДАКТИРОВАТЬ:

Если кто-то хочет полный проект, я перенес пример проекта на github .

93 голосов
/ 20 сентября 2011

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

Мы взломали решение, используя смесь TabActivity, FragmentActivities (мы использовали библиотеку поддержки для фрагментов) и Fragments. В ретроспективе, я почти уверен, что это было не лучшее архитектурное решение, но нам удалось заставить его работать. Если бы мне пришлось делать это снова, я бы, вероятно, попытался бы сделать решение, основанное на более активных действиях (без фрагментов), или попытаться иметь только одну операцию для вкладок, и позволить всем остальным быть представлениями (которые, как я считаю, являются гораздо более многократного использования, чем деятельность в целом).

Итак, требовалось, чтобы на каждой вкладке было несколько вкладок и вложенных экранов:

tab 1
  screen 1 -> screen 2 -> screen 3
tab 2
  screen 4
tab 3
  screen 5 -> 6

и т.д ...

Так сказать: пользователь начинает с вкладки 1, переходит с экрана 1 на экран 2, затем на экран 3, затем он переключается на вкладку 3 и перемещается с экрана 4 на 6; если он переключился обратно на вкладку 1, он должен снова увидеть экран 3, а если он нажал «Назад», он должен вернуться к экрану 2; Назад снова, и он на экране 1; перейдите на вкладку 3, и он снова на экране 6.

Основным действием в приложении является MainTabActivity, которое расширяет TabActivity. Каждая вкладка связана с действием, скажем ActivityInTab1, 2 и 3. И тогда каждый экран будет фрагментом:

MainTabActivity
  ActivityInTab1
    Fragment1 -> Fragment2 -> Fragment3
  ActivityInTab2
    Fragment4
  ActivityInTab3
    Fragment5 -> Fragment6

Каждый ActivityInTab содержит только один фрагмент за раз и знает, как заменить один фрагмент на другой (почти так же, как ActvityGroup). Круто то, что таким способом довольно легко создавать отдельные задние стеки для каждой вкладки.

Функциональность каждого ActivityInTab была совершенно одинаковой: знать, как перемещаться от одного фрагмента к другому и поддерживать задний стек, поэтому мы поместили это в базовый класс. Давайте назовем это просто ActivityInTab:

abstract class ActivityInTab extends FragmentActivity { // FragmentActivity is just Activity for the support library.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_in_tab);
    }

    /**
     * Navigates to a new fragment, which is added in the fragment container
     * view.
     * 
     * @param newFragment
     */
    protected void navigateTo(Fragment newFragment) {
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();

        ft.replace(R.id.content, newFragment);

        // Add this transaction to the back stack, so when the user presses back,
        // it rollbacks.
        ft.addToBackStack(null);
        ft.commit();
    }

}

Activity_in_tab.xml - это просто:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/content"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:isScrollContainer="true">
</RelativeLayout>

Как видите, макет представления для каждой вкладки был одинаковым. Это потому, что это просто FrameLayout с именем content, который будет содержать каждый фрагмент. Фрагменты - это те, которые имеют вид каждого экрана.

Только для бонусных баллов мы также добавили небольшой код для отображения диалогового окна подтверждения, когда пользователь нажимает кнопку Назад, и больше нет фрагментов для возврата:

// In ActivityInTab.java...
@Override
public void onBackPressed() {
    FragmentManager manager = getSupportFragmentManager();
    if (manager.getBackStackEntryCount() > 0) {
        // If there are back-stack entries, leave the FragmentActivity
        // implementation take care of them.
        super.onBackPressed();
    } else {
        // Otherwise, ask user if he wants to leave :)
        showExitDialog();
    }
}

Это в значительной степени установка. Как вы можете видеть, каждая FragmentActivity (или просто просто Activity в Android> 3) берет на себя все бэк-стекирование со своим собственным FragmentManager.

Действие, такое как ActivityInTab1, будет действительно простым, оно просто покажет свой первый фрагмент (то есть экран):

public class ActivityInTab1 extends ActivityInTab {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        navigateTo(new Fragment1());
    }
}

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

// In Fragment1.java for example...
// Need to navigate to Fragment2.
((ActivityIntab) getActivity()).navigateTo(new Fragment2());

Так что это в значительной степени так. Я почти уверен, что это не очень каноническое (и в основном не очень хорошее) решение, поэтому я хотел бы спросить опытных разработчиков Android, как лучше подходить к этой функциональности, а если нет, то как готово »в Android, я был бы признателен, если бы вы указали мне какую-нибудь ссылку или материал, который объясняет, каким является способ Android для этого (вкладки, вложенные экраны во вкладках и т. д.). Не стесняйтесь разрывать этот ответ в комментариях:)

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


Наконец, если вам нужно пережить изменения ориентации, важно, чтобы ваши фрагменты создавались с помощью setArguments / getArguments. Если вы установите переменные экземпляра в конструкторы ваших фрагментов, вы будете испорчены. Но, к счастью, это действительно легко исправить: просто сохраните все в setArguments в конструкторе и затем извлеките эти вещи с помощью getArguments в onCreate, чтобы использовать их.

23 голосов
/ 17 августа 2011

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

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

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

6 голосов
/ 19 апреля 2014

Хранение сильных ссылок на фрагменты не является правильным способом.

FragmentManager предоставляет putFragment(Bundle, String, Fragment) и saveFragmentInstanceState(Fragment).

Либо одного достаточно для реализации обратного стека.


Используя putFragment, вместо замены фрагмента вы отсоединяете старый и добавляете новый. Это то, что платформа делает для замены транзакции, которая добавляется в backstack. putFragment сохраняет индекс для текущего списка активных фрагментов, и эти фрагменты сохраняются платформой во время изменения ориентации.

Второй способ, используя saveFragmentInstanceState, сохраняет все состояние фрагмента в Bundle, позволяя вам действительно удалить его, а не отсоединять. Использование этого подхода облегчает манипулирование задним стеком, поскольку вы можете извлекать фрагмент в любое время.


Я использовал второй метод для этого варианта использования:

SignInFragment ----> SignUpFragment ---> ChooseBTDeviceFragment
               \                          /
                \------------------------/

Я не хочу, чтобы пользователь возвращался к экрану регистрации с третьего, нажав кнопку назад. Я также делаю анимацию между ними (используя onCreateAnimation), поэтому хакерские решения не будут работать, по крайней мере, если пользователь не заметит, что что-то не так.

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

private static final String STATE_BACKSTACK = "SetupActivity.STATE_BACKSTACK";

private MyBackStack mBackStack;

@Override
protected void onCreate(Bundle state) {
    super.onCreate(state);

    if (state == null) {
        mBackStack = new MyBackStack();

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.add(R.id.act_base_frg_container, new SignInFragment());
        tr.commit();
    } else {
        mBackStack = state.getParcelable(STATE_BACKSTACK);
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable(STATE_BACKSTACK, mBackStack);
}

private void showFragment(Fragment frg, boolean addOldToBackStack) {
    final FragmentManager fm = getSupportFragmentManager();
    final Fragment oldFrg = fm.findFragmentById(R.id.act_base_frg_container);

    FragmentTransaction tr = fm.beginTransaction();
    tr.replace(R.id.act_base_frg_container, frg);
    // This is async, the fragment will only be removed after this returns
    tr.commit();

    if (addOldToBackStack) {
        mBackStack.push(fm, oldFrg);
    }
}

@Override
public void onBackPressed() {
    MyBackStackEntry entry;
    if ((entry = mBackStack.pop()) != null) {
        Fragment frg = entry.recreate(this);

        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction tr = fm.beginTransaction();
        tr.replace(R.id.act_base_frg_container, frg);
        tr.commit();

        // Pop it now, like the framework implementation.
        fm.executePendingTransactions();
    } else {
        super.onBackPressed();
    }
}

public class MyBackStack implements Parcelable {

    private final List<MyBackStackEntry> mList;

    public MyBackStack() {
        mList = new ArrayList<MyBackStackEntry>(4);
    }

    public void push(FragmentManager fm, Fragment frg) {
        push(MyBackStackEntry.newEntry(fm, frg);
    }

    public void push(MyBackStackEntry entry) {
        if (entry == null) {
            throw new NullPointerException();
        }
        mList.add(entry);
    }

    public MyBackStackEntry pop() {
        int idx = mList.size() - 1;
        return (idx != -1) ? mList.remove(idx) : null;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        final int len = mList.size();
        dest.writeInt(len);
        for (int i = 0; i < len; i++) {
            // MyBackStackEntry's class is final, theres no
            // need to use writeParcelable
            mList.get(i).writeToParcel(dest, flags);
        }
    }

    protected MyBackStack(Parcel in) {
        int len = in.readInt();
        List<MyBackStackEntry> list = new ArrayList<MyBackStackEntry>(len);
        for (int i = 0; i < len; i++) {
            list.add(MyBackStackEntry.CREATOR.createFromParcel(in));
        }
        mList = list;
    }

    public static final Parcelable.Creator<MyBackStack> CREATOR =
        new Parcelable.Creator<MyBackStack>() {

            @Override
            public MyBackStack createFromParcel(Parcel in) {
                return new MyBackStack(in);
            }

            @Override
            public MyBackStack[] newArray(int size) {
                return new MyBackStack[size];
            }
    };
}

public final class MyBackStackEntry implements Parcelable {

    public final String fname;
    public final Fragment.SavedState state;
    public final Bundle arguments;

    public MyBackStackEntry(String clazz, 
            Fragment.SavedState state,
            Bundle args) {
        this.fname = clazz;
        this.state = state;
        this.arguments = args;
    }

    public static MyBackStackEntry newEntry(FragmentManager fm, Fragment frg) {
        final Fragment.SavedState state = fm.saveFragmentInstanceState(frg);
        final String name = frg.getClass().getName();
        final Bundle args = frg.getArguments();
        return new MyBackStackEntry(name, state, args);
    }

    public Fragment recreate(Context ctx) {
        Fragment frg = Fragment.instantiate(ctx, fname);
        frg.setInitialSavedState(state);
        frg.setArguments(arguments);
        return frg;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(fname);
        dest.writeBundle(arguments);

        if (state == null) {
            dest.writeInt(-1);
        } else if (state.getClass() == Fragment.SavedState.class) {
            dest.writeInt(0);
            state.writeToParcel(dest, flags);
        } else {
            dest.writeInt(1);
            dest.writeParcelable(state, flags);
        }
    }

    protected MyBackStackEntry(Parcel in) {
        final ClassLoader loader = getClass().getClassLoader();
        fname = in.readString();
        arguments = in.readBundle(loader);

        switch (in.readInt()) {
            case -1:
                state = null;
                break;
            case 0:
                state = Fragment.SavedState.CREATOR.createFromParcel(in);
                break;
            case 1:
                state = in.readParcelable(loader);
                break;
            default:
                throw new IllegalStateException();
        }
    }

    public static final Parcelable.Creator<MyBackStackEntry> CREATOR =
        new Parcelable.Creator<MyBackStackEntry>() {

            @Override
            public MyBackStackEntry createFromParcel(Parcel in) {
                return new MyBackStackEntry(in);
            }

            @Override
            public MyBackStackEntry[] newArray(int size) {
                return new MyBackStackEntry[size];
            }
    };
}
5 голосов
/ 17 июня 2014

Это может быть легко достигнуто с ChildFragmentManager

Вот пост об этом со связанным проектом.посмотрите,

http://tausiq.wordpress.com/2014/06/06/android-multiple-fragments-stack-in-each-viewpager-tab/

2 голосов
/ 10 декабря 2016

Это сложная проблема, поскольку Android обрабатывает только 1 задний стек, но это возможно. Мне понадобилось несколько дней, чтобы создать библиотеку с названием Tab Stacker, которая будет делать именно то, что вы ищете: историю фрагментов для каждой вкладки. Он имеет открытый исходный код и полностью документирован, и может быть легко включен с Gradle. Вы можете найти библиотеку на github: https://github.com/smart-fun/TabStacker

Вы также можете загрузить пример приложения, чтобы увидеть, что поведение соответствует вашим потребностям:

https://play.google.com/apps/testing/fr.arnaudguyon.tabstackerapp

Если у вас есть какие-либо вопросы, не стесняйтесь оставлять письма.

2 голосов
/ 24 декабря 2015

Я хотел бы предложить свое собственное решение на тот случай, если кто-то ищет и хочет попробовать выбрать лучшее для его / ее нужд.

https://github.com/drusak/tabactivity

Цель создания библиотеки довольно банальна - реализовать ее как iPhone.

Основные преимущества:

  • использовать библиотеку android.support.design с TabLayout;
  • каждая вкладка имеет свой собственный стек с использованием FragmentManager (без сохранения ссылок на фрагменты);
  • поддержка глубоких ссылок (когда вам нужно открыть определенную вкладку и определенный уровень фрагмента в ней);
  • сохранение / восстановление состояний вкладок;
  • адаптивные методы жизненного цикла фрагментов во вкладках;
  • довольно легко реализовать для ваших нужд.

Надеюсь, это кому-нибудь поможет.

Спасибо!

2 голосов
/ 27 марта 2013

У меня была точно такая же проблема, и я реализовал проект github с открытым исходным кодом, который охватывает сложную навигацию по вкладкам, назад и вверх и хорошо протестирован и задокументирован:

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

Это простая и небольшая структура для навигации по вкладкам и переключения фрагментов, а также для навигации вверх и назад.Каждая вкладка имеет свой собственный стек фрагментов.Он использует ActionBarSherlock и совместим до уровня API 8.

2 голосов
/ 11 августа 2012

Отказ от ответственности:


Я чувствую, что это лучшее место для публикации соответствующего решения, над которым я работал, для решения проблемы аналогичного типа, которая кажется довольно стандартной для Android.Это не решит проблему для всех, но может помочь некоторым.


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

Вот описание одного из возможных примеров этого подхода:

У меня естьприложение, которое использует ListViews.Каждый элемент в списке является родителем с некоторым количеством детей.При нажатии на элемент необходимо открыть новый список с этими дочерними элементами на той же вкладке ActionBar, что и исходный список.Эти вложенные списки имеют очень похожую компоновку (возможно, некоторые условные изменения здесь и там), но данные отличаются.

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

1) Создатьновый адаптер с соответствующими полями «to» и «from», которые будут соответствовать новым представлениям элементов, добавляемых в список, и столбцам, возвращаемым новым курсором.

2) Установите этот адаптер в качестве нового адаптера дляthe ListView.

3) Создайте новый URI на основе элемента, по которому щелкнули, и перезапустите загрузчик курсора с новым URI (и проекцией).В этом примере URI сопоставляется с конкретными запросами с аргументами выбора, переданными из UI.

4) Когда новые данные были загружены из URI, поменяйте местами курсор, связанный с адаптером, на новыйкурсор, и затем список обновится.

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

Если выпопадайте в похожую ситуацию, обязательно прочитайте документы: http://developer.android.com/guide/topics/ui/layout/listview.html

http://developer.android.com/reference/android/support/v4/app/LoaderManager.LoaderCallbacks.html

Надеюсь, это кому-нибудь поможет!

1 голос
/ 19 октября 2014

Простое решение:

Каждый раз, когда вы меняете вызов с представлением tab / root:

fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);

Это очистит BackStack.Не забудьте вызвать это, прежде чем изменять корневой фрагмент.

И добавьте фрагменты с этим:

FragmentTransaction transaction = getFragmentManager().beginTransaction();
NewsDetailsFragment newsDetailsFragment = NewsDetailsFragment.newInstance(newsId);
transaction.add(R.id.content_frame, newsDetailsFragment).addToBackStack(null).commit();

Обратите внимание, что .addToBackStack(null) и transaction.add можно, например, изменить с помощью transaction.replace.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...