Нам пришлось реализовать то же поведение, которое вы недавно описали для приложения. Экраны и общий поток приложения уже были определены, поэтому нам пришлось придерживаться его (это клон приложения для 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, чтобы использовать их.