В предыдущей статье я рассказал о добавлении Support Library в ваш проект и привёл простой пример SupportActionBar. Но очень часто ActionBar используется не только как замена меню, но и как способ навигации по приложению. Под катом написано, как её реализовать.
Способы навигации
У ActionBar есть 3 способа навигации:
NAVIGATION_MODE_STANDART – по сути вообще не навигация, просто ActionBar с элементами;
NAVIGATION_MODE_LIST – вместо заголовка выпадающий список;
NAVIGATION_MODE_TABS – вкладки под ActionBar.
Выпадающий список
Давайте не будем ничего создавать, а возьмём проект из предыдущей статьи. Создадим новый класс – ScreenFragment, он будет аналогом разных экранов приложения:
import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; public class ScreenFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { TextView tv = new TextView(getActivity()); tv.setText("Screen " + getArguments().getInt(MainActivity.key_screen_number)); tv.setTextSize(30); return tv; } }
Я не стал создавать отдельный xml-файл разметки, он здесь не особо нужен. Мы берём из аргументов номер экрана и вставляем его в программно созданный TextView, который потом показываем.
Изменим код метода onCreate() и добавим ещё один в MainActivity:
public static final String key_screen_number = "key_screen_number"; ActionBar ab; FragmentTransaction ft; ScreenFragment screen_fragment; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ab = getSupportActionBar(); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); String[] screens = new String[] {"Screen 1", "Screen 2", "Screen 3"}; ArrayAdapter<String> sp_adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, screens); sp_adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); ab.setListNavigationCallbacks(sp_adapter, this); selected_list_item_position = -1; ab.setSelectedNavigationItem(0); } public boolean onNavigationItemSelected(int position, long id) { ft = getSupportFragmentManager().beginTransaction(); screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, position + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); ft.commit(); return true; }
В onCreate мы говорим ActionBar, что будем использовать метод навигации – список, и подготавливаем адаптер для него, а также присваиваем обработчик событий. У него всего один метод — onNavigationItemSelected(int position, long id). Он вызывается, когда пользователь выбирает какой–нибудь элемент выпадающего списка. Здесь мы создаём новый ScreenFragment и даём ему номер экрана, чтобы он мог его показать. Затем начинаем FragmentTransaction и добавляем этот фрагмент в View с id=android.support.v7.appcompat.R.id.action_bar_activity_content. Это FrameLayout, куда добавляется наш layout из setContentView(). Запускаем приложение и выбираем различные экраны:
В качестве разметки для элементов выпадающего списка я использую системный layout, но он выглядит не очень красиво. Поэтому лучше использовать свой. За его добавление отвечает метод Adapter.setDropDownViewResource().
Вкладки
Чтобы изменить способ навигации на вкладки, подправим MainActivity:
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ab = getSupportActionBar(); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); Tab tab = ab.newTab(); tab.setText("Screen 1"); tab.setTabListener(this); ab.addTab(tab, 0, true); tab = ab.newTab(); tab.setText("Screen 2"); tab.setTabListener(this); ab.addTab(tab, 1, false); tab = ab.newTab(); tab.setText("Screen 3"); tab.setTabListener(this); ab.addTab(tab, 2, false); }
Также нужно сделать MainActivity… implements… TabListener. Это обработчик нажатий на вкладки. У него есть целых 3 метода:
onTabUnselected(Tab tab, FragmentTransaction ft) — вызывается, когда текущая вкладка закрывается;
onTabSelected(Tab tab, FragmentTransaction ft) — вызывается, когда открывается новая вкладка (срабатывает сразу после предыдущего);
onTabReselected(Tab tab, FragmentTransaction ft) — когда пользователь нажимает на уже открытую вкладку:
public void onTabUnselected(Tab tab, FragmentTransaction ft) { } public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); } public void onTabReselected(Tab tab, FragmentTransaction ft) { }
Здесь нам уже не нужно создавать FragmentTransaction, она даётся нам изначально (предполагается, что мы будем работать с фрагментами). Но для этой FragmentTransaction нельзя вызывать методы addToBackStack() и commit(). Также у нас есть нажатая вкладка, из которой мы можем вытащить всё, что нужно — текст, иконку, позицию и т.д.
Вкладкам можно присваивать свой View, если системный вас не устраивает — setCustomView(int layoutResId)
Запускаем приложение, щёлкаем по вкладкам:
Кстати, если вкладок очень много, то их заголовки можно скроллить по горизонтали (как в Google Play), но ниже заголовков свайп не работает.
Дополнение к «Выпадающий список»
Скорее всего, при нажатии на уже выбранный элемент навигации, на экране ничего не нужно менять. Ну, со вкладками всё понятно — не трогать метод onTabReselected() и всё. А как же быть со списком? Всё очень просто: добавляем в MainActivity переменную
private int selected_list_item_position;
И изменяем код onNavigationItemSelected(int position, long id):
public boolean onNavigationItemSelected(int position, long id) { if (position != selected_list_item_position) { ft = getSupportFragmentManager().beginTransaction(); screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, position + 1); screen_fragment.setArguments(args); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); ft.commit(); selected_list_item_position = position; return true; } return false; }
Теперь новый экран будет открываться только при выборе не открытого элемента навигации.
Меню
На разных вкладках обычно размещается разный контент, и меню для него должно быть разным. Ребята из гугла сделали такую возможность. Далее я буду показывать всё на примере вкладок. Добавим в ScreenFragment следующий код:
public static final String key_menu_resource = "key_menu_resource"; @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(getArguments().getInt(key_menu_resource), menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); Log.d("MENU", "Cliced MenuItem is " + item.getTitle() + " (from ScreenFragment)"); return true; }
Создадим в папке res/menu/ три файла:
screen_1.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item1" android:title="Item 1" android:icon="@android:drawable/ic_menu_add"/> <item android:id="@+id/settings" android:title="Settings" android:icon="@android:drawable/ic_menu_edit"/> </menu>
screen_2.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item2" android:title="Item 2" android:icon="@android:drawable/ic_menu_camera"/> <item android:id="@+id/settings" android:title="Settings" android:icon="@android:drawable/ic_menu_edit"/> </menu>
screen_3.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/item3" android:title="Item 3" android:icon="@android:drawable/ic_menu_call" /> </menu>
Изменим onTabSelected():
private int[] menu_resources = new int[] {R.menu.screen_1, R.menu.screen_2, R.menu.screen_3}; public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = new ScreenFragment(); Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); args.putInt(ScreenFragment.key_menu_resource, menu_resources[tab.getPosition()]); screen_fragment.setArguments(args); screen_fragment.setHasOptionsMenu(true); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); }
Теперь нужно удалить (ну или лучше закомментить) метод onCreateOptionsMenu — он нам сейчас будет только мешать. И onOptionsItemSelected() в MainActivity тоже подправим:
@Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() != R.id.settings) { return false; } else { Log.d("MENU", "Cliced MenuItem is " + item.getTitle() + " (from MainActivity)"); return true; } }
Сейчас поясню, что я здесь накодил. Дело в том, что во фрагменте тоже можно создавать меню. Чтобы оно было видно, нужно вызывать метод Fragment.setHasOptionsMenu(true). Если мы создаём меню не в Activity, а в фрагменте, то метод onOptionsItemSelected() вызывается сначала в MainActivity, а лишь затем в ScreenFragment, если в Activity возвращается false. Здесь вместо if должно быть switch/case, в конце каждого case — return true; Это значит, что мы уже обработали нажатие и не нужно вызывать onOptionsItemSelected во фрагменте. Например, на каждой вкладке есть пункт меню «Настройки». Чтобы не набирать код в каждом фрагменте, при нажатии на этот пункт возвращаем true. Тогда onOptionsItemSelected() вызывается только в Activity, где мы можем открыть новую SettingsActivity, например. Если запустить программу и на разных вкладках нажимать кнопку «Меню» на устройстве, то будут показаны разные элементы.
При нажатии на пункты меню в логах будет не только их имя, но и в каком классе были обработаны нажатия. А можно вообще создать отдельный xml-файл в папке res/menu/ с этим самым элементом Settings, а в MainActivity в методе onCreateOptionsMenu() создавать меню из этого файла. Тогда 2 меню как бы объединятся, и будут видны пункты обоих.
Сохранение состояния
Часто бывает, что при переключении между вкладками состояние контента на них должно сохраняться. Для этого у фрагментов есть специальный метод — setRetainInstance(boolean retain). Если ему передать true в параметре, то фрагмент не будет создаваться заново. Чтобы проверить это, перепишем метод onTabSelected() в MainActivity:
private int[] menu_resources = new int[] {R.menu.screen_1, R.menu.screen_2, R.menu.screen_3}; private ScreenFragment[] screens = new ScreenFragment[] {new ScreenFragment(), new ScreenFragment(), new ScreenFragment()}; public void onTabSelected(Tab tab, FragmentTransaction ft) { screen_fragment = screens[tab.getPosition()]; Bundle args = new Bundle(); args.putInt(key_screen_number, tab.getPosition() + 1); args.putInt(ScreenFragment.key_menu_resource, menu_resources[tab.getPosition()]); screen_fragment.setArguments(args); screen_fragment.setHasOptionsMenu(true); screen_fragment.setRetainInstance(true); ft.replace(android.support.v7.appcompat.R.id.action_bar_activity_content, screen_fragment); }
Послесловие
Ну вот в общем-то и всё, что я хотел сказать. Статья получилась большая, но надеюсь полезная)
ссылка на оригинал статьи http://habrahabr.ru/post/189678/
Добавить комментарий