Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Добавляем кнопки действий на 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 на экране активности в виде отдельной дополнительной панели, поэтому сосредоточимся на первой панели, которая должна служить заменой 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 - ещё одно доказательство, что панель встроилась в активность и стала её родной частью. Если вы добавите код для меню, то в панели появятся также и три вертикальные точки.
Добавим различные настройки для него, они вам знакомы по 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().
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>
Если вы используете дизайн 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 служит заменой 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>
Для демонстрации я использовал системные ресурсы для изображения и готовый текст. В своих проектах вы должны использовать правильные способы использования строковых и графических ресурсов. Если убрать первый пункт, то три точки не появятся, а будет только наша кнопка действия.
Созданная кнопка будет использоваться для перехода на вторую активность. Создайте её самостоятельно.
Перейдём в класс основной активности и добавим код для перехода на вторую активность.
@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);
}
}
Во многих примерах часто используют приём включения одного файла разметки в другой.
Создадим файл action_toolbar.xml в папке res/layout и укажем в качестве корневого элемента 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);
}
Запустим код и получим прозрачную панель.
Вряд ли мы ожидали такого варианта. Добавим фон.
<?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>
Смотрим на результат. Видим, что панель имеет отступы, которые были добавлены в шаблоне.
Для панели, которая выступает в качестве 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">
Теперь мы получили правильную панель.
Интересно отметить, что на старом устройстве можно вызвать меню из двух мест. Современный способ через три точки на панели.
Старый способ через аппаратную кнопку меню. Меню будет вызвано из другого места.
Можно изменить цвет меню, добавив дополнительный атрибут.
<?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>
Можно задать свою отдельную тему для панели, определив цвет текста, который должен быть достаточно контрастным на фоне панели. Добавим в файл 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 также можно указать следующие атрибуты:
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"/>
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;
}
}
Если вторая активность зависит от первой, то в манифесте прописывают атрибут parentActivityName.
<activity android:name=".SecondActivity"
android:parentActivityName=".MainActivity"></activity>
В этом случае на панель у второй активности можно добавить специальный значок возврата обратно в родительскую активность в виде стрелки.
supportActionBar?.apply {
title = "Second activity's Toolbar"
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
Вам не придётся писать код для возврата на родительскую активность, система сама сделает возврат по щелчку на значке.
Цвет значка определяется системой. Можно программно изменить его. Поменяем цвет стрелки на красный.
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();
}
});