воскресенье, 28 марта 2010 г.

Оповещение пользователя: Toast

Начинаю цикл статей о способах оповещения пользователя. Цикл рассчитан на новичков. Существует три способа оповещения пользователя: через Toast, через Notification и через различные виды Dialog. Сегодня я расскажу об использовании первого типа оповещения - Toast.

Введение

Toast представляет собой всплывающее сообщение, которое позволяет быстро оповестить пользователя о произошедшем событии, например, сохранении настроек программы на SD-карте. Особенность Toast заключается в том, что во время появления сообщения пользователь может взаимодействовать с находящимися за ним Activity, либо с домашним экраном (home screen). Также стоит заметить, что пользователь не может контролировать закрытие Toast с помощью аппаратной кнопки Back или другими возможными способами, сообщение плавно появляется и потом само же плавно исчезает. Время задержки между появлением и исчезновением можно задавать программно. В большинстве случаев ToastToast, например, добавить изображение рядом с текстом. Помимо этого можно управлять расположением Toast на экране. Toast может быть создан из Activity, либо из Service. В случае создания из сервиса Toast появляется поверх Activity, которое имеет фокус, либо поверх домашнего экрана.

Создание простого Toast

Создать простой Toast можно через статичный метод makeText класса Toast, задав необходимые параметры.
Toast.makeText(getApplicationContext(), "Привет, мир!", Toast.LENGTH_SHORT).show();
В качестве параметров задается контекст приложения, сообщение и задержка, о которой писал ранее. Сообщение может быть задано непосредственно в виде текста, либо используя текстовый ресурс-строку, например, R.string.hello_world, в которой хранится текст, который необходимо отобразить, в нашем случае "Привет, мир!". Задержка может быть короткой - LENGTH_SHORT, либо длинной - LENGTH_LONG. По умолчанию при создании Toast задается короткая задержка. Программно задержка задается методом setDuration.

Суть метода makeText такова: внутри метода создается объект класса Toast, устанавливается текст сообщения и тип задержки. Далее к объекту может быть либо применен, как в моем случае, метод show, который отображает созданный Toast, либо заданы дополнительные свойства Toast, например, его расположение на экране или созданный вами внешний вид.

Созданный Toast выглядит следующим образом:


Изменение положения Toast

Расположение Toast на экране задается с помощью метода setGravity следующим образом:
Toast toast = Toast.makeText(getApplicationContext(), "Привет, мир!",
Toast.
LENGTH_LONG);toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
Первый параметр метода задает выравнивание, вариантов которого довольно много в классе Gravity. Второй и третий параметры задают на сколько пикселей будет смещен Toast по горизонтали вправо и по вертикали вниз соответственно относительно значения, заданного в первом параметре. Результат приведенного выше кода отображается следующим образом:


Добавление изображения в простой Toast

Для добавления изображения в стандартный Toast потребуется программно создать объект класса ImageView и задать для него изображение из ресурсов с помощью метода setImageResource. Затем потребуется получить стандартный внешний вид Toast, если посмотреть в отладчике он является LinearLayout, и добавить в него созданный объект ImageView с указанием в какую позицию добавить изображение, в моем случае я указал нулевую позицию, чтобы изображение было добавлено выше текста. Код для создания этого Toast с изображением представлен ниже.
Toast toast = Toast.makeText(getApplicationContext(), "Привет, мир!", Toast.LENGTH_LONG);
toast.
setGravity(Gravity.CENTER, 0, 0);
LinearLayout toastView = (LinearLayout) toast.getView();
ImageView imageWorld
= new ImageView(getApplicationContext());
imageWorld.
setImageResource(R.drawable.world);
toastView.addView(imageWorld, 0);
toast.show();
Созданный таким образом Toast выглядит следующим образом:


Создание сложного Toast

Для создания сложного Toast потребуется создать собственный layout, код которого выглядит следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content" android:layout_width="wrap_content"
android:background="#ffffffff" android:orientation="vertical"
android:id="@+id/llToast">
<TextView android:layout_height="wrap_content"
android:layout_margin="1dip" android:textColor="#ffffffff"
android:layout_width="fill_parent" android:gravity="center"
android:background="#bb000000" android:id="@+id/tvTitleToast"></TextView>
<LinearLayout android:layout_height="wrap_content"
android:orientation="vertical" android:id="@+id/llToastContent"
android:layout_marginLeft="1dip" android:layout_marginRight="1dip"
android:layout_marginBottom="1dip" android:layout_width="wrap_content"
android:padding="15dip" android:background="#44000000">
<ImageView android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_width="wrap_content"
android:id="@+id/tvImageToast" />
<TextView android:layout_height="wrap_content"
android:paddingRight="10dip" android:paddingLeft="10dip"
android:layout_width="wrap_content" android:gravity="center"
android:textColor="#ff000000" android:id="@+id/tvTextToast" />
</LinearLayout>
</LinearLayout>

Я создал Toast в виде диалога с заголовком, внутри которого располагаются изображение и текст.

Теперь этот layout нужно прописать для Toast и задать сообщения для заголовка и текста, а также задать изображение. Делается это следующим образом:
LayoutInflater inflater = getLayoutInflater();
View layout = inflater.inflate(R.layout.toast,
(ViewGroup) findViewById(R.id.llToast));
ImageView image
= (ImageView) layout.findViewById(R.id.tvImageToast);
image.
setImageResource(R.drawable.world);
TextView title
= (TextView) layout.findViewById(R.id.tvTitleToast);
title.
setText("Внимание");
TextView text
= (TextView) layout.findViewById(R.id.tvTextToast);
text.
setText("Привет, мир!");

Toast toast
= new Toast(getApplicationContext());
toast.
setGravity(Gravity.RIGHT | Gravity.TOP, 12, 40);
toast.
setDuration(Toast.LENGTH_LONG);
toast.
setView(layout);
toast.
show();

В первых двух строках происходит инициализация объекта View путем наполнения его из xml-файла (для этого используется экземпляр класса LayoutInflater, получаемый с помощью метода getLayoutInflater), созданного ранее. Первый параметр метода inflate
задает идентификатор созданного ранее layout - R.layout.toast (в данном случае он соответствует файлу res/layout/toast.xml). Затем получаются ссылки на изображение, заголовок и текст сообщения, и заполняются нужными данными. Далее непосредственно создается Toast, задаются необходимые параметры и в качестве layout прописывается тот layout, который мы инициализировали ранее Делается это с помощью метода setView. В результате всех этих манипуляций у нас получится Toast, выглядящий как на рисунке ниже.


Вызов Toast из другого потока

Для вызова Toast из другого потока необходимо объявить в вашем классе переменную-член типа Handler следующим образом:
Handler handler = new Handler();
Теперь допустим у вас есть поток, в котором нужно вызвать функцию, отображающую Toast на экране:
new Thread(new Runnable() {
public void run() {
showToast();
}
}).start();
В данный момент функция будет выполняться в отдельно созданном потоке, но Toast может отображен только в главном потоке, то есть в данном случае вы увидите сообщение об ошибке.

Чтобы выполнить действия в главном потоке при вызове из отдельного потока, необходим Handler, который мы объявили ранее. Отобразить Toast из созданного потока можно следующим образом:
public void showToast() {
handler.post(new Runnable() {
public void run() {
Toast.makeText(getApplicationContext(), "Привет, мир!",
Toast.LENGTH_SHORT).show();
}
});
}
На этом первая статья о способах оповещения пользователя закончена. Если вы нашли ошибку или хотите добавить какие-то другие способы работы с Toast, напишите комментарии и я обязательно добавлю ваши замечания в статью.

9 комментариев:

  1. Отличное описание! Жду с нетерпением новых статей!

    ОтветитьУдалить
  2. спасибо, узнал интересные мещи про тоаст!

    ОтветитьУдалить
  3. Спасибо! Продолжайте подобные статьи! Очень познавательно!

    ОтветитьУдалить
  4. Статья хорошая, спасибо. Хотелось бы еще почитать о ProgressDialog и о том, что лучше вместе с ним использовать - треды или бекграунд-таски.

    ОтветитьУдалить
  5. Отличная статья! Ждем продолжения.

    ОтветитьУдалить
  6. troorl, про диалог буду рассказывать в третьей статье цикла, вторая будет посвящена Notification, потому что про диалоги довольно много писать.

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

    ОтветитьУдалить
  7. Максим, спасибо большое за статью!
    Подскажите, пожалуйста, как показать Toast из сервиса, у меня в цикле сделать это не получилось.
    Пример кода (функция класса-сервиса):

    @Override
    public void onCreate()
    {
    super.onCreate();
    timer.scheduleAtFixedRate(
    new TimerTask() {
    public void run() {
    getAdsFromServer();
    }
    },
    0,
    5000);
    }

    Функция getAdsFromServer() выполняет простейший код показа toast. В результате ничего не показывается (

    ОтветитьУдалить
  8. Pavlo, не показывается потому что ты выполняешь Toast в отдельном потоке:

    public void run() {

    }

    Тебе нужно объявить Handler handler = new Handler();

    и потом в run()

    прописывать типа

    handler.post(new Runnable() {
    public void run() {
    getAdsFromServer();
    }
    });

    то бишь handler позволит тебе показывать выполнять getAdsFromServer(); в главном поток, а если нужно только Toast нужно показать, то внутри этого метода используешь handler, как я показал.

    ОтветитьУдалить
  9. Скажите, пожалуйста, а если мне надо показывать маленькое окошко поверх других окошек, но при этом оно должно быть способно воспринимать нажатия на кнопки в нем. Тост никогда не получает пользовательский фокус =( кнопку задать в нем можно,а нажатие на нее нельзя

    ОтветитьУдалить