Pull to refresh

Material Design. Динамический Toolbar на живом примере

Reading time 6 min
Views 151K
Уверен, что те, кто следят за изменениями в мире Android, заметили, что Toolbar начинает играть в приложениях всё более значимую роль. Например в последней версии Gmail клиента в Toolbar вынесен почти весь функционал по работе с почтой, а в новом Google Chrome Toolbar отвечает за работу с текстом страниц.

В данной статье я постараюсь рассказать о создании динамического Toolbar, который позволит пользователю работать с контентом четырьмя различными способами в рамках одного Activity. Мы рассмотрим весь процесс разработки Toolbar-a начиная с xml файлов стилей и заканчивая анимацией иконок, а в конце статьи я оставлю ссылку на GitHub репозиторий с примером полностью рабочего приложения.

Начнём с постановки задачи


Мы будем разрабатывать Toolbar для приложения, которое позволит пользователю следить за изменениями цен на акции. На главном экране будет расположен список всех акций, за которыми следит пользователь, мы также должны реализовать базовый функционал: удаление, поиск и сортировку акций. Вот так я реализовал этот функционал с помощью динамического Toolbar-a:

Стандартный режим Режим поиска Режим удаления Режим сортировки

Создаём xml файлы конфигураций


Итак, в первую очередь нам нужно создать xml файл самого Toolbar-a. Я советую сделать это в отдельном файле, так как в будущем мы скорее всего захотим использовать один и тот же (или похожий) Toolbar во всех Activity нашего приложения.

res/layout/toolbar.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar_actionbar"
    android:layout_width="match_parent"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
    android:layout_height="?android:actionBarSize"
    android:background="@color/toolbar_orange"/>

Теперь мы можем добавить toolbar.xml в xml Activity следующим образом:
res/layout/activity_main.xml
<include layout="@layout/toolbar" />

Поскольку в нашем Toolbar будет располагаться виджет поиска, мы можем настроить его внешний в вид в файле styles.xml нашего приложения. В 21 версии Android SDK появилось гораздо больше возможностей для кастомизации виджета поиска (SearchView Widget), вы можете посмотреть полный список атрибутов по этой ссылке: AppCompat v21 — Material Design for Pre-Lollipop Devices! В этом же файле мы зададим цвет нашего Toolbar.

res/values/styles.xml
<resources>
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="colorPrimaryDark">@color/status_bar_orange</item>
        <item name="searchViewStyle">@style/AppSearchViewStyle</item>
    </style>
    <style name="AppSearchViewStyle" parent="Widget.AppCompat.SearchView">
        <item name="android:textCursorDrawable">@drawable/white_cursor</item>
        <item name="queryBackground">@android:color/transparent</item>
        <item name="searchIcon">@drawable/icon_toolbar_search</item>
        <item name="closeIcon">@drawable/icon_toolbar_clear</item>
        <item name="queryHint">@string/search_hint</item>
        <item name="android:imeActionId">6</item>
    </style>
</resources>

И наконец создадим файл со списком всех элементов нашего Toolbar-а. Тут у нас есть несколько вариантов:
  • В начале создать только те элементы, которые будут видны в стандартном режиме, а затем в коде добавлять или удалять элементы при переходе между режимами.
  • Сразу создать все существующие элементы в xml файле, а в коде просто управлять их видимостью.

Я выбрал второй вариант так как у нас не так много элементов внутри Toolbar и нам нет смысла экономить память храня в ней только видимые элементы.

Также существует два способа создания элементов Toolbar:
  • Создавать элементы внутри меню (Menu), как экземпляры класса MenuItem. Этот способ использовался в предыдущих версиях Анрдроид (API Level < 21), когда еще не было Toolbar.
  • Создавать все элементы, как обычные View внутри файла toolbar.xml.

Я решил использовать способ с меню потому, что во-первых, так нам не нужно создавать свой лейаут для Toolbar-а. Во-вторых, у нас не будет проблем с обратной совместимостью и в-третьих, мы избежим конфликтов между Toolbar и Navigation Drawer (боковое меню присутствующие в приложении и управляющиеся с помощью ActionBarDrawerToggle, который в свою очередь работает с MenuItem)

res/menu/menu_activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:yourapp="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_search"
        android:title="@android:string/search_go"
        android:icon="@drawable/icon_toolbar_search"
        yourapp:showAsAction="always|collapseActionView"
        yourapp:actionViewClass="android.support.v7.widget.SearchView" />
    <item
        android:id="@+id/action_edit"
        android:title="@string/edit"
        android:icon="@drawable/icon_toolbar_edit"
        yourapp:showAsAction="ifRoom" />
    <item
        android:id="@+id/action_micro"
        android:title="@string/microphone"
        android:icon="@drawable/icon_toolbar_micro"
        yourapp:showAsAction="always" />
    <item
        android:id="@+id/action_remove"
        android:title="@string/remove"
        android:icon="@drawable/icon_toolbar_remove"
        yourapp:showAsAction="always" />
    <item
        android:id="@+id/action_sort"
        android:title="@string/sort_ab"
        android:icon="@drawable/icon_toolbal_sort"
        yourapp:showAsAction="always" />
</menu>

Добавляем Toolbar к Activity


Все xml файлы созданы и теперь мы можем добавить Toolbar к Activity. Для начала перезапишем метод onCreateOptionsMenu, который отвечает за инициализацию меню.
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_activity_main, menu);
        return true;
    }

Теперь зададим Toolbar, как ActioanBar, это обеспечит нам обратную совместимость с предыдущими версиями Android (API Level < 21).
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        Toolbar mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
        setSupportActionBar(mActionBarToolbar);
}

Управление элементами Toolbar


Полный код отвечающий за управление элементами вы можете посмотреть в репозитории примера на github, а в статье мы остановимся на основных методах:
  • Метод onOptionsItemSelected(MenuItem item) — вызывается при любом нажатии по элементу внутри Toolbar
  • Метод onMenuItemActionExpand(MenuItem item) — вызывается, когда виджет поиска переходит в активное состояние. Для того, чтобы этот метод вызвался необходимо реализовать интерфейс MenuItemCompat.OnActionExpandListener, и затем задать его для MenuItem поиска:
    MenuItemCompat.setOnActionExpandListener(searchMenuItem, this);
    //this - интерфейс MenuItemCompat.OnActionExpandListener
    
  • Метод onMenuItemActionCollapse — вызывается при закрытии виджета поиска, для него также необходимо реализовать интерфейс OnActionExpandListener. Вы также можете вызвать его искусственно, например когда пользователь нажимает кнопку «назад». Пример:
    @Override
        public void onBackPressed() {
            if (mode == Mode.SEARCH) 
                searchMenuItem.collapseActionView();
    }
    

Анимирование элементов Toolbar


Для создания анимации я использовал библиотеку AndroidViewAnimations. Эта библиотека умеет анимировать любые объекты класса View или объекты классов, которые наследуются от View. Основная проблема с которой мы сталкиваемся когда хотим создать анимацию для элементов Toolbar это отсутствие объекта класса View. У нас есть только MenuItem элемента, который мы хотим анимировать.

Если мы будем работать с MenuItem, который мы создали сами, к примеру иконкой поиска, то получить его View довольно легко:
MenuItem searchMenuItem = menu.findItem(R.id.action_search);
View searchItemView = findViewById(searchMenuItem.getItemId()); 

Всё усложняется, когда мы хотим получить View системного элемента Toolbar, к примеру стрелки «назад», которая становиться видна в режиме поиска (см. скриншот режима поиск). Поскольку мы не знаем id этой стрелки, нам придётся использовать Field, который позволяет нам получать динамический доступ к любому члену класса. В нашем случаи стрелка «назад» является членом класса Toolbar, но перед тем как мы начнём нам понадобиться узнать имя стрелки внутри класса. Идём в исходный код Android, открываем класс Toolbar и находим нашу стрелку на 100-й строчке под именем «mNavButtonView». Пример кода, в котором мы получаем View стрелки и анимируем его:
// Получение View 
ImageButton  btnToolbarButton = null;
 try {
            Toolbar mActionBarToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
            Field fNavBtn = mActionBarToolbar.getClass().getDeclaredField("mNavButtonView");
            fNavBtn.setAccessible(true);
            btnToolbarButton = (ImageButton) fNavBtn .get(mActionBarToolbar);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
//Анимация
if(btnToolbarButton!=null)
   YoYo.with(Techniques.FlipInX).duration(850).playOn(btnToolbarButton);

Заключение


В данной статье мы рассмотрели процесс создания динамического Toolbar и методы работы с его элементами. К сожалению в рамках одной статьи нельзя рассмотреть абсолютно все нюансы и разобрать весь код, поэтому если вам интересно узнать больше вы можете взглянуть на код приложения, которое мы использовали в качестве живого примера на GitHub.
Tags:
Hubs:
+17
Comments 4
Comments Comments 4

Articles