Урок 113. Android 3. ActionMode, как альтернатива контекстному меню


В этом уроке:

- работаем с ActionMode


Рассмотренный нами в прошлых уроках ActionBar – это альтернатива обычному меню прошлых версий. В третьей версии Андроида появилась также альтернатива и контекстному меню - ActionMode. Посмотрим, как его можно использовать.

Создадим проект:

Project name: P1131_ActionMode
Build Target: Android 4.1
Application name: ActionMode
Package name: ru.startandroid.develop.p1131actionmode
Create Activity: MainActivity


Добавим строки в strings.xml:

<string name="action_mode">Action Mode</string>
<string name="item1">Item1</string>
<string name="item2">Item2</string>
<string name="item3">Item3</string>
<string name="item4">Item4</string>


main.xml:

<?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:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/btnActionMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/action_mode">
</Button>
</LinearLayout>

Одна кнопка, по нажатию на которую будем показывать/скрывать ActionMode


Пункты меню, res/menu/context.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/item1"
android:showAsAction="always"
android:title="@string/item1">
</item>
<item
android:id="@+id/item2"
android:icon="@android:drawable/ic_menu_call"
android:showAsAction="ifRoom"
android:title="@string/item2">
</item>
<item
android:id="@+id/item3"
android:icon="@android:drawable/ic_menu_info_details"
android:showAsAction="ifRoom|withText"
android:title="@string/item3">
</item>
<item
android:id="@+id/item4"
android:icon="@android:drawable/ic_menu_view"
android:showAsAction="never"
android:title="@string/item4">
</item>
</menu>

Те же пункты, что мы использовали для примеров с ActionBar. Их будет отображать ActionMode.


MainActivity.java:

package ru.startandroid.develop.p1131actionmode;

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

public class MainActivity extends Activity {

 
ActionMode actionMode;
 
final String LOG_TAG = "myLogs";

 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);
 
}

 
public boolean onCreateOptionsMenu(Menu menu) {
   
getMenuInflater().inflate(R.menu.main, menu);
   
return true;
 
}

 
public void onClick(View v) {
   
if (actionMode == null)
     
actionMode = startActionMode(callback);
   
else
     
actionMode.finish();
 
}

 
private ActionMode.Callback callback = new ActionMode.Callback() {

   
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
     
mode.getMenuInflater().inflate(R.menu.context, menu);
     
return true;
   
}

   
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
     
return false;
   
}

   
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
     
Log.d(LOG_TAG, "item " + item.getTitle());
     
return false;
   
}

   
public void onDestroyActionMode(ActionMode mode) {
     
Log.d(LOG_TAG, "destroy");
      actionMode =
null;
   
}

  }
;

}

Чтобы вызвать ActionMode, используется метод startActionMode. На вход он берет объект callback, который будет обрабатывать все события, связанные с ActionMode. На выходе получаем объект ActionMode.

В методе onClick мы проверяем, если ActionMode еще не был вызван, то вызываем. Иначе – убираем его с помощью его же метода finish.

Объект callback реализует интерфейс ActionMode.Callback. Его методы:

onCreateActionMode – вызывается при создании ActionMode. Возвращаем true, если ActionMode можно создавать. Здесь мы наполняем ActionMode пунктами меню (через объект Menu).

onPrepareActionMode – вызывается при обновлении ActionMode. Например, в случае вызова метода invalidate. Возвращаем true, если ActionMode можно обновить.

onActionItemClicked – обработка нажатия на какой-либо пункт ActionMode. Будем выводить в лог текст нажатого пункта.

onDestroyActionMode – вызывается при закрытии ActionMode. Пишем лог и обнуляем переменную actionMode, чтобы в onClick (см.выше) у нас работала проверка (actionMode == null).


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


Жмем кнопку, появляется ActionMode

Располагается он там же, где и ActionBar. Да и выглядит также, только слева у него кнопка закрытия. Пункты меню видны так, как мы описывали в файле context.xml.


Нажмем, например, на Item1, смотрим лог.

item Item1

Сработал метод onActionItemClicked.


Закрыть ActionMode мы теперь можем снова нажав кнопку Action Mode, либо нажав на галку в левой части, либо нажав кнопку Назад. Во всех этих случаях будет выполнен метод onDestroyActionMode. Опробуйте эти способы и смотрите логи, там должна появиться строка:

destroy


Мы рассмотрели способ ручного вызова ActionMode, обрабатывая нажатие на кнопку. Т.к. ActionMode позиционируется как замена контекстному меню, то вполне можно повесить его вызов на долгое нажатие на какой-либо элемент экрана. Чтобы обработать долгое нажатие надо вызывать для элемента метод setOnLongClickListener, передать туда объект, реализующий интерфейс OnLongClickListener, и в методе onLongClick этого объекта накодить вызов ActionMode.

Для некоторых элементов вызов ActionMode по долгому нажатию уже реализован. Это наследники класса AbsListView, например GridView и ListView. Попробуем на ListView, как это работает.


Меняем main.xml:

<?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:id="@+id/LinearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lvActionMode"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>

На экране будет только список.


Меняем MainActivity.java:

package ru.startandroid.develop.p1131actionmode;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends Activity {

 
ActionMode actionMode;
  ListView lvActionMode;
 
final String LOG_TAG = "myLogs";

  String
[] data = { "one", "two", "three", "four", "five" };

 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);

    ArrayAdapter<String> adapter =
new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_activated_1, data
);
    lvActionMode =
(ListView) findViewById(R.id.lvActionMode);
    lvActionMode.setAdapter
(adapter);
    lvActionMode.setChoiceMode
(ListView.CHOICE_MODE_MULTIPLE_MODAL);
    lvActionMode.setMultiChoiceModeListener
(new MultiChoiceModeListener() {

     
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
       
mode.getMenuInflater().inflate(R.menu.context, menu);
       
return true;
     
}

     
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
       
return false;
     
}

     
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
       
mode.finish();
       
return false;
     
}

     
public void onDestroyActionMode(ActionMode mode) {
      }

     
public void onItemCheckedStateChanged(ActionMode mode,
         
int position, long id, boolean checked) {
       
Log.d(LOG_TAG, "position = " + position + ", checked = "
           
+ checked);
     
}
    })
;

 
}

 
public boolean onCreateOptionsMenu(Menu menu) {
   
getMenuInflater().inflate(R.menu.main, menu);
   
return true;
 
}

}

В onCreate мы создаем адаптер и присваиваем его списку. Далее для списка мы включаем режим выбора (Урок 43) CHOICE_MODE_MULTIPLE_MODAL (появившийся в API Level 11) и устанавливаем объект обработчик, реализующий AbsListView.MultiChoiceModeListener. Методы здесь все те же, что и ранее нами рассмотренные в ActionMode.Callback, плюс добавляется один – onItemCheckedStateChanged, в котором мы получаем инфу о выделенных пунктах списка. Т.е. этот обработчик и выделение пунктов списка отслеживает и ActionMode контролирует.

В onCreateActionMode мы указываем, из какого файла брать пункты меню, в onActionItemClicked закрываем ActionMode независимо от того, какой пункт меню был выбран, а в onItemCheckedStateChanged просто выводим в лог инфу о выбираемых пунктах списка. Остальные методы не трогаем, сейчас они не нужны.


Все сохраняем и запускаем приложение.


Долгое нажатие на какой-либо пункт списка вызовет ActionMode

А в логах появится инфа о выбранном пункте:

position = 2, checked = true


Теперь мы можем дальше простыми нажатиями выбирать и «развыбирать» пункты списка


и после этого нажать на какой-либо пункт ActionMode для операции над группой выделенных пунктов. Получить список выбранных пунктов можно так же, как в Уроке 43 для множественного выбора.


Как нам теперь закрыть ActionMode?

- кнопка отмены (слева)

- кнопка Назад

- любой пункт в ActionMode, т.к. мы повесили вызов метода finish в onActionItemClicked

При закрытии ActionMode «развыделятся» и все выделенные пункты.

Еще, как вариант закрытия ActionMode, – это не оставить ни одного выделенного пункта. Если в процессе выделения пунктов не останется ни одного выделенного, то ActionMode закроется.


Для списка в режиме CHOICE_MODE_MULTIPLE_MODAL есть еще один способ, кроме долгого нажатия на пункт, вызвать ActionMode. Это метод setItemChecked. Если выделить этим методом какой-либо пункт, то ActionMode появится.


У ActionMode, как и у ActionBar есть методы:

setTitle - установить свой заголовок

setSubtitle - установить свой подзаголовок 


На следующем уроке:

- разбираемся, зачем нужна библиотека Support Library
- на примере фрагментов используем библиотеку v4