Отдельный задний стек для каждой вкладки на Android с помощью фрагментов

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

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

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

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

BR Martin

Solutions Collecting From Web of "Отдельный задний стек для каждой вкладки на Android с помощью фрагментов"

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

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

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

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

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

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

У меня были такие же требования в прошлом, и я сделал это с помощью TabActivityGroup (который тоже был устаревшим) и Activities. На этот раз я хотел использовать Фрагменты.

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

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() об использовании 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. Ok, Main Tab Activity. Прочитайте комментарии в коде ..

 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 (Первый фрагмент в Tab A, simliar для всех вкладок)

 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 .

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

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

Таким образом, требования должны были иметь некоторые вкладки и вложенные экраны на каждой вкладке:

 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, называемый контентом, который будет содержать каждый фрагмент. Фрагменты – это те, которые имеют представление каждого экрана.

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

 // 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 in Android> 3) заботится обо всех back-stacking с собственным 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, чтобы использовать их.

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

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

Либо одного достаточно, чтобы реализовать заднюю часть.


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

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


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

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

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

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

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

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

Вот сообщение об этом с соответствующим проектом. взглянуть,

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

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


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


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

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

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

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

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

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

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

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

Я надеюсь, что это помогает кому-то!

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

https://github.com/SebastianBaltesObjectCode/PersistentFragmentTabs

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

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

https://github.com/drusak/tabactivity

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

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

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

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

Благодаря!

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

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

 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 .

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

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

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

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

Эта тема была очень интересной и полезной.
Благодаря Krishnabhadra для вашего объяснения и кода, я использую ваш код и немного улучшаю, позволяя сохранять стеки, currentTab и т. Д. Из конфигурации изменений (в основном, вращающихся).
Протестировано на реальных устройствах 4.0.4 и 2.3.6, не протестированных на эмуляторе

Я изменяю эту часть кода на «AppMainTabActivity.java», остальные остаются неизменными. Возможно, Кришнабхадра добавит это в свой код.

Восстановить данные onCreate:

 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 */ //if we are recreating this activity... if (savedInstanceState!=null) { mStacks = (HashMap<String, Stack<Fragment>>) savedInstanceState.get("stack"); mCurrentTab = savedInstanceState.getString("currentTab"); } else { 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.setup(); initializeTabs(); //set the listener the last, to avoid overwrite mCurrentTab everytime we add a new Tab mTabHost.setOnTabChangedListener(listener); } 

Сохраните переменные и поставьте в Bundle:

  //Save variables while recreating @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putSerializable("stack", mStacks); outState.putString("currentTab", mCurrentTab); //outState.putInt("tabHost",mTabHost); } 

Если существует предыдущая CurrentTab, установите это, иначе создайте новую Tab_A:

 public void initializeTabs(){ /* Setup your tab icons and content views.. Nothing special in this..*/ TabHost.TabSpec spec = mTabHost.newTabSpec(AppConstants.TAB_A); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_a_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_b_state_btn)); mTabHost.addTab(spec); //if we have non default Tab as current, change it if (mCurrentTab!=null) { mTabHost.setCurrentTabByTag(mCurrentTab); } else { mCurrentTab=AppConstants.TAB_A; pushFragments(AppConstants.TAB_A, new AppTabAFirstFragment(), false,true); } } 

Надеюсь, это поможет другим людям.

Я бы порекомендовал не использовать backstack на основе HashMap> есть много ошибок в режиме «Не сохранять активности». Он не будет правильно восстанавливать состояние, если вы глубоко в стеке фрагмента. А также будет трепетать в вложенном фрагменте карты (с exeption: Fragment не найдено ни одного представления для ID). Coz HashMap> после фона \ foreground приложение будет null

Я оптимизирую код выше для работы с backstack фрагмента

Это нижний TabView

Основной вид деятельности

 import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.widget.ImageView; import android.widget.TabHost; import android.widget.TextView; import com.strikersoft.nida.R; import com.strikersoft.nida.abstractActivity.BaseActivity; import com.strikersoft.nida.screens.tags.mapTab.MapContainerFragment; import com.strikersoft.nida.screens.tags.searchTab.SearchFragment; import com.strikersoft.nida.screens.tags.settingsTab.SettingsFragment; public class TagsActivity extends BaseActivity { public static final String M_CURRENT_TAB = "M_CURRENT_TAB"; private TabHost mTabHost; private String mCurrentTab; public static final String TAB_TAGS = "TAB_TAGS"; public static final String TAB_MAP = "TAB_MAP"; public static final String TAB_SETTINGS = "TAB_SETTINGS"; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR); getActionBar().hide(); setContentView(R.layout.tags_activity); mTabHost = (TabHost) findViewById(android.R.id.tabhost); mTabHost.setup(); if (savedInstanceState != null) { mCurrentTab = savedInstanceState.getString(M_CURRENT_TAB); initializeTabs(); mTabHost.setCurrentTabByTag(mCurrentTab); /* when resume state it's important to set listener after initializeTabs */ mTabHost.setOnTabChangedListener(listener); } else { mTabHost.setOnTabChangedListener(listener); initializeTabs(); } } private View createTabView(final int id, final String text) { 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)); TextView textView = (TextView) view.findViewById(R.id.tab_text); textView.setText(text); return view; } /* create 3 tabs with name and image and add it to TabHost */ public void initializeTabs() { TabHost.TabSpec spec; spec = mTabHost.newTabSpec(TAB_TAGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_tag_drawable, getString(R.string.tab_tags))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_MAP); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_map_drawable, getString(R.string.tab_map))); mTabHost.addTab(spec); spec = mTabHost.newTabSpec(TAB_SETTINGS); spec.setContent(new TabHost.TabContentFactory() { public View createTabContent(String tag) { return findViewById(R.id.realtabcontent); } }); spec.setIndicator(createTabView(R.drawable.tab_settings_drawable, getString(R.string.tab_settings))); mTabHost.addTab(spec); } /* first time listener will be trigered immediatelly after first: mTabHost.addTab(spec); for set correct Tab in setmTabHost.setCurrentTabByTag ignore first call of listener */ TabHost.OnTabChangeListener listener = new TabHost.OnTabChangeListener() { public void onTabChanged(String tabId) { mCurrentTab = tabId; if (tabId.equals(TAB_TAGS)) { pushFragments(SearchFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_MAP)) { pushFragments(MapContainerFragment.getInstance(), false, false, null); } else if (tabId.equals(TAB_SETTINGS)) { pushFragments(SettingsFragment.getInstance(), false, false, null); } } }; /* Example of starting nested fragment from another fragment: Fragment newFragment = ManagerTagFragment.newInstance(tag.getMac()); TagsActivity tAct = (TagsActivity)getActivity(); tAct.pushFragments(newFragment, true, true, null); */ public void pushFragments(Fragment fragment, boolean shouldAnimate, boolean shouldAdd, String tag) { FragmentManager manager = getFragmentManager(); FragmentTransaction ft = manager.beginTransaction(); if (shouldAnimate) { ft.setCustomAnimations(R.animator.fragment_slide_left_enter, R.animator.fragment_slide_left_exit, R.animator.fragment_slide_right_enter, R.animator.fragment_slide_right_exit); } ft.replace(R.id.realtabcontent, fragment, tag); if (shouldAdd) { /* here you can create named backstack for realize another logic. ft.addToBackStack("name of your backstack"); */ ft.addToBackStack(null); } else { /* and remove named backstack: manager.popBackStack("name of your backstack", FragmentManager.POP_BACK_STACK_INCLUSIVE); or remove whole: manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); */ manager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } ft.commit(); } /* If you want to start this activity from another */ public static void startUrself(Activity context) { Intent newActivity = new Intent(context, TagsActivity.class); newActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(newActivity); context.finish(); } @Override public void onSaveInstanceState(Bundle outState) { outState.putString(M_CURRENT_TAB, mCurrentTab); super.onSaveInstanceState(outState); } @Override public void onBackPressed(){ super.onBackPressed(); } } 

tags_activity.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="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_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:background="@drawable/bg_main_app_gradient" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <TabWidget android:id="@android:id/tabs" android:background="#EAE7E1" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0"/> </LinearLayout> </TabHost> 

tags_icon.xml

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/tabsLayout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/bg_tab_gradient" android:gravity="center" android:orientation="vertical" tools:ignore="contentDescription" > <ImageView android:id="@+id/tab_icon" android:layout_marginTop="4dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tab_text" android:layout_marginBottom="3dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="@color/tab_text_color"/> </LinearLayout> 

Введите описание изображения здесь