Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Добавляем картинку
Toast.Callback
Что такое тост? Представьте себе картину. За столом собралась большая куча народа и весело отмечает день рождения кота. Стоит шум и гам. Соседи громко разговаривают между собой и не обращают внимания на других. И тут из-за стола поднимается всеми уважаемый человек со стаканом вина и вилочкой стучит по стеклу стакана, чтобы привлечь внимание присутствующих. Шум смолкает и человек произносит тост. Также и в телефоне, когда вы увлечены какой-то задачей, вдруг всплывает сообщение, привлекая ваше внимание. Это и есть Toast. Второй пример - когда вы заряжаете специальный хлеб (тосты) в тостер, то они через определённое время подпрыгивают, сигнализируя о своей готовности. Посмотрим, как это работает в Android.
Всплывающее уведомление (Toast Notification) является сообщением, которое появляется на поверхности окна приложения, заполняя необходимое ему количество пространства, требуемого для сообщения. При этом текущая деятельность приложения остаётся работоспособной для пользователя. В течение нескольких секунд сообщение плавно закрывается. Всплывающее уведомление также может быть создано службой, работающей в фоновом режиме. Как правило, всплывающее уведомление используется для показа коротких текстовых сообщений.
Для создания всплывающего уведомления необходимо инициализировать объект Toast при помощи метода Toast.makeText(), а затем вызвать метод show() для отображения сообщения на экране:
// Kotlin
val text = "Пора покормить кота!"
val duration = Toast.LENGTH_SHORT
val toast = Toast.makeText(applicationContext, text, duration)
toast.show()
// Java
Toast toast = Toast.makeText(getApplicationContext(),
"Пора покормить кота!", Toast.LENGTH_SHORT);
toast.show();
Обычно пишут в одну строчку, соединяя вызов методов в цепочку .
// Kotlin
Toast.makeText(applicationContext, text, duration).show()
У метода makeText() есть три параметра:
Если покопаться в исходниках Android, то можно найти такие строчки:
private static final int LONG_DELAY = 3500; // 3.5 seconds
private static final int SHORT_DELAY = 2000; // 2 seconds
Как видите, уведомления выводятся на 3 с половиной секунды или на 2 секунды. Других вариантов нет, не пытайтесь использовать другие значения - у вас ничего не получится.
По умолчанию стандартное всплывающее уведомление появляется в нижней части экрана. Изменить место появления уведомления можно с помощью метода setGravity(int, int, int). Метод принимает три параметра:
Например, если вы хотите, чтобы уведомление появилось в центре экрана, то используйте следующий код (до вызова метода show()):
toast.setGravity(Gravity.CENTER, 0, 0);
Для вывода в левом верхнем углу.
toast.setGravity(Gravity.TOP or Gravity.LEFT, 0, 0)
Если нужно сместить уведомление направо, то просто увеличьте значение второго параметра. Для смещения вниз нужно увеличить значение последнего параметра. Соответственно, для смещения вверх и влево используйте отрицательные значения.
Типичная ошибка начинающих программистов - забывают добавить вызов метода show() для отображения сообщения на экране. К счастью, в студии, если вы пропустите метод show(), то строка будет подсвечена, а при подведении указателя мыши к строке увидите:
Создайте новый проект или используйте любой старый проект из предыдущих занятий. Добавьте на экран активности кнопку и присвойте ей текст Показать Toast. Теперь напишем код:
// Kotlin
// Если этот код работает, его написал Александр Климов,
// а если нет, то не знаю, кто его писал.
package ru.alexanderklimov.hellokot
import android.os.Bundle
import android.view.Gravity
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.button)
button.setOnClickListener {
val text = "Пора покормить кота!"
val duration = Toast.LENGTH_SHORT
val toast = Toast.makeText(applicationContext, text, duration)
toast.setGravity(Gravity.CENTER, 0, 0)
}
}
}
// Java
public void showToast(View view) {
//создаём и отображаем текстовое уведомление
Toast toast = Toast.makeText(getApplicationContext(),
"Пора покормить кота!",
Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
Запустите приложение и нажмите кнопку. В центре экрана появится на короткое время текстовое сообщение, которое само исчезнет. Очень похоже на поведение чеширского кота.
Начиная с Android 4.4, внешний вид всплывающего сообщения изменился, раньше был прямоугольник без закруглений.
Для закрепления материала напишем ещё один пример. Удалим предыдущий код для щелчка кнопки и напишем такой код:
// Kotlin
val duration = Toast.LENGTH_LONG
val toast = Toast.makeText(applicationContext, R.string.cat_food, duration)
toast.setGravity(Gravity.TOP, 0, 0)
toast.show()
// Java
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(getApplicationContext(),
R.string.catfood,
duration);
toast.setGravity(Gravity.TOP, 0, 0);
toast.show();
Я подумал, что вы можете не заметить сообщение, которое показывается слишком мало времени. Поэтому на этот раз я использовал константу LENGTH_LONG, чтобы вы успели обратить внимание на сообщение и покормить наконец несчастного голодного кота. Помимо этого, я поместил текст сообщения в XML-ресурсы, как это рекомендуется всегда делать. Кроме того, сообщение будет выводиться в верхней части экрана.
Как правило, для Toast используются короткие текстовые сообщения. При необходимости вы можете добавить к сообщению и картинку. Используя метод setView(), принадлежащий объекту Toast, вы можете задать любое представление (включая разметку) для отображения.
Начнем с приготовлений. Подготовьте картинку и разместите её в папке res/drawable, как мы делали в уроке с "Hello Kitty". Картинка будет доступна приложению как ресурс через название файла без расширения. Например, я добавил в папку файл с изображением кота hungrycat.jpg и могу получить к нему доступ через выражение R.drawable.hungrycat. Чтобы изображение появилось в стандартном Toast-сообщении, нам потребуется программно создать объект класса ImageView и задать для него изображение из ресурсов с помощью метода setImageResource. Сам по себе стандартный внешний вид Toast состоит из контейнера LinearLayout, в который нужно добавить созданный объект ImageView. Можно задать также позицию, в которую следует вывести изображение. Если указать значение 0, то изображение будет показано выше текста. Код для создания Toast с изображением выглядит следующим образом:
// Kotlin
button.setOnClickListener {
val toast = Toast.makeText(applicationContext, R.string.cat_food, Toast.LENGTH_LONG)
toast.setGravity(Gravity.CENTER, 0, 0)
val toastContainer = toast.view as LinearLayout
val catImage = ImageView(this)
catImage.setImageResource(R.drawable.hungrycat)
toastContainer.addView(catImage, 0)
toast.show()
}
// Java
public void showToast(View view) {
Toast toast3 = Toast.makeText(getApplicationContext(),
R.string.catfood, Toast.LENGTH_LONG);
toast3.setGravity(Gravity.CENTER, 0, 0);
LinearLayout toastContainer = (LinearLayout) toast3.getView();
ImageView catImageView = new ImageView(getApplicationContext());
catImageView.setImageResource(R.drawable.hungrycat);
toastContainer.addView(catImageView, 0);
toast3.show();
}
Вообще, получив доступ к контейнеру, вы можете делать с ним что угодно. Например, сделать его прозрачным. Смотрится интересно.
// Kotlin
val toast = Toast.makeText(applicationContext, "Чеширский кот", Toast.LENGTH_LONG)
val toastContainer = toast.view as LinearLayout
val catImage = ImageView(this)
catImage.setImageResource(R.drawable.hungrycat)
toastContainer.addView(catImage, 0)
// Устанавливаем прозрачность у контейнера
toastContainer.setBackgroundColor(Color.TRANSPARENT)
toast.show()
// Java
public void showToast(View view) {
Toast toast = Toast.makeText(getApplicationContext(),
"Чеширский кот", Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
LinearLayout toastContainer = (LinearLayout) toast.getView();
// Устанавливаем прозрачность у контейнера
toastContainer.setBackgroundColor(Color.TRANSPARENT);
toast.show();
}
В предыдущем примере мы получили доступ к контейнеру через метод getView(). Можно пойти от обратного - подготовить свой контейнер и внедрить его в объект Toast через метод setView().
Создадим собственный дизайн разметки для сообщения.
Вам нужно создать разметку в файле res/layout/custom_toast.xml.
Определите два дочерних элемента ImageView и TextView:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/custom_toast_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#DAAA"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:scaleType="centerCrop"
android:src="@drawable/hungrycat" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/cat_food"
android:textColor="#FFF777" />
</LinearLayout>
Для получения разметки из ХМL-файла и работы с ней в программе используется класс LayoutInflater и его метод getLayoutInflater(), которые возвращает объект LayoutInflater. Затем вызовом метода inflate() получают корневой объект View этой разметки. Например, для файла разметки уведомления с именем custom_toast.xml и его корневого элемента c идентификатором android:id="@+id/custom_toast_container" код будет таким:
// Kotlin
button.setOnClickListener {
val inflater = layoutInflater
val container = findViewById<ViewGroup>(R.id.custom_toast_container)
val layout: View = inflater.inflate(R.layout.custom_toast, container)
val text: TextView = layout.findViewById(R.id.text)
text.text = "Пора покормить кота!"
with (Toast(applicationContext)) {
setGravity(Gravity.CENTER_VERTICAL, 0, 0)
duration = Toast.LENGTH_LONG
view = layout
show()
}
}
// Java
// Упрощённо
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.custom_layout,
(ViewGroup) findViewById(R.id.toast_layout));
Toast toast = new Toast(getApplicationContext());
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
toast.setView(layout);
toast.show();
Параметры, передаваемые в метод inflate():
После получения корневого элемента из него можно получить все дочерние элементы методом findViewByid() и определить информационное наполнение для этих элементов.
Затем создаётся объект Toast и устанавливаются нужные свойства, например, Gravity и продолжительность времени показа уведомления.
После этого вызывается свойство view() (Kotlin) или метод setView() (Java), которому передаётся разметка уведомления, и метод show(), чтобы отобразить уведомление с собственной разметкой.
Запустите проект на выполнение. При нажатии кнопки вызова должно появиться на несколько секунд окно уведомления с текстовым сообщением и значком.
Ради интереса можете посмотреть, как выглядит разметка в исходных кода Android.
В Android 11 R (API 30) добавили возможность отслеживания момента, когда сообщение появляется и скрывается при помощи Toast.Callback.
button.setOnClickListener {
val alertToast = Toast.makeText(this, "Пора покормить кота", Toast.LENGTH_SHORT)
alertToast.addCallback(object: Toast.Callback() {
override fun onToastShown() {
super.onToastShown()
Log.d("Toast", "shown")
}
override fun onToastHidden() {
super.onToastHidden()
Log.d("Toast", "hidden")
}
})
alertToast.show()
}
Ещё раз напомню, что метод setView() в Android 11 признан устаревшим. А также вообще рекомендуют не использовать собственные виды всплывающих сообщений, а ограничиться простыми текстовыми сообщениями. Иначе можно нарваться на системные ограничения.
Как элемент графического интерфейса Toast должен быть вызван в потоке GUI, иначе существует риск выброса межпотокового исключения. В листинге объект Handler используется для гарантии того, что уведомление Toast было вызвано в потоке GUI.
// Java
private void mainProcessing() {
Thread thread = new Thread(null, doBackgroundThreadProcessing,
"Background");
thread.start();
}
private Runnable doBackgroundThreadProcessing = new Runnable() {
public void run() {
backgroundThreadProcessing();
}
};
private void backgroundThreadProcessing() {
handler.post(doUpdateGUI);
}
// Объект Runnable, который вызывает метод из потока GUI
private Runnable doUpdateGUI = new Runnable() {
public void run() {
Context context = getApplicationContext();
String msg = "To open mobile development!";
int duration = Toast.LENGTH_SHORT;
Toast.makeText(context, msg, duration).show();
}
};
Напоследок хочу предупредить об одной потенциальной проблеме. При вызове сообщения нужно указывать контекст в первом параметре метода makeText(). В интернете и, возможно и у меня на сайте будет попадаться пример makeText(MainActivity.this, ...). Ошибки в этом нет, так как класс Activity является потомком Context и в большинстве случаев пример будет работать. Но иногда я получаю письма от пользователей, которые жалуются на непонятное поведение сообщения, когда текст не выравнивается, обрезается и т.д. Это связано с тем, что активность может использовать определённую тему или стиль, которые вызывают такой побочный эффект. Поэтому я рекомендую вам использовать метод getApplicationContext().
Второй момент - фрагменты, которые будете изучать позже, не являются потомками контекста. Если вы захотите вызвать всплывающее сообщение в фрагменте, то проблема может поставить вас в тупик. Вам нужно добавить новую переменную класса Activity через метод getActivity():
Activity activity = getActivity();
Toast.makeText(activity, "Кота покормили?", Toast.LENGTH_SHORT).show();
Такое же может случиться при вызове всплывающих сообщений из диалоговых окон, которые тоже не относятся к классу Context. Вместо getApplicationContext() также можно вызывать метод getBaseContext().
Сегодня вы научились выводить всплывающие сообщения на экран, а также получили образец кода, который вы обязаны вставлять в любое ваше приложение, чтобы пользователи никогда не забывали кормить своего питомца.
Пример частично цветного текста в Toast через Spannable
GrenderG/Toasty: The usual Toast, but with steroids - библиотека, позволяющая создавать продвинутые сообщения