Основные возможности:
Краткое описание:
В Android-приложении часто требуется выводить небольшие информационные сообщения на экран, которые не влияют на работу приложения. Обычно это делается с помощью встроенной в Android библиотеки Toast.
Toast.makeText(mContext, "Сообщение", Toast.LENGTH_SHORT).show();
Начинаешь использовать его и думаешь, что он удобен, и начинаешь показывать любую информацию через Toast. Однако со временем выясняется, что стандартный вид Toast недостаточно привлекателен, а также ограничены варианты анимации появления и исчезновения. Время отображения-toast можно выбрать только между SHORT и LONG.
После изучения исходного кода Toast была создана расширенная версия ExToast, которая предоставляет следующие методы для изменения содержимого:
setView(View):void
setDuration(int):void
setMargin(float,float):void
setGravity(int,int,int):void
setText(int):void
setText(CharSequence):void
Это позволяет заменять представление, устанавливать время отображения, задавать отступы, управлять положением отображения и менять текстовое содержание.
На основе стандартной реализации Toast были добавлены два новых метода:
setDuration(int):void
setAnimations(int):void
```Метод setDuration теперь позволяет задавать произвольное время отображения в секундах. Предоставлены три значения по умолчанию: `LENGTH_SHORT`, `LENGTH_LONG`, `LENGTH_ALWAYS`. Последнее значение (`LENGTH_ALWAYS`) означает, что Toast будет постоянно отображаться до вызова метода hide().
---
Пример использования ExToast:
```java
ExToast exToast = ExToast.makeText(context, "сообщение", ExToast.LENGTH_ALWAYS);
exToast.setAnimations(R.style.anim_view);
exToast.show();
// При использовании LENGTH_ALWAYS следует вызвать hide() в подходящий момент
exToast.hide();
Вышеуказанный код позволяет задавать собственные xml-анимации для Toast и обеспечивает длительное отображение сообщений. Далее рассмотрим пример стилизованных анимаций.
styles.xml
<style name="anim_view">
<item name="@android:windowEnterAnimation">@anim/anim_in</item>
<item name="@android:windowExitAnimation">@anim/anim_out</item>
</style>
anim_in.xml
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="85"
android:duration="1"
/>
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="-105"
android:duration="350"
android:fillAfter="true"
android:interpolator="@android:anim/decelerate_interpolator"
/>
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="100"
/>
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="0"
android:toYDelta="20"
android:duration="80"
android:fillAfter="true"
android:startOffset="350"
/>
</set>
Для просмотра конкретного эффекта запустите демонстрацию.
Каждый, кто использовал Toast, знает, что он предоставляет два временных интервала — LENGTH_SHORT и LENGTH_LONG, продолжительность которых составляет 2 секунды и около 3 секунд соответственно. Для всех Toast'ов, которые отображаются менее чем за 3 секунды, можно использовать toast.cancel()
, чтобы прекратить их отображение. Однако если требуется отобразить Toast с длительностью более 3 секунд, это становится невозможным.
Проблема времени отображения ещё не самая серьёзная. Самой серьёзной проблемой является то, что системные Toast'ы отображаются в порядке очереди, и следующий Toast начинает отображаться только после того, как текущий завершится.
Многие студенты сталкиваются с этой проблемой, например, при создании кнопки, которая показывает Toast при нажатии. При проведении небольшого теста на нагрузку, такого как быстрое нажатие кнопки сохранения, очередь Toast'ов может стать очень длинной и продолжать отображаться несколько минут.
Чтобы понять работу метода show()
в классе Toast, рассмотрим его реализацию:
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
``` INotificationManager service = getService();
String pkg = mContext.getPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Пустое
}
}
```Можно заметить, что основное отображение и скрытие Toast заключены в методе `enqueueToast` интерфейса `INotificationManager`. Видя слово "enqueue", можно понять, что это функция обработки очереди, которая принимает три аргумента: packageName, объект tn и продолжительность. Соответственно с эффектом отображения Toast, можно предположить, что внутренняя реализация этого метода состоит в последовательном отображении и скрытии каждого переданного Toast. Package name и продолжительность мы уже знаем, так что акцент делается именно на этом объекте tn.
<font color=#0099ff><b>*Но что же такое этот объект tn?*</b></font>
Продолжая изучение исходного кода Toast, можно узнать, что Toast является конкретной формой системного плавающего окна. Его ключевым моментом является приватный статический вложенный класс `class TN`, который занимается отображением и скрытием Toast. <font color=#0099ff><b>*Поэтому, используя рефлексию, можно получить этот объект TN и активно управлять отображением и скрытием Toast, минуя системный сервис*</b></font>Исходный код класса TN:
```java
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
handleShow();
}
};
final Runnable mHide = new Runnable() {
@Override
public void run() {
handleHide();
// Do not do this in handleHide(), as it is also called by the handleShow() method
mNextView = null;
}
};
...
final Handler mHandler = new Handler();
...
View mView;
View mNextView;
WindowManager mWM;
}
``` TN() {
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.title = "Toast";
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* планирование выполнения метода handleShow в правильном потоке
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.post(mShow);
}
/**
* планирование выполнения метода handleHide в правильном потоке
*/
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}```markdown
private void trySendAccessibilityEvent() {. . . }
public void handleHide() {
. . .
if (mView != null) {
// Note: the parent() check is performed only to ensure that
// the view has been added. I have seen cases where we reach
// this point when the view has not yet been added, so let's
// try to avoid a crash.
if (mView.getParent() != null) {
. . .
mWM.removeView(mView);
}
mView = null;
}
}
}
``````java
public void show(){
...
WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWN.addView(mView, mParams);
}
public void hide(){
...
WindowManager mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWN.removeView(mView);
}
Суть работы Toast заключается в добавлении и удалении view через WindowManager. Для реализации пользовательской длительности отображения можно переопределить методы show()
и hide()
.
private void initTN() {
try {
Field tnField = toast.getClass().getDeclaredField("mTN");
tnField.setAccessible(true);
mTN = (ITransientNotification) tnField.get(toast);
/** Установите mNextView перед вызовом tn.show() */
Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");
tnNextViewField.setAccessible(true);
tnNextViewField.set(mTN, toast.getView());
} catch (Exception e) {
e.printStackTrace();
}
}
public void show(){
initTN();
mTN.show();
}
Объект mTN
получается из Toast с помощью рефлексии, тип объекта — ITransientNotification
, который взят из исходников Android. После активного вызова метода mTN.show()
, Toast будет долго отображаться на экране даже после выхода из приложения, пока не будет вызван метод mTN.hide()
.
Проблема с длительностью отображения Toast решена, но остаётся вопрос о возможности создания пользовательских анимаций. В методе инициализации TN используется объект WindowManager.LayoutParams
. Если вы работали над созданием плавающих окон, вы должны были столкнуться с этим объектом. Вот ключевое место, где определяются анимации окна:```java
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
К сожалению, `params` является приватным свойством, поэтому придётся использовать рефлексию для получения доступа к нему и изменения анимации окна:
```java
private void initTN() {
try {
Field tnField = toast.getClass().getDeclaredField("mTN");
tnField.setAccessible(true);
mTN = (ITransientNotification) tnField.get(toast);
/** Установите mNextView перед вызовом tn.show() */
Field tnNextViewField = mTN.getClass().getDeclaredField("mNextView");
tnNextViewField.setAccessible(true);
tnNextViewField.set(mTN, toast.getView());
} catch (Exception e) {
e.printStackTrace();
}
}
params
и повторное определение анимации окна/**Получаем params и переопределяем анимацию окна*/
Field tnParamsField = mTN.getClass().getDeclaredField("mParams");
tnParamsField.setAccessible(true);
WindowManager.LayoutParams params = (WindowManager.LayoutParams) tnParamsField.get(mTN);
params.windowAnimations = R.style.anim_view;
} catch (Exception e) {
e.printStackTrace();
}
---
## Чёрная технология Android: Toast. Системное плавающее окно без необходимости получения разрешений
Ранее мы уже говорили, что Toast является конкретной формой системного плавающего окна. Но какова же его суть? Какие различия между ним и обычным системным плавающим окном?
Давайте рассмотрим традиционную реализацию плавающего окна в Android:
```java
// Получаем контекст приложения
mContext = context.getApplicationContext();
// Получаем WindowManager
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mView = setUpView(context);
``````markdown
Финальный объект WindowManager.LayoutParams используется для инициализации:
```java
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
// Тип
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
int flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
params.flags = flags;
params.format = PixelFormat.TRANSLUCENT;
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
params.gravity = Gravity.CENTER;
mWindowManager.addView(mView, params);
В отличие от этого, внутри внутреннего класса TN Toast инициализация WindowManager.LayoutParams происходит следующим образом:
// Тип
params.type = WindowManager.LayoutParams.TYPE_TOAST;
Как видно, мы используем Toast для создания постоянно отображаемого плавающего окна. Основное различие между обычным плавающим окном и плавающим окном Toast заключается в том, что последнее не требует специальных разрешений! При использовании Toast в нашем приложении мы не указываем никаких дополнительных разрешений, тогда как использование обычного плавающего окна требует наличия следующего разрешения:
<!-- Отображение верхнего уровня плавающего окна -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Все это происходит вне поля зрения пользователя, и лишь разработчики знают эти различия. Из того, что известно пользователям, можно выделить то, что Toast не может покрывать системную панель состояния, в то время как большинство других типов плавающих окон могут её покрывать. Более подробные различия будут добавлены в будущем.
````Дополнительные материалы доступны здесь:`
Механизмы добавления окон Activity, Dialog, PopWindow, Toast в Android-приложении и анализ исходного кода
Пожалуйста, используйте "Issues" для отчета об ошибках или неправильной информации.```
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )