ActionBar на Android 2.1+ с помощью Support Library. Часть 2 - Навигация

Aug 20, 2013 17:58


Привет, Хабр!

В предыдущей статье я рассказал о добавлении 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 sp_adapter = new ArrayAdapter(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:

screen_2.xml:

screen_3.xml:

Изменим 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); } Послесловие

Ну вот в общем-то и всё, что я хотел сказать. Статья получилась большая, но надеюсь полезная)

via habrahabr

Originally published at ALeXVoz Blog. You can comment here or there.

2.1, android, actionbar, Android-программирование

Previous post Next post
Up