Меню вибору опцій (Options Menu)

За меню відповідає клас android.view.Menu. Кожна активність пов'язана з одним об'єктом меню. Саме меню містить пункти меню (клас android.view.MenuItem) і підменю (клас android.view.SubMenu).

При натисканні кнопки Menu на старих телефонах з'являється набір пунктів меню, що прикріплюється до активності. Меню може містити значки. Таке меню може містити шість пунктів (як правило). При наявності більше шести пунктів використовується розширене меню - в цьому випадку замість шостого пункту з'являється пункт Опції (More). При натисканні даного пункту показується розширене меню зі списком пунктів, які не помістилися в основній частині меню вибору опцій.

Коли меню відкривається вперше, Android викликає метод onCreateOptionsMenu(), передаючи в якості параметра об'єкт Menu. Меню можна створювати у вигляді ресурсів в XML-файлі або використовувати метод add().

У стандартному проекті при виборі звичайного шаблону вже є заготівка для меню з одного пункту Settings і виклик методу для меню (ви про це вже знаєте).

Створення меню за допомогою ресурсів

Розглянемо роботу з меню через ресурси. Для створення меню використовуються ресурси, які повинні зберігатися в XML-файлі. Сам файл повинен знаходитися в папці res/menu/ вашого проекту. Меню складається з наступних елементів:

<menu>
Визначає меню, яке буде містити пункти меню. Елемент <menu> повинен бути корінним елементом XML-структури файлу і може містити один або кілька елементів <item> та <group>.
<item>
Створює безпосередньо пункти меню. Даний елемент може мати вкладений елемент <menu> для створення підменю.
<group>
При бажанні можете також використовувати невидимий контейнер для елементів <item>. Це дозволяє досягти деяких ефектів.

Припустимо, ми вирішили використати меню для якоїсь гри. Створимо новий файл game_menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/new_game"
android:title="@string/new_game" />
<item android:id="@+id/help"
android:title="@string/help />
</menu>

Ми створили меню з двома пунктами. Кожен пункт включає в себе наступні атрибути:

android:id
Ідентифікатор пункту меню, за яким програма може розпізнати при виділенні пункту меню користувачем.
android:title
Текст, який буде відображатися в меню.

Існують і інші атрибути елемента item, наприклад android:icon="@drawable/home" дозволить також вивести значок для пункту меню, а android:enabled="false" дозволяє зробити пункт меню недоступним.

Атрибут android:titleCondensed застосовується в тому випадку, якщо звичайний заголовок надто широкий і не «вміщується» в вибраному елементі меню.

Атрибут android:orderInCategory визначає порядок, в якому відображаються елементи меню MenuItems.

До речі, ви можете використовувати вбудовані значки Android. Наприклад, android:icon="@android:drawable/ic_menu_help" дозволить вам вивести значок допомоги, який зашитий в систему.

При створенні меню ми вказали на рядкові ресурси @string/new_game і @string/help. Необхідно додати нові рядки у файлі strings.xml:

<string name="new_game">Нова гра</string>
<string name="help">Довідка</string>

Тепер потрібно внести зміни у класі активності, в якому буде виводитися меню. Програма повинна сконвертувати створений нами ресурс меню в програмний об'єкт. Для цієї мети існує спеціальний метод MenuInflater.inflate(), який викликається в спеціальному методі зворотного виклику onCreateOptionsMenu(). Даний метод призначений для виведення меню при натисканні кнопки MENU на пристрої:

@Override
public boolean onCreateOptionsMenu(Menu menu)  {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R. menu.game_menu, menu);
return true;
}

Після вставки коду середовище розробки попросить імпортувати відсутні простори імен.

import android.view.Menu;
import android.view.MenuInflater;

Метод onCreateOptionsMenu() метод ініціює першу появу меню на екрані і приймає як параметр об'єкт Menu (для старих пристроїв). Ви можете зберегти посилання на меню і використовувати її в будь-якому місці коду, поки метод onCreateOptionsMenu() знову не буде викликаний. Вам необхідно завжди використовувати реалізацію цього обробника з батьківського класу, бо вона при необхідності автоматично включає в меню додаткові системні пункти. У нових пристроях метод викликається при створенні активності. Метод повинен повертати значення true, щоб меню було видимим на екрані.

Запустивши програму, натисніть кнопку MENU на емуляторі, щоб побачити створене меню.

 

 

 

 

 

Метод getMenuInflater() повертає екземпляр класу MenuInflater, який ми використовуємо для читання даних з XML.

Як бачите, меню з'являється в нижній частині екрана. Всього можна вивести на екран шість пунктів меню. Якщо пунктів більше, то буде виведено п'ять пунктів плюс шостий пункт More, який дозволить побачити інші пункти. Давайте перевіримо і додамо нові пункти меню.

Спочатку додамо шість пунктів.

 

 

 

 

 

Додамо ще один пункт до меню, щоб їх стало сім.

 

 

 

 

 

Вибір пунктів меню

Ми навчилися створювати меню. Але поки воно марно, так як пункти меню ніяк не реагують на наші натискання. Для обробки натискань пунктів меню служить метод onOptionsItemSelected(). Метод розпізнає пункт, вибраний користувачем, через MenuItem. Ми можемо тепер визначити вибраний пункт через виклик getItemId(), який повертає ідентифікатор пункту меню. Далі через оператор switch нам залишається визначити потрібні команди:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Операції для вибраного пункту меню
switch (item.getItemId()) {
case R. id.new_game:
newGame();
return true;
case R. id.help:
showHelp();
return true;
default:
return super.onOptionsItemSelected(item);
}}

public void newGame() {
edtext.setText("Обраний пункт Нова гра");
}

public void showHelp() {
edtext.setText("Обраний пункт Довідка");
}

Запустіть програму, відкрийте меню і виберіть перший або другий пункт меню. У текстовому полі повинно з'явитися повідомлення.

 

 

 

 

 

У наведеному прикладі getItemId() запитує ID для вибраного пункту меню і починає порівнювати через оператор вибору switch з ідентифікаторами, які ми поставили в XML-ресурсах. При виявленні потрібного ідентифікатора виконується обробник для заданого пункту меню. Якщо програма не знайде, то виконується оператор default, який повертає super class.

В Android 3.0 можна додати атрибут android:onClick в ресурсах меню, і вам вже не потрібно використовувати onOptionsItemSelected(). За допомогою android:onClick ви можете вказати потрібний метод при виборі пункту меню.

// у атрибута пункту меню встановлено значення android:onClick="onMenuClick"
public void onMenuClick(MenuItem item) {
edtext.setText("Обраний пункт Нагодувати кота");
}

Програмне створення меню

Розглянемо програмне створення меню. Нам знадобиться визначити кілька констант для пунктів меню:

// ідентифікатори для пунктів меню
private static final int IDM_OPEN = 101;
private static final int IDM_SAVE = 102;

public boolean onCreateOptionsMenu(Menu menu) {
// додаємо пункти меню
menu.add(Menu.NONE, IDM_OPEN, Menu.NONE, "Відкрити");
menu.add(Menu.NONE, IDM_SAVE, Menu.NONE, "Зберегти");
}

У метода add() є чотири параметри:

Ідентифікатор групи - дозволяє пов'язувати пункт меню з групою інших пунктів цього меню;
Ідентифікатор пункту для обробника події вибору пункту меню;
Порядок розташування пункту в меню - дозволяє визначати позицію меню. За замовчуванням (Menu.NONE або 0) пункти йдуть в тому порядку, як встановлено в коді;
Заголовок - текст, який виводиться в пункті меню. Можна використовувати рядковий ресурс.

Метод повертає об'єкт MenuItem, який можна використовувати для установки додаткових властивостей, наприклад, щоб встановити значок, гарячу клавішу і т. д.

Якщо ви хочете створити меню зі значками, то скористайтеся методом setIcon().

menu.add(Menu.NONE, IDM_OPEN, Menu.NONE, "Відкрити").setIcon(R. drawable.icon_menu_open);

Метод onCreateOptionsMenu викликається системою тільки один раз при створенні меню. Якщо вам потрібно оновити меню під час роботи програми, то використовуйте метод зворотного виклику onPrepareOptionsMenu().

При виборі пункту меню викликається метод onOptionsItemSelected, який передає об'єкт MenuItem - пункт меню, вибраний користувачем. За допомогою методу getItemId можна отримати ідентифікатор вибраного пункту меню. Після ідентифікації пункту меню можна написати код для обробки події вибору меню:

public boolean onOptionsItemSelected(MenuItem item) {
switсh (item.getItemId())
case IDM_OPEN:
return true;
case IDM_SAVE:
return true;
return false;
}

Гарячі клавіші

Також можна задати гарячі клавіші для швидкого доступу, використовуючи символи клавіатури, за допомогою декількох методів:

setAlphabeticShortcut(char) - додає символ;
setNumericShortcut(int) - додає число;
setShortcut(char, int) - додає комбінацію символа та числа.

Наприклад, якщо задати гарячу клавішу setAlphabeticShortcut('q');, то при відкритті меню (або при утримуванні клавіші MENU) натискання клавіші Q вибере даний пункт меню. Ця гаряча клавіша (або поєднання клавіш) буде показана як підказка, що відображена нижче ім'я пункту меню. У нових клавіатурах є окрема клавіша Ctrl, яка працює также, як на звичайних клавіатурах.

Гарячі клавіші можна створити і через XML: android:alphabeticShortcut="c".

Обробляти натискання можна через метод активності onKeyShortcut():

@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_R:
Toast.makeText(this, "Reply", Toast.LENGTH_SHORT).show();
return true;
default:
return super.onKeyShortcut(keyCode, event);
}}

Створення підменю

Підменю можна додати до будь-якого меню, крім іншого підменю. Підменю створюється в методі зворотного виклику onCreateOptionsMenu() за допомоги методу addSubMenu(), який повертає об'єкт SubMenu. В об'єкт SubMenu можна додати додаткові пункти цього меню, використовуючи метод add(). Наприклад:

public static final int IDM_HELP = 101; 
public static final int IDM_NEW = 201; 
public static final int IDM_OPEN = 202; 
public static final int IDM_SAVE = 203; 
public static final int IDM_CUT = 301; 
public static final int IDM_COPY = 302; 
public static final int IDM_PASTE = 303; 

@Override 
public boolean onCreateOptionsMenu(Menu menu) {
SubMenu subMenuFile = menu.addSubMenu("Файл");
subMenuFile.add(Menu.NONE, IDM_NEW, Menu.NONE, "Новий");
subMenuFile.add(Menu.NONE, IDM_OPEN, Menu.NONE, "Відкрити");
subMenuFile.add(Menu.NONE, IDM_SAVE, Menu.NONE, "Зберегти");
SubMenu subMenuEdit = menu.addSubMenu("Правка");
subMenuEdit.add(Menu.NONE, IDM_CUT, Menu.NONE, "Вирізати");
subMenuEdit.add(Menu.NONE, IDM_COPY, Menu.NONE, "Копіювати");
subMenuEdit.add(Menu.NONE, IDM_PASTE, Menu.NONE, "Вставити");
menu.add(Menu.NONE, IDM_HELP, Menu.NONE, "Довідка");
 return super.onCreateOptionsMenu(menu); 
}

@Override 
public boolean onOptionsItemSelected(MenuItem item) {
CharSequence message;
switch (item.getItemId()) {
case IDM_NEW:
message = "вибрати пункт Новий";
break;
case IDM_OPEN:
message = "вибрати пункт Відкрити";
break;
case IDM_SAVE:
message = "вибрати пункт Зберегти";
break;
case IDM_CUT:
message = "вибрати пункт Вирізати";
break;
case IDM_COPY:
message = "вибрати пункт Копіювати";
break;
case IDM_PASTE:
message = "вибрати пункт Вставити";
break;
case IDM_HELP:
message = "вибрати пункт "Довідка";
break;
default:
return false;
}
// виводимо повідомлення по вибраному пункті меню
Toast toast = Toast.makeText(this, message, Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
return true;
}

Тепер при виборі пункту меню з'явиться ще одне вікно з підменю.

Додавання прапорців і перемикачів

До пунктів меню можливе додавання прапорців або перемикачів. Щоб додати прапорець або перемикач для окремого елемента меню, необхідно використовувати метод setCheckable():

MenuItem item = menu.add(0, IDM_FORMAT_BOLD, 0, "Bold");
item.setCheckable(true);

Якщо є необхідність додати декілька пунктів меню з прапорцями або перемикачами, можна об'єднати їх у групу меню, створивши окремий ідентифікатор. Пункт меню додається до групи через метод add(), передаючи йому в якості першого параметра ідентифікатор групи меню. Припустимо, ми оголосили ідентифікатори для групи меню Колір і елементів меню для установки кольору:

public static final int IDM_COLOR_GROUP = 400;
public static final int IDM_COLOR_RED = 401;
public static final int IDM_COLOR_GREEN = 402;
public static final int IDM_COLOR_BLUE = 403;

Тепер для створення групи меню з прапорцями потрібно призначити ідентифікатор групи на кожен пункт меню і викликати метод setGroupCheckable() для всієї групи (в цьому випадку немає необхідності викликати метод setCheckable() для кожного пункту меню):

SubMenu subMenuColor = menu.addSubMenu("Колір");
subMenuColor.add(IDM_COLOR_GROUP, IDM_COLOR_RED, Menu.NONE, "Червоний");
subMenuColor.add(IDM_COLOR_GROUP, IDM_COLOR_GREEN, Menu.NONE,"Зелений");
subMenuColor.add(IDM_COLOR_GROUP, IDM_COLOR_BLUE, Menu.NONE, "Синій");
subMenuColor.setGroupCheckable(IDM_COLOR_GROUP, true, false);

У методу setGroupCheckable() три параметра:

перший параметр — ідентифікатор групи меню;
другий параметр — true, якщо в групі можливі перемикачі або прапорці;
третій параметр — встановлює єдиний (true) або множинний (false) вибір пунктів меню. Цей параметр фактично визначає зовнішній вигляд меню — це меню буде з перемикачами або прапорцями.

Для управління станом прапорців і перемикачів в оброблювачі події вибору пункту меню треба написати наступне:

@Override
public boolean onOptionsItemSelected(MenuItem item){
CharSequence message;
switch (item.getItemId()) {
...
case IDM_COLOR_RED:
// інвертуємо стан прапорця
item.setChecked(!item.isChecked());
message = "Червоний колір";
break;
default:
return false;
}

Запустіть проект, викличте меню і виберіть пункт меню Колір. У вас з'явиться підменю з трьома пунктами (Червоний, Зелений, Синій) у вигляді прапорців. Стан прапорців і перемикачів обробляється в коді програми і зберігається при повторних викликів меню.

Можна відразу призначити намір вибраного пункту меню через метод setIntent(), яке спрацює при натисканні цього пункту, якщо ця подія не була перехоплена обробниками onMenuItemClickListener (застаріло) або onOptionsItemSelected. Спрацювавши, намір передається в метод startActivity.

menuItem.setIntent(new Intent(this, MyOtherActivity.class));

Програмне відкриття або закриття меню

Якщо вам з якихось причин потрібно програмно відкрити меню (наприклад, у демонстраційних цілях), то використовуйте метод openOptionsMenu().

Для програмного закриття меню використовуйте метод closeOptionsMenu(), втім повторний виклик методу openOptionsMenu() закриває меню.

Програмне видалення пункту меню

Допустимо, ми визначили пункт меню в xml-файлі:

<item
android:id="@+id/action_dog"
android:orderInCategory="100"
android:showAsAction="never"
android:title="Песик"/>

Щоб видалити явно зайвий пункт меню з нашої програми про котів, потрібно отримати доступ до пункту меню через метод findItem() і зробити його невидимим. Посилання на об'єкт Menu потрібно передати в метод onCreateOptionsMenu, щоб програма дізналася про зміну складу меню.

// змінна класу
Menu menu;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// передаємо посилання на наш об'єкт
this.menu = menu;
getMenuInflater().inflate(R. menu.test, menu);
return true;
}
// клацання кнопки
public void onClick(View v) {
if (menu != null) {
// знаходимо потрібний елемент
MenuItem item_dog = menu.findItem(R. id.action_dog);
// робимо його невидимим
item_dog.setVisible(false);
}}

Але у цього рішення є недолік, якщо ми повернемо екран, то активність створиться наново і видалене меню знову з'явиться. Як же нам позбутися дивного песика?

Треба запам'ятати стан пункту меню і зберегти його в об'єкті типу Bundle в методі onSaveInstanceState, а в методі onCreate() витягти збережений стан і передати методу onPrepareOptionsMenu, який викликається перед показом меню на екрані:

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class TestActivity extends Activity {
Menu menu;
Boolean savedMenuDogIsVisible;
static final String KEY_MENU_DOG = "KEY_MENU_DOG";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R. layout.activity_test);
// отримуємо дані про видимість пункту меню
if (savedInstanceState != null) {
savedMenuDogIsVisible = savedInstanceState.getBoolean(KEY_MENU_DOG, true);
}}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
this.menu = menu;
getMenuInflater().inflate(R. menu.test, menu);
return true;
}
public void onClick(View v) {
if (menu != null) {
MenuItem item_dog = menu.findItem(R. id.action_dog);
// ховаємо пункт меню
item_dog.setVisible(false);
}}
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
if (menu != null) {
MenuItem item_dog = menu.findItem(R. id.action_dog);
// зберігаємо поточний стан пункту меню - true або false
outState.putBoolean(KEY_MENU_DOG, item_dog.isVisible());
}}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (savedMenuDogIsVisible != null) {
 MenuItem item_dog = menu.findItem(R. id.action_dog);
// перед виведенням на екран дізнаємося потрібний стан пункту меню
item_dog.setVisible(savedMenuDogIsVisible);
}
return super.onPrepareOptionsMenu(menu);
}}

Визначити наявність кнопки Menu

На старих пристроях використовувалася реальна кнопка Menu. У нових версіях Android меню прибрали в ActionBar і її наявність у вигляді окремої кнопки стало необов'язковим. Але багато виробників, як і раніше випускають телефони з кнопкою для меню. Щоб визначити, чи є така кнопка, в Android 14 додали новий метод, який дозволить визначити наявність цієї кнопки.

if (Build.VERSION.SDK_INT <= 10 || (Build.VERSION.SDK_INT >= 14 && ViewConfiguration.get(this).hasPermanentMenuKey())) {
// menu key is present
 Toast.makeText(this, "Кнопка Menu є", Toast.LENGTH_LONG).show();
} else {
// No menu key
Toast.makeText(this, "Кнопки Menu немає", Toast.LENGTH_LONG).show();
}

Розмітка для меню

У сучасних пристроях меню є частиною ActionBar. І ви можете налаштувати розмітку меню через XML.

Припустимо, ви вибрали такий варіант:

<item
android:id="@+id/action_new"
android:actionLayout="@layout/action_layout"
android:orderInCategory="100"
android:showAsAction="always|withText"
android:title="Новий"/>

В атрибуті showAsAction не використовуйте значення never, інакше розмітку не побачите. Сама розмітка задана через атрибут actionLayout. Код для розмітки:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<ImageButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:contentDescription="Custom button!"
android:scaleType="fitCenter"
android:src="@drawable/blue" />
</LinearLayout>

Меню у фрагментах

Меню може бути не тільки частиною активності, але і частиною фрагмента. Принцип роботи практично не відрізняється. У фрагмента є відповідний метод.

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R. menu.fragment_crime_list, menu);
}

FragmentManager відповідає за виклик onCreateOptionsMenu() при отриманні активністю зворотного виклику onCreateOptionsMenu() від системи. Ви повинні явно повідомити менеджеру FragmentManager, що фрагмент повинен отримати виклик onCreateOptionsMenu(). Для цього викликається метод setHasOptionsMenu():

// В коді фрагменту
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
getActivity().setTitle(R. string.cat);
...
}

Вікторія Пряжнікова


2016-06-04 • Просмотров [ 45 ]