Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Toolbar

Добавляем кнопки действий на Toolbar
Вложенный Toolbar
Полоса под панелью
Значок возврата

Компонент появился в Android 5 Lollipop, но доступен и для старых устройств в рамках Material Design. Можно найти в разделе Containers на панели инструментов.

Toolbar является дальнейшим развитием ActionBar, но имеет больше возможностей. Он гораздо гибче в настройках. Теперь вы можете задать многие свойства через XML, также вы можете задать различную высоту и вставлять в него другие компоненты. Кроме того, Toolbar может играть не только роль заголовка в приложении, его можно разместить и в других местах.

У вас должна быть прописана зависимость (скорее всего уже есть):


implementation 'androidx.appcompat:appcompat:1.5.1'

Важный момент - Toolbar замещает ActionBar, поэтому следует позаботиться, чтобы они не работали одновременно. Самое простое - выбрать тему без использования ActionBar, например, <style name="Theme.Dolphin_Kot" parent="Theme.MaterialComponents.DayNight.NoActionBar"> или более ранний вариант <style name="AppTheme" parent = "Theme.AppCompat.NoActionBar">.

Помните, что сам по себе компонент Toolbar может служить не только заменой ActionBar, но и как отдельная панель снизу, слева, справа, быть плавающей поверх экрана, иметь различную ширину и высоту.

Создадим разметку с использованием Toolbar:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:weightSum="1">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_dark" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Используем Toolbar"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/second_toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

Запускаем и видим две цветные полоски Toolbar, а между ними приютился TextView.

Toolbar

Я пока не видел практического применения Toolbar на экране активности в виде отдельной дополнительной панели, поэтому сосредоточимся на первой панели, которая должна служить заменой ActionBar. На данный момент панель совсем не похожа на заголовок приложения - нет ни значка, ни текста, ни меню. Просто пустая полоска.

Чтобы подсказать приложению, что панель является заменой ActionBar, нужно присоединить её в коде через метод setSupportActionBar().


// Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    val toolbar: Toolbar = findViewById(R.id.toolbar)

    setSupportActionBar(toolbar)
    
    title = "Toolbar Demo"
}

// Java package ru.alexanderklimov.toolbar; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import android.view.View; public class MainActivity extends ActionBarActivity { private Toolbar mToolbar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mToolbar = findViewById(R.id.toolbar); setSupportActionBar(mToolbar); //setTitle("Toolbar Demo"); } }

Теперь мы видим привычный заголовок приложения. Панель стала частью заголовка. Сейчас в заголовке выводится имя приложения. Если вы уберёте комментарий со строки setTitle(), то увидите, что этот метод активности действует и на Toolbar - ещё одно доказательство, что панель встроилась в активность и стала её родной частью. Если вы добавите код для меню, то в панели появятся также и три вертикальные точки.

Toolbar

Добавим различные настройки для него, они вам знакомы по ActionBar:


// Kotlin
title = "Стандартный заголовок" // работает
toolbar.title = "Стандартный заголовок" // не работает
toolbar.subtitle = "Подзаголовок"
toolbar.setLogo(R.mipmap.ic_launcher)

// Java
setTitle("Стандартный заголовок"); // работает
mToolbar.setTitle("Стандартный заголовок"); // не работает
mToolbar.setSubtitle("Подзаголовок");
mToolbar.setLogo(R.drawable.ic_launcher);

Это только часть методов. В документации найдёте остальное. Метод Toolbar.setTitle() у меня не заработал, в заголовке всё равно выводился текст из ресурсов app_name. Но работает метод активности setTitle().

Toolbar

Upd. Насколько я понял, в методе onCreate() рано вызывать метод Toolbar.setTitle(), так как текст заголовка переписывается текстом из манифеста (android:labed). Нужно сделать это чуть позже. Как вариант, вызвать в методе onPostCreate(), который сработает после onCreate(). Если действовать через щелчок кнопки, то текст меняется без проблем.

Как я уже говорил, панель инструментов может быть любой ширины и высоты. Теперь мы сами можем задать нужные размеры, не полагаясь на системные настройки. Кроме того, панель инструментов является контейнером, мы можем разместить в ней нужные компоненты. Например, аналоговые часы.


<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:background="@android:color/holo_blue_dark">

    <AnalogClock
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/analogClock" />
</androidx.appcompat.widget.Toolbar>

Toolbar

Если вы используете дизайн Material, то для фона желательно использовать соответствующие цвета, например, так.


android:background="?attr/colorPrimary"

Другие атрибуты:


android:minHeight="?attr/actionBarSize"

В качестве темы используйте следующий вариант.


app:theme="@style/ThemeOverlay.AppCompat.ActionBar"

Обратите внимание, что используется пространство имён xmlns:app="http://schemas.android.com/apk/res-auto".

Сам по себе Toolbar автоматически не цепляет тему активности, поэтому при необходимости нужно дублировать нужные свойства.


<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:minHeight="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>

В старых проектах родительский элемент часто использовал отступы и ваша панель может быть уже. Просто уберите их.

Стилизация

Соберём все стили вместе.


<style name="ToolbarTextAppearance">  
    <item name="android:fontFamily">sans-serif-condensed</item>
    <item name="android:textColor">@android:color/white</item>
    <item name="android:shadowDx">1</item>
    <item name="android:shadowDy">1</item>
    <item name="android:shadowRadius">2</item>
    <item name="android:shadowColor">?colorAccent</item>
</style>

<style name="ToolbarTextAppearance.Title">  
    <item name="android:textSize">20sp</item>
</style>

<style name="ToolbarTextAppearance.Subtitle">  
    <item name="android:textSize">14sp</item>
</style>

<style name="MyToolbar">  
    <item name="theme">@style/ThemeOverlay.AppCompat.Dark</item>
    <item name="android:background">?colorPrimary</item>
    <item name="android:elevation">4dp</item>
</style>  

Ещё один стиль добавляет пустое место сверху (добавить в MyToolbar). Редко используется.


<item name="titleMarginTop">?actionBarSize</item>

Применим через style, tittleTextAppearance and subtittleTextAppearance:


<androidx.appcompat.widget.Toolbar  
    app:title="Toolbar"
    app:subtitle="Subtitle"
    app:titleTextAppearance="@style/ToolbarTextAppearance.Title"
    app:subtitleTextAppearance="@style/ToolbarTextAppearance.Subtitle"
    style="@style/MyToolbar"  />

Прозрачность достигается через windowTranslucentStatus:


<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <item name="android:windowTranslucentStatus">true</item>
</style>

Добавляем кнопки действий на Toolbar

Чтобы на панели появились три вертикальные точки для вызова меню, нужно просто добавить соответствующие методы. Но также можно вывести на панель и кнопки действия, минуя эти точки.

Добавим кнопку действия на Toolbar. Так как в нашем случае Toolbar служит заменой ActionBar, то наш код ничем не будет отличаться от старого способа для панели действий. Просто освежим память.

В файле res/menu/menu_main.xml добавим новый пункт (при необходимости создайте папку и файл вручную).


<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="@string/action_settings"
        app:showAsAction="never"/>

    <item
        android:id="@+id/action_next"
        android:icon="@android:drawable/ic_media_play"
        android:orderInCategory="200"
        android:title="Далее"
        app:showAsAction="always"/>

</menu>

Для демонстрации я использовал системные ресурсы для изображения и готовый текст. В своих проектах вы должны использовать правильные способы использования строковых и графических ресурсов. Если убрать первый пункт, то три точки не появятся, а будет только наша кнопка действия.

Menu

Созданная кнопка будет использоваться для перехода на вторую активность. Создайте её самостоятельно.

Перейдём в класс основной активности и добавим код для перехода на вторую активность.


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}
    
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    switch (id){
        case R.id.action_settings:
            return true;
        case R.id.action_next:
            // Запускаем вторую активность
            Intent intent = new Intent(MainActivity.this, SecondActivity.class);
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

Используем <include>

Во многих примерах часто используют приём включения одного файла разметки в другой.

Создадим файл action_toolbar.xml в папке res/layout и укажем в качестве корневого элемента Toolbar.

Toolbar


<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

</androidx.appcompat.widget.Toolbar>

Переходим в разметку основной активности и добавим созданную панель через тег include.


<include
    android:id="@+id/toolbar"
    layout="@layout/action_toolbar" />

Конечно, мы могли бы добавить и напрямую тег Toolbar в разметку активности, но такой способ удобнее, если приложение будет состоять из нескольких активностей и в каждой из них должна быть наша панель.

Если вставлять код в шаблон с RelativeLayout, то текст "Hello World" может наложиться на панель. Поэтому укажем ему место под панелью.


<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <include
        android:id="@+id/toolbar"
        layout="@layout/action_toolbar" />

    <TextView
        android:text="@string/hello_world"
        android:layout_below="@+id/toolbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

Напишем код. Объявим переменную типа Toolbar и присоединим её к экрану активности.


private Toolbar mToolbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mToolbar = findViewById(R.id.toolbar);
    setSupportActionBar(mToolbar);
}

Запустим код и получим прозрачную панель.

Toolbar

Вряд ли мы ожидали такого варианта. Добавим фон.


<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/primaryColor">

</androidx.appcompat.widget.Toolbar>

Смотрим на результат. Видим, что панель имеет отступы, которые были добавлены в шаблоне.

Toolbar

Для панели, которая выступает в качестве ActionBar, таких отступов быть не должно. Очистим лишние атрибуты у корневого элемента RelativeLayout.


<RelativeLayout 
    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"
    tools:context=".MainActivity">

Теперь мы получили правильную панель.

Toolbar

Интересно отметить, что на старом устройстве можно вызвать меню из двух мест. Современный способ через три точки на панели.

Toolbar

Старый способ через аппаратную кнопку меню. Меню будет вызвано из другого места.

Toolbar

Можно изменить цвет меню, добавив дополнительный атрибут.


<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/primaryColor"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Dark">

</androidx.appcompat.widget.Toolbar>

Toolbar

Можно задать свою отдельную тему для панели, определив цвет текста, который должен быть достаточно контрастным на фоне панели. Добавим в файл styles.xml


<style name="MyToolbarTheme" parent="ThemeOverlay.AppCompat.Light">
    <item name="android:textColorPrimary">#FFF</item>
    <item name="android:textColorSecondary">#00F</item>
</style>

Подключим стили к панели.


<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar 
    xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    app:theme="@style/MyToolbarTheme">

</androidx.appcompat.widget.Toolbar>

Я намеренно задал разные цвета для атрибутов android:textColorPrimary и android:textColorSecondary, чтобы было заметно, где они используются.

Toolbar

Теперь видно, что первый атрибут отвечает за текст на панели, а второй за точки, которые вызывают меню.

Для Toolbar также можно указать следующие атрибуты:


android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"

Системный размер высоты для панели приложения можно использовать и в стандартном атрибуте высоты:


android:layout_height="?attr/actionBarSize"

Если хотите использовать стандартную тему для панели, не переопределяя никаких атрибутов, то так и пишите:


android:theme="@android:style/ThemeOverlay.Material.Dark.ActionBar"

Если мы хотим использовать Toolbar не как замену ActionBar, а как отдельную панель, то её создание и присоединение к ней меню может быть следующим.


@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = findViewById(R.id.my_toolbar);
    
    // Добавляем меню к отдельной панели
    toolbar.inflateMenu(R.menu.your_toolbar_menu);

    // Set an OnMenuItemClickListener to handle menu item clicks
    toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            // Handle the menu item
            return true;
        }
    });
}

При использовании SearchView на панели также были внесены изменения. Можно задать стили для данного компонента. Не проверял, оставлю на память.


values/themes.xml

<style name="Theme.MyTheme" parent="Theme.AppCompat">
    <item name="searchViewStyle">@style/MySearchViewStyle</item>
</style>

<style name="MySearchViewStyle" parent="Widget.AppCompat.SearchView">
    <!-- The layout for the search view. Be careful. -->
    <item name="layout">...</item>
    <!-- Background for the search query section (e.g. EditText) -->
    <item name="queryBackground">...</item>
    <!-- Background for the actions section (e.g. voice, submit) -->
    <item name="submitBackground">...</item>
    <!-- Close button icon -->
    <item name="closeIcon">...</item>
    <!-- Search button icon -->
    <item name="searchIcon">...</item>
    <!-- Go/commit button icon -->
    <item name="goIcon">...</item>
    <!-- Voice search button icon -->
    <item name="voiceIcon">...</item>
    <!-- Commit icon shown in the query suggestion row -->
    <item name="commitIcon">...</item>
    <!-- Layout for query suggestion rows -->
    <item name="suggestionRowLayout">...</item>
</style>

При использовании на панели меню и других компонентов с выпадающими элементами может понадобиться следующий код.


values-21
//Activity theme:
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:popupMenuStyle">@style/PopupMenuStyle.Light</item>
</style>

//Popup style
<style name="PopupMenuStyle.Light" parent="@style/Widget.AppCompat.Light.PopupMenu">
    <item name="android:overlapAnchor">true</item>
</style>

В API 21 также появились новые стили для элементов меню.


<style name="MyPopupTheme" parent="ThemeOverlay.AppCompat.Dark">
    <item name="android:colorControlActivated">@color/redColor</item>
    <item name="android:colorControlHighlight">@color/redColor</item>
    <item name="android:colorControlNormal">@color/yellowColor</item>
    <item name="android:textColorPrimary">@color/yellowColor</item>
</style>

Применим к панели.


<androidx.appcompat.widget.Toolbar
    ...
    app:popupTheme="@style/MyPopupTheme"/>

Style Toolbar

Вложенный Toolbar

Toolbar можно вложить в какой-нибудь контейнер, например, CardView. Сам на практике не использовал. Но для общего развития оставлю.

Добавим пару ресурсов в файл dimens.xml:


<dimen name="toolbar_double_height">112dp</dimen>
<dimen name="cardview_toolbar_spacer">32dp</dimen>

Создадим разметку экрана.


<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/background_material_light">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/generalToolbar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/toolbar_double_height"
        android:background="?attr/colorPrimary"/>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="16dp"
        android:layout_marginLeft="@dimen/cardview_toolbar_spacer"
        android:layout_marginRight="@dimen/cardview_toolbar_spacer"
        android:layout_marginTop="?attr/actionBarSize"
        app:cardBackgroundColor="@android:color/white"
        app:cardElevation="4dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <androidx.appcompat.widget.Toolbar
                android:id="@+id/nestedToolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:alpha="0.12"
                android:background="@android:color/holo_orange_dark"/>

            <FrameLayout
                android:id="@+id/content_frame"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</FrameLayout>

В макете у первой панели установлена большая высота, а CardView частично налезает на главную панель. И при этом содержит вторую панель.

Также обратите внимание на компонент View с высотой 1dp оранжевого цвета, который добавляет полоску под панелью. Вы можете усложнить пример, добавив тень и другие эффекты.

Добавим на вторую панель кнопку действия. Для этого создадим ресурс меню res/menu/menu_main.xml:


<menu 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".MainActivity">
    <item
        android:id="@+id/action_search"
        android:orderInCategory="100"
        android:title="Search"
        app:actionViewClass="android.support.v7.widget.SearchView"
        app:showAsAction="always"/>
</menu>

Код для активности.


package ru.alexanderklimov.toolbar;

// замените support на compat-версии
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

public class MainActivity extends AppCompatActivity implements SearchView.OnQueryTextListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Toolbar nestedToolbar = findViewById(R.id.nestedToolbar);
        Toolbar generalToolbar = findViewById(R.id.generalToolbar);

        generalToolbar.setNavigationIcon(android.R.drawable.ic_menu_camera);
        setSupportActionBar(nestedToolbar);
        getSupportActionBar().setTitle("Вложенный Toolbar");
    }

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

    @Override
    public boolean onQueryTextSubmit(String query) {
        return false;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        return false;
    }
}

Вложенный Toolbar

Если вторая активность зависит от первой, то в манифесте прописывают атрибут parentActivityName.


<activity android:name=".SecondActivity"
    android:parentActivityName=".MainActivity"></activity>

В этом случае на панель у второй активности можно добавить специальный значок возврата обратно в родительскую активность в виде стрелки.


supportActionBar?.apply {
    title = "Second activity's Toolbar"
    setDisplayHomeAsUpEnabled(true)
    setDisplayShowHomeEnabled(true)
}

Вам не придётся писать код для возврата на родительскую активность, система сама сделает возврат по щелчку на значке.

NavigationIcon

Цвет значка определяется системой. Можно программно изменить его. Поменяем цвет стрелки на красный.


toolbar.navigationIcon?.apply {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        colorFilter = BlendModeColorFilter(Color.RED, BlendMode.SRC_IN)
    } else {
        setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN)
    }
}

Если вы хотите заменить системный значок стрелки на что-то своё, то добавьте стиль в MyToolbar (см. выше):


<item name="navigationIcon">@drawable/ic_pets_24</item>

Можно переопределить поведение значка возврата. Не хотите возвращаться на родительскую активность? Напишите свой код.


// Kotlin
toolbar.setNavigationOnClickListener {
    Toast.makeText(this, "Не хочу возвращаться!", Toast.LENGTH_LONG).show()
}

// Java toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Может быть другой код onBackPressed(); } });
Реклама