diff --git a/.github/ISSUE_TEMPLATE/issue_template_bug.md b/.github/ISSUE_TEMPLATE/issue_template_bug.md
index c23ccae..3ee27d2 100644
--- a/.github/ISSUE_TEMPLATE/issue_template_bug.md
+++ b/.github/ISSUE_TEMPLATE/issue_template_bug.md
@@ -22,6 +22,8 @@ assignees: getActivity
* 出现问题的安卓版本【必填】:请填写出现问题的 Android 版本
+* 问题信息的来源渠道【必填】:请填写问题的来源(例如:自己遇到的/Bugly 看到的/用户反馈等等)
+
#### 请回答
* 是部分机型还是所有机型都会出现【必答】:部分/全部(例如:某为,某 Android 版本会出现)
diff --git a/README.md b/README.md
index 09a58ad..c45142d 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
# 吐司框架
-* 项目地址:[Github](https://github.com/getActivity/ToastUtils)、[码云](https://gitee.com/getActivity/ToastUtils)
+* 项目地址:[Github](https://github.com/getActivity/ToastUtils)
* 博客地址:[只需体验三分钟,你就会跟我一样,爱上这款 Toast](https://www.jianshu.com/p/9b174ee2c571)
-* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ToastUtils/releases/download/10.5/ToastUtils.apk)
+* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ToastUtils/releases/download/11.0/ToastUtils.apk)

@@ -47,7 +47,7 @@ android {
dependencies {
// 吐司框架:https://github.com/getActivity/ToastUtils
- implementation 'com.github.getActivity:ToastUtils:10.5'
+ implementation 'com.github.getActivity:ToastUtils:11.0'
}
```
@@ -72,6 +72,7 @@ public class XxxApplication extends Application {
// 显示 Toast
ToastUtils.show(CharSequence text);
ToastUtils.show(int id);
+ToastUtils.show(ToastParams params);
// debug 模式下显示 Toast
ToastUtils.debugShow(int id);
@@ -140,15 +141,20 @@ ToastUtils.init(this, new ToastStrategy() {
| 功能或细节 | [ToastUtils](https://github.com/getActivity/ToastUtils) | [AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) | [Toasty](https://github.com/GrenderG/Toasty) |
| :----: | :------: | :-----: | :-----: |
-| 对应版本 | 10.5 | 1.30.6 | 1.5.0 |
+| 对应版本 | 11.0 | 1.30.6 | 1.5.0 |
| issues 数 | [](https://github.com/getActivity/ToastUtils/issues) | [](https://github.com/Blankj/AndroidUtilCode/issues) | [](https://github.com/GrenderG/Toasty/issues) |
-| **aar 包大小** | 29 KB | 500 KB | 50 KB |
+| **aar 包大小** | 31 KB | 500 KB | 50 KB |
+| 框架维护状态 | **维护中** | 停止维护 | 停止维护 |
| **调用代码定位** | ✅ | ❌ | ❌ |
-| 支持在子线程中调用显示 | ✅ | ✅ | ❌ |
-| 支持全局设置统一 Toast 样式 | ✅ | ❌ | ❌ |
-| 处理 Toast 在 Android 7.1 崩溃的问题 | ✅ | ✅ | ❌ |
-| 兼容通知栏权限关闭后 Toast 显示不出来的问题 | ✅ | ✅ | ❌ |
-| 适配 Android 11 不能在后台显示 Toast 的问题 | ✅ | ❌ | ❌ |
+| **支持在子线程中调用显示** | ✅ | ✅ | ❌ |
+| 支持设置**局部** Toast 样式 | ✅ | ❌ | ❌ |
+| 支持设置**全局** Toast 样式 | ✅ | ❌ | ❌ |
+| 支持 Toast **即显即示** | ✅ | ✅ | ❌ |
+| 支持 Toast **排队显示** | ✅ | ❌ | ✅ |
+| 支持 Toast **延迟显示** | ✅ | ❌ | ❌ |
+| **处理 Toast 在 Android 7.1 崩溃的问题** | ✅ | ✅ | ❌ |
+| **兼容通知栏权限关闭后 Toast 显示不出来的问题** | ✅ | ✅ | ❌ |
+| **适配 Android 11 不能在后台显示 Toast 的问题** | ✅ | ❌ | ❌ |
#### 调用代码定位功能介绍
@@ -170,7 +176,7 @@ ToastUtils.init(this, new ToastStrategy() {
* 这个问题的出现是因为原生 Toast 的显示要通过 NMS(NotificationManagerService) 才会 addView 到 Window 上面,而在 NMS 中有一个 `static final boolean ENABLE_BLOCKED_TOASTS = true` 的字段,当这个常量值为 true 时,会触发 NMS 对应用通知栏权限的检查,如果没有通知栏权限,那么这个 Toast 将会被 NMS 所拦截,并输出 `Suppressing toast from package` 日志信息,而小米手机没有这个问题是因为它是将 `ENABLE_BLOCKED_TOASTS` 字段值修改成 `false`,所以就不会触发对通知栏权限的检查,另外我为什么会知道有这个事情?因为我曾经和一名 MIUI 工程师一起确认过这个事情。
-* 框架处理这个问题的方式有两种,先判断当前应用是否处于前台状态,如果是则使用自定义的 WindowManager 代替 Toast 来显示,如果当前应用处于后台状态,则会通过 Hook Toast 中的 INotificationManager 接口,将 enqueueToast 方法传递的包名参数修改成 `android` 来欺骗 NotificationManagerService,因为 NotificationManagerService 已经将 `android` 包名的应用纳入白名单,会自动放行,需要注意的是,这种方式在 Android 10 上面已经失效了,已经被系统纳入反射黑名单,但是好消息是,通过查看和对比 NotificationManagerService 源码发现,这个问题(关闭通知栏权限后无法在前台弹 Toast 的问题)已经在 Android 10.0 的版本上面被修复了,所以框架只在 Android 9.0 及以下版本并且在关闭了通知栏权限的情况下才去 Hook INotificationManager,另外我还找到了官方关于这块的代码提交记录:[Always allow toasts from foreground apps](https://cs.android.com/android/_/android/platform/frameworks/base/+/58b2453ed69197d765c7254241d9966ee49a3efb),大家可以感兴趣可以看看,还有一个问题,如果你想在 Android 10 之后仍然能在后台显示 Toast,请保证应用的通知栏权限或者悬浮窗权限处于开启的状态。
+* 框架处理这个问题的方式有两种,先判断当前应用是否处于前台状态,如果是则使用自定义的 WindowManager 代替 Toast 来显示,如果当前应用处于后台状态,则会通过 Hook Toast 中的 INotificationManager 接口,将 enqueueToast 方法传递的包名参数修改成 `android` 来欺骗 NotificationManagerService,因为 NotificationManagerService 已经将 `android` 包名的应用纳入白名单,会自动放行,需要注意的是,这种方式在 Android 10 上面已经失效了,已经被系统纳入反射黑名单,但是好消息是,通过查看和对比 NotificationManagerService 源码发现,这个问题(关闭通知栏权限后无法在前台弹 Toast 的问题)已经在 Android 10.0 的版本上面被修复了,所以框架只在 Android 9.0 及以下版本并且在关闭了通知栏权限的情况下才去 Hook INotificationManager,另外我还找到了官方关于这块的代码提交记录:[Always allow toasts from foreground apps](https://cs.android.com/android/_/android/platform/frameworks/base/+/58b2453ed69197d765c7254241d9966ee49a3efb),大家可以感兴趣可以看看,还有一个问题,如果你想在 Android 10 之后仍然能在后台显示 Toast,请保证应用的通知栏权限或者悬浮窗权限处于开启的状态,如果你一定要求在后台要 100% 能显示 Toast,请保证应用有悬浮窗权限,因为在某些厂商的手机上,就算有通知栏权限也是无法在后台显示 Toast,例如我用 HarmonyOS 2.0 测试就不行,所以具体要看产品怎么斟酌。
#### Android 11 不能在后台显示 Toast 的问题介绍
@@ -253,6 +259,8 @@ new Toast
* Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)  
+* Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
+
* Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)  
* Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins)  
diff --git a/app/build.gradle b/app/build.gradle
index 5ab94b7..e483d75 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "com.hjq.toast.demo"
minSdkVersion 16
targetSdkVersion 31
- versionCode 1050
- versionName "10.5"
+ versionCode 1100
+ versionName "11.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
@@ -62,14 +62,16 @@ dependencies {
implementation 'com.google.android.material:material:1.4.0'
// 标题栏框架:https://github.com/getActivity/TitleBar
- implementation 'com.github.getActivity:TitleBar:9.3'
+ implementation 'com.github.getActivity:TitleBar:9.5'
// 权限请求框架:https://github.com/getActivity/XXPermissions
- implementation 'com.github.getActivity:XXPermissions:13.6'
+ implementation 'com.github.getActivity:XXPermissions:16.2'
// 悬浮窗框架:https://github.com/getActivity/XToast
implementation 'com.github.getActivity:XToast:8.5'
// 内存泄漏捕捉:https://github.com/square/leakcanary
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
+
+ implementation 'com.tencent.mars:mars-xlog:1.2.6'
}
\ No newline at end of file
diff --git a/app/src/main/java/com/hjq/toast/demo/MainActivity.java b/app/src/main/java/com/hjq/toast/demo/MainActivity.java
index f219571..c18ef5a 100644
--- a/app/src/main/java/com/hjq/toast/demo/MainActivity.java
+++ b/app/src/main/java/com/hjq/toast/demo/MainActivity.java
@@ -15,8 +15,11 @@
import com.hjq.bar.TitleBar;
import com.hjq.permissions.Permission;
import com.hjq.permissions.XXPermissions;
+import com.hjq.toast.ToastParams;
+import com.hjq.toast.ToastStrategy;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.style.BlackToastStyle;
+import com.hjq.toast.style.CustomViewToastStyle;
import com.hjq.toast.style.WhiteToastStyle;
import com.hjq.xtoast.XToast;
@@ -44,16 +47,22 @@ public void onTitleClick(TitleBar titleBar) {
});
}
- public void show1(View v) {
+ public void showToast(View v) {
ToastUtils.show("我是普通的 Toast");
}
- public void show2(View v) {
+ public void showThriceToast(View v) {
+ for (int i = 0; i < 3; i++) {
+ ToastUtils.show("我是第 " + (i + 1) + " 个 Toast");
+ }
+ }
+
+ public void delayShowToast(View v) {
ToastUtils.delayedShow("我是延迟 2 秒显示的 Toast", 2000);
}
@SuppressWarnings("AlibabaAvoidManuallyCreateThread")
- public void show3(View v) {
+ public void threadShowToast(View v) {
new Thread(new Runnable() {
@Override
@@ -63,23 +72,36 @@ public void run() {
}).start();
}
- public void show4(View v) {
+ public void switchToastStyleToWhite(View v) {
ToastUtils.setStyle(new WhiteToastStyle());
ToastUtils.show("动态切换白色吐司样式成功");
}
- public void show5(View v) {
+ public void switchToastStyleToBlack(View v) {
ToastUtils.setStyle(new BlackToastStyle());
ToastUtils.show("动态切换黑色吐司样式成功");
}
- public void show6(View v) {
+ public void customLocalToastStyle(View v) {
+ ToastParams params = new ToastParams();
+ params.text = "我是自定义布局的 Toast(局部生效)";
+ params.style = new CustomViewToastStyle(R.layout.toast_custom_view);
+ ToastUtils.show(params);
+ }
+
+ public void customGlobalToastStyle(View v) {
ToastUtils.setView(R.layout.toast_custom_view);
ToastUtils.setGravity(Gravity.CENTER);
- ToastUtils.show("自定义 Toast 布局");
+ ToastUtils.show("我是自定义布局的 Toast(全局生效)");
+ }
+
+ public void switchToastStrategy(View v) {
+ ToastUtils.setStrategy(new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE));
+ ToastUtils.show("切换到排队显示策略成功,请点击下方文本来体验效果");
+ findViewById(R.id.tv_main_thrice_show).setVisibility(View.VISIBLE);
}
- public void show7(View v) {
+ public void toBackgroundShowToast(View v) {
Snackbar.make(getWindow().getDecorView(), "温馨提示:安卓 10 在后台显示 Toast 需要有通知栏权限或者悬浮窗权限的情况下才可以显示", Snackbar.LENGTH_SHORT).show();
v.postDelayed(new Runnable() {
@@ -107,7 +129,7 @@ public void run() {
}, 3000);
}
- public void show8(View v) {
+ public void combinationXToastShow(View v) {
new XToast<>(this)
.setDuration(1000)
// 将 ToastUtils 中的 View 转移给 XToast 来显示
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index dc79f2e..8e0d888 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -34,63 +34,90 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
- android:onClick="show1"
+ android:onClick="showToast"
android:text="简简单单显示 Toast" />
+ android:layout_marginTop="15dp"
+ android:onClick="customLocalToastStyle"
+ android:text="自定义 Toast 布局(局部生效)" />
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/toast_custom_view.xml b/app/src/main/res/layout/toast_custom_view.xml
index 08f1be4..942d7a9 100644
--- a/app/src/main/res/layout/toast_custom_view.xml
+++ b/app/src/main/res/layout/toast_custom_view.xml
@@ -8,8 +8,8 @@
android:padding="10dp">
+ android:textSize="14sp" />
\ No newline at end of file
diff --git a/library/build.gradle b/library/build.gradle
index f8784c2..2f377f4 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -5,8 +5,8 @@ android {
defaultConfig {
minSdkVersion 14
- versionCode 1050
- versionName "10.5"
+ versionCode 1100
+ versionName "11.0"
}
// 使用 JDK 1.8
diff --git a/library/src/main/java/com/hjq/toast/ActivityStack.java b/library/src/main/java/com/hjq/toast/ActivityStack.java
index 02e20be..d47840c 100644
--- a/library/src/main/java/com/hjq/toast/ActivityStack.java
+++ b/library/src/main/java/com/hjq/toast/ActivityStack.java
@@ -1,5 +1,6 @@
package com.hjq.toast;
+import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
@@ -12,13 +13,31 @@
*/
final class ActivityStack implements Application.ActivityLifecycleCallbacks {
+ @SuppressLint("StaticFieldLeak")
+ private static volatile ActivityStack sInstance;
+
+ public static ActivityStack getInstance() {
+ if(sInstance == null) {
+ synchronized (ActivityStack.class) {
+ if(sInstance == null) {
+ sInstance = new ActivityStack();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ /** 私有化构造函数 */
+ private ActivityStack() {}
+
/**
- * 注册
+ * 注册 Activity 生命周期监听
*/
- static ActivityStack register(Application application) {
- ActivityStack lifecycle = new ActivityStack();
- application.registerActivityLifecycleCallbacks(lifecycle);
- return lifecycle;
+ public void register(Application application) {
+ if (application == null) {
+ return;
+ }
+ application.registerActivityLifecycleCallbacks(this);
}
/** 前台 Activity 对象 */
diff --git a/library/src/main/java/com/hjq/toast/WindowToast.java b/library/src/main/java/com/hjq/toast/ApplicationToast.java
similarity index 84%
rename from library/src/main/java/com/hjq/toast/WindowToast.java
rename to library/src/main/java/com/hjq/toast/ApplicationToast.java
index 3e4b024..4fe031b 100644
--- a/library/src/main/java/com/hjq/toast/WindowToast.java
+++ b/library/src/main/java/com/hjq/toast/ApplicationToast.java
@@ -8,12 +8,12 @@
* time : 2021/11/30
* desc : 利用悬浮窗权限弹出全局 Toast
*/
-public class WindowToast extends CustomToast {
+public class ApplicationToast extends CustomToast {
/** Toast 实现类 */
private final ToastImpl mToastImpl;
- public WindowToast(Application application) {
+ public ApplicationToast(Application application) {
mToastImpl = new ToastImpl(application, this);
}
diff --git a/library/src/main/java/com/hjq/toast/ToastLogInterceptor.java b/library/src/main/java/com/hjq/toast/ToastLogInterceptor.java
index d6abd5c..087c12e 100644
--- a/library/src/main/java/com/hjq/toast/ToastLogInterceptor.java
+++ b/library/src/main/java/com/hjq/toast/ToastLogInterceptor.java
@@ -15,8 +15,8 @@
public class ToastLogInterceptor implements IToastInterceptor {
@Override
- public boolean intercept(CharSequence text) {
- printToast(text);
+ public boolean intercept(ToastParams params) {
+ printToast(params.text);
return false;
}
diff --git a/library/src/main/java/com/hjq/toast/ToastParams.java b/library/src/main/java/com/hjq/toast/ToastParams.java
new file mode 100644
index 0000000..3044073
--- /dev/null
+++ b/library/src/main/java/com/hjq/toast/ToastParams.java
@@ -0,0 +1,32 @@
+package com.hjq.toast;
+
+import com.hjq.toast.config.IToastInterceptor;
+import com.hjq.toast.config.IToastStrategy;
+import com.hjq.toast.config.IToastStyle;
+
+/**
+ * author : Android 轮子哥
+ * github : https://github.com/getActivity/ToastUtils
+ * time : 2022/10/31
+ * desc : Toast 参数类
+ */
+public final class ToastParams {
+
+ /** 显示的文本 */
+ public CharSequence text;
+
+ /** 延迟时间 */
+ public long delayMillis = 0;
+
+ /** 显示时长 */
+ public int toastDuration = -1;
+
+ /** Toast 样式 */
+ public IToastStyle> style;
+
+ /** Toast 拦截器 */
+ public IToastInterceptor interceptor;
+
+ /** Toast 策略 */
+ public IToastStrategy strategy;
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/hjq/toast/ToastStrategy.java b/library/src/main/java/com/hjq/toast/ToastStrategy.java
index 0a90c92..df98f3a 100644
--- a/library/src/main/java/com/hjq/toast/ToastStrategy.java
+++ b/library/src/main/java/com/hjq/toast/ToastStrategy.java
@@ -9,6 +9,7 @@
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemClock;
import android.provider.Settings;
import android.widget.Toast;
@@ -30,98 +31,185 @@
*/
public class ToastStrategy implements IToastStrategy {
+ /**
+ * 即显即示模式
+ *
+ * 在发起多次 Toast 的显示请求情况下,显示下一个 Toast 之前
+ * 会先立即取消上一个 Toast,保证当前显示 Toast 消息是最新的
+ */
+ public static final int SHOW_STRATEGY_TYPE_IMMEDIATELY = 0;
+
+ /**
+ * 不丢消息模式
+ *
+ * 在发起多次 Toast 的显示请求情况下,等待上一个 Toast 显示 1 秒或者 1.5 秒后
+ * 然后再显示下一个 Toast,不按照 Toast 的显示时长来,因为那样等待时间会很长
+ * 这样既能保证用户能看到每一条 Toast 消息,又能保证用户不会等得太久,速战速决
+ */
+ public static final int SHOW_STRATEGY_TYPE_QUEUE = 1;
+
/** Handler 对象 */
private final static Handler HANDLER = new Handler(Looper.getMainLooper());
- /** 延迟时间 */
- private static final int DELAY_TIMEOUT = 200;
+ /**
+ * 默认延迟时间
+ *
+ * 延迟一段时间之后再执行,因为在没有通知栏权限的情况下,Toast 只能显示在当前 Activity 上面
+ * 如果当前 Activity 在 showToast 之后立马进行 finish 了,那么这个时候 Toast 可能会显示不出来
+ * 因为 Toast 会显示在销毁 Activity 界面上,而不会显示在新跳转的 Activity 上面
+ */
+ private static final int DEFAULT_DELAY_TIMEOUT = 200;
/** 应用上下文 */
private Application mApplication;
- /** Activity 栈管理 */
- private ActivityStack mActivityStack;
-
/** Toast 对象 */
private WeakReference mToastReference;
- /** Toast 样式 */
- private IToastStyle> mToastStyle;
+ /** 吐司显示策略 */
+ private final int mShowStrategyType;
- /** 最新的文本 */
- private volatile CharSequence mLatestText;
+ /** 显示消息 Token */
+ private final Object mShowMessageToken = new Object();
+ /** 取消消息 Token */
+ private final Object mCancelMessageToken = new Object();
- @Override
- public void registerStrategy(Application application) {
- mApplication = application;
- mActivityStack = ActivityStack.register(application);
+ /** 上一个 Toast 显示的时间 */
+ private volatile long mLastShowToastMillis;
+
+ public ToastStrategy() {
+ this(ToastStrategy.SHOW_STRATEGY_TYPE_IMMEDIATELY);
+ }
+
+ public ToastStrategy(int type) {
+ mShowStrategyType = type;
+ switch (mShowStrategyType) {
+ case SHOW_STRATEGY_TYPE_IMMEDIATELY:
+ case SHOW_STRATEGY_TYPE_QUEUE:
+ break;
+ default:
+ throw new IllegalArgumentException("Please don't pass non-existent toast show strategy");
+ }
}
@Override
- public void bindStyle(IToastStyle> style) {
- mToastStyle = style;
+ public void registerStrategy(Application application) {
+ mApplication = application;
+ ActivityStack.getInstance().register(application);
}
@Override
- public IToast createToast(Application application) {
- Activity foregroundActivity = mActivityStack.getForegroundActivity();
+ public IToast createToast(IToastStyle> style) {
+ Activity foregroundActivity = ActivityStack.getInstance().getForegroundActivity();
IToast toast;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
- Settings.canDrawOverlays(application)) {
+ Settings.canDrawOverlays(mApplication)) {
// 如果有悬浮窗权限,就开启全局的 Toast
- toast = new WindowToast(application);
+ toast = new ApplicationToast(mApplication);
} else if (foregroundActivity != null) {
// 如果没有悬浮窗权限,就开启一个依附于 Activity 的 Toast
toast = new ActivityToast(foregroundActivity);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
// 处理 Android 7.1 上 Toast 在主线程被阻塞后会导致报错的问题
- toast = new SafeToast(application);
+ toast = new SafeToast(mApplication);
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
- !areNotificationsEnabled(application)) {
+ !areNotificationsEnabled(mApplication)) {
// 处理 Toast 关闭通知栏权限之后无法弹出的问题
// 通过查看和对比 NotificationManagerService 的源码
// 发现这个问题已经在 Android 10 版本上面修复了
// 但是 Toast 只能在前台显示,没有通知栏权限后台 Toast 仍然无法显示
// 并且 Android 10 刚好禁止了 Hook 通知服务
// 已经有通知栏权限,不需要 Hook 系统通知服务也能正常显示系统 Toast
- toast = new NotificationToast(application);
+ toast = new NotificationToast(mApplication);
} else {
- toast = new SystemToast(application);
+ toast = new SystemToast(mApplication);
}
- // targetSdkVersion >= 30 的情况下在后台显示自定义样式的 Toast 会被系统屏蔽,并且日志会输出以下警告:
- // Blocking custom toast from package com.xxx.xxx due to package not in the foreground
- // targetSdkVersion < 30 的情况下 new Toast,并且不设置视图显示,系统会抛出以下异常:
- // java.lang.RuntimeException: This Toast was not created with Toast.makeText()
- if (toast instanceof CustomToast || Build.VERSION.SDK_INT < Build.VERSION_CODES.R ||
- application.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.R) {
- toast.setView(mToastStyle.createView(application));
- toast.setGravity(mToastStyle.getGravity(), mToastStyle.getXOffset(), mToastStyle.getYOffset());
- toast.setMargin(mToastStyle.getHorizontalMargin(), mToastStyle.getVerticalMargin());
+ if (isSupportToastStyle(toast)) {
+ diyToastStyle(toast, style);
}
return toast;
}
@Override
- public void showToast(CharSequence text, long delayMillis) {
- mLatestText = text;
- HANDLER.removeCallbacks(mShowRunnable);
- // 延迟一段时间之后再执行,因为在没有通知栏权限的情况下,Toast 只能显示当前 Activity
- // 如果当前 Activity 在 showToast 之后立马进行 finish 了,那么这个时候 Toast 可能会显示不出来
- // 因为 Toast 会显示在销毁 Activity 界面上,而不会显示在新跳转的 Activity 上面
- HANDLER.postDelayed(mShowRunnable, delayMillis + DELAY_TIMEOUT);
+ public void showToast(ToastParams params) {
+ switch (mShowStrategyType) {
+ case SHOW_STRATEGY_TYPE_IMMEDIATELY: {
+ // 移除之前未显示的 Toast 消息
+ HANDLER.removeCallbacksAndMessages(mShowMessageToken);
+ long uptimeMillis = SystemClock.uptimeMillis() + params.delayMillis + DEFAULT_DELAY_TIMEOUT;
+ HANDLER.postAtTime(new ShowToastRunnable(params), mShowMessageToken, uptimeMillis);
+ break;
+ }
+ case SHOW_STRATEGY_TYPE_QUEUE: {
+ // 计算出这个 Toast 显示时间
+ long showToastMillis = SystemClock.uptimeMillis() + params.delayMillis + DEFAULT_DELAY_TIMEOUT;
+ // 根据吐司的长短计算出等待时间
+ long waitMillis = generateToastWaitMillis(params);
+ // 如果当前显示的时间在上一个 Toast 的显示范围之内
+ // 那么就重新计算 Toast 的显示时间
+ if (showToastMillis < (mLastShowToastMillis + waitMillis)) {
+ showToastMillis = mLastShowToastMillis + waitMillis;
+ }
+ HANDLER.postAtTime(new ShowToastRunnable(params), mShowMessageToken, showToastMillis);
+ mLastShowToastMillis = showToastMillis;
+ break;
+ }
+ default:
+ break;
+ }
}
@Override
public void cancelToast() {
- HANDLER.removeCallbacks(mCancelRunnable);
- HANDLER.post(mCancelRunnable);
+ HANDLER.removeCallbacksAndMessages(mCancelMessageToken);
+ long uptimeMillis = SystemClock.uptimeMillis();
+ HANDLER.postAtTime(new CancelToastRunnable(), mCancelMessageToken, uptimeMillis);
+ }
+
+ /**
+ * 是否支持设置自定义 Toast 样式
+ */
+ protected boolean isSupportToastStyle(IToast toast) {
+ // targetSdkVersion >= 30 的情况下在后台显示自定义样式的 Toast 会被系统屏蔽,并且日志会输出以下警告:
+ // Blocking custom toast from package com.xxx.xxx due to package not in the foreground
+ // targetSdkVersion < 30 的情况下 new Toast,并且不设置视图显示,系统会抛出以下异常:
+ // java.lang.RuntimeException: This Toast was not created with Toast.makeText()
+ return toast instanceof CustomToast || Build.VERSION.SDK_INT < Build.VERSION_CODES.R ||
+ mApplication.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.R;
+ }
+
+ /**
+ * 定制 Toast 的样式
+ */
+ protected void diyToastStyle(IToast toast, IToastStyle> style) {
+ toast.setView(style.createView(mApplication));
+ toast.setGravity(style.getGravity(), style.getXOffset(), style.getYOffset());
+ toast.setMargin(style.getHorizontalMargin(), style.getVerticalMargin());
+ }
+
+ /**
+ * 生成 Toast 等待时间
+ */
+ protected int generateToastWaitMillis(ToastParams params) {
+ if (params.toastDuration == Toast.LENGTH_SHORT) {
+ return 1000;
+ } else if (params.toastDuration == Toast.LENGTH_LONG) {
+ return 1500;
+ }
+ return 0;
}
/**
* 显示任务
*/
- private final Runnable mShowRunnable = new Runnable() {
+ private class ShowToastRunnable implements Runnable {
+
+ private final ToastParams mToastParams;
+
+ private ShowToastRunnable(ToastParams params) {
+ mToastParams = params;
+ }
@Override
public void run() {
@@ -131,24 +219,23 @@ public void run() {
}
if (toast != null) {
- // 取消上一个 Toast 的显示
+ // 取消上一个 Toast 的显示,避免出现重叠的效果
toast.cancel();
}
-
- toast = createToast(mApplication);
+ toast = createToast(mToastParams.style);
// 为什么用 WeakReference,而不用 SoftReference ?
// https://github.com/getActivity/ToastUtils/issues/79
mToastReference = new WeakReference<>(toast);
- toast.setDuration(getToastDuration(mLatestText));
- toast.setText(mLatestText);
+ toast.setDuration(mToastParams.toastDuration);
+ toast.setText(mToastParams.text);
toast.show();
}
- };
+ }
/**
* 取消任务
*/
- private final Runnable mCancelRunnable = new Runnable() {
+ private class CancelToastRunnable implements Runnable {
@Override
public void run() {
@@ -164,13 +251,6 @@ public void run() {
}
};
- /**
- * 获取 Toast 显示时长
- */
- protected int getToastDuration(CharSequence text) {
- return text.length() > 20 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
- }
-
/**
* 是否有通知栏权限
*/
diff --git a/library/src/main/java/com/hjq/toast/ToastUtils.java b/library/src/main/java/com/hjq/toast/ToastUtils.java
index e53d024..80209ad 100644
--- a/library/src/main/java/com/hjq/toast/ToastUtils.java
+++ b/library/src/main/java/com/hjq/toast/ToastUtils.java
@@ -3,13 +3,14 @@
import android.app.Application;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
+import android.widget.Toast;
import com.hjq.toast.config.IToastInterceptor;
import com.hjq.toast.config.IToastStrategy;
import com.hjq.toast.config.IToastStyle;
import com.hjq.toast.style.BlackToastStyle;
+import com.hjq.toast.style.CustomViewToastStyle;
import com.hjq.toast.style.LocationToastStyle;
-import com.hjq.toast.style.ViewToastStyle;
import com.hjq.toast.style.WhiteToastStyle;
/**
@@ -160,20 +161,42 @@ private static void show(Object object, long delayMillis) {
}
private static void show(CharSequence text, long delayMillis) {
+ ToastParams params = new ToastParams();
+ params.text = text;
+ params.delayMillis = delayMillis;
+ show(params);
+ }
+
+ public static void show(ToastParams params) {
// 如果是空对象或者空文本就不显示
- if (text == null || text.length() == 0) {
+ if (params.text == null || params.text.length() == 0) {
return;
}
- if (sToastInterceptor == null) {
- sToastInterceptor = new ToastLogInterceptor();
+ if (params.strategy == null) {
+ params.strategy = sToastStrategy;
+ }
+
+ if (params.interceptor == null) {
+ if (sToastInterceptor == null) {
+ sToastInterceptor = new ToastLogInterceptor();
+ }
+ params.interceptor = sToastInterceptor;
}
- if (sToastInterceptor.intercept(text)) {
+ if (params.style == null) {
+ params.style = sToastStyle;
+ }
+
+ if (params.interceptor.intercept(params)) {
return;
}
- sToastStrategy.showToast(text, delayMillis);
+ if (params.toastDuration == -1) {
+ params.toastDuration = params.text.length() > 20 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
+ }
+
+ params.strategy.showToast(params);
}
/**
@@ -197,7 +220,7 @@ public static void setGravity(int gravity, int xOffset, int yOffset) {
}
public static void setGravity(int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) {
- sToastStrategy.bindStyle(new LocationToastStyle(sToastStyle, gravity, xOffset, yOffset, horizontalMargin, verticalMargin));
+ sToastStyle = new LocationToastStyle(sToastStyle, gravity, xOffset, yOffset, horizontalMargin, verticalMargin);
}
/**
@@ -207,7 +230,9 @@ public static void setView(int id) {
if (id <= 0) {
return;
}
- setStyle(new ViewToastStyle(id, sToastStyle));
+ setStyle(new CustomViewToastStyle(id, sToastStyle.getGravity(),
+ sToastStyle.getXOffset(), sToastStyle.getYOffset(),
+ sToastStyle.getHorizontalMargin(), sToastStyle.getVerticalMargin()));
}
/**
@@ -219,7 +244,6 @@ public static void setView(int id) {
*/
public static void setStyle(IToastStyle> style) {
sToastStyle = style;
- sToastStrategy.bindStyle(style);
}
public static IToastStyle> getStyle() {
diff --git a/library/src/main/java/com/hjq/toast/config/IToastInterceptor.java b/library/src/main/java/com/hjq/toast/config/IToastInterceptor.java
index 4ddb664..7b264d1 100644
--- a/library/src/main/java/com/hjq/toast/config/IToastInterceptor.java
+++ b/library/src/main/java/com/hjq/toast/config/IToastInterceptor.java
@@ -1,5 +1,7 @@
package com.hjq.toast.config;
+import com.hjq.toast.ToastParams;
+
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/ToastUtils
@@ -11,5 +13,5 @@ public interface IToastInterceptor {
/**
* 根据显示的文本决定是否拦截该 Toast
*/
- boolean intercept(CharSequence text);
+ boolean intercept(ToastParams params);
}
\ No newline at end of file
diff --git a/library/src/main/java/com/hjq/toast/config/IToastStrategy.java b/library/src/main/java/com/hjq/toast/config/IToastStrategy.java
index 49aa5b3..dcf2973 100644
--- a/library/src/main/java/com/hjq/toast/config/IToastStrategy.java
+++ b/library/src/main/java/com/hjq/toast/config/IToastStrategy.java
@@ -2,6 +2,8 @@
import android.app.Application;
+import com.hjq.toast.ToastParams;
+
/**
* author : Android 轮子哥
* github : https://github.com/getActivity/ToastUtils
@@ -15,20 +17,15 @@ public interface IToastStrategy {
*/
void registerStrategy(Application application);
- /**
- * 绑定样式
- */
- void bindStyle(IToastStyle> style);
-
/**
* 创建 Toast
*/
- IToast createToast(Application application);
+ IToast createToast(IToastStyle> style);
/**
* 显示 Toast
*/
- void showToast(CharSequence text, long delayMillis);
+ void showToast(ToastParams params);
/**
* 取消 Toast
diff --git a/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java b/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java
index bedadc5..3d79cce 100644
--- a/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java
+++ b/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java
@@ -6,6 +6,7 @@
import android.os.Build;
import android.util.TypedValue;
import android.view.Gravity;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -18,10 +19,10 @@
* desc : 默认黑色样式实现
*/
@SuppressWarnings({"unused", "deprecation"})
-public class BlackToastStyle implements IToastStyle {
+public class BlackToastStyle implements IToastStyle {
@Override
- public TextView createView(Context context) {
+ public View createView(Context context) {
TextView textView = new TextView(context);
textView.setId(android.R.id.message);
textView.setGravity(getTextGravity(context));
diff --git a/library/src/main/java/com/hjq/toast/style/CustomViewToastStyle.java b/library/src/main/java/com/hjq/toast/style/CustomViewToastStyle.java
new file mode 100644
index 0000000..cef6c10
--- /dev/null
+++ b/library/src/main/java/com/hjq/toast/style/CustomViewToastStyle.java
@@ -0,0 +1,75 @@
+package com.hjq.toast.style;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import com.hjq.toast.config.IToastStyle;
+
+/**
+ * author : Android 轮子哥
+ * github : https://github.com/getActivity/ToastUtils
+ * time : 2021/03/09
+ * desc : Toast 自定义 View 包装样式实现
+ */
+public class CustomViewToastStyle implements IToastStyle {
+
+ private final int mLayoutId;
+ private final int mGravity;
+ private final int mXOffset;
+ private final int mYOffset;
+ private final float mHorizontalMargin;
+ private final float mVerticalMargin;
+
+ public CustomViewToastStyle(int id) {
+ this(id, Gravity.CENTER);
+ }
+
+ public CustomViewToastStyle(int id, int gravity) {
+ this(id, gravity, 0, 0);
+ }
+
+ public CustomViewToastStyle(int id, int gravity, int xOffset, int yOffset) {
+ this(id, gravity, xOffset, yOffset, 0f, 0f);
+ }
+
+ public CustomViewToastStyle(int id, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) {
+ mLayoutId = id;
+ mGravity = gravity;
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mHorizontalMargin = horizontalMargin;
+ mVerticalMargin = verticalMargin;
+ }
+
+ @Override
+ public View createView(Context context) {
+ return LayoutInflater.from(context).inflate(mLayoutId, null);
+ }
+
+ @Override
+ public int getGravity() {
+ return mGravity;
+ }
+
+ @Override
+ public int getXOffset() {
+ return mXOffset;
+ }
+
+ @Override
+ public int getYOffset() {
+ return mYOffset;
+ }
+
+ @Override
+ public float getHorizontalMargin() {
+ return mHorizontalMargin;
+ }
+
+ @Override
+ public float getVerticalMargin() {
+ return mVerticalMargin;
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/hjq/toast/style/ViewToastStyle.java b/library/src/main/java/com/hjq/toast/style/ViewToastStyle.java
deleted file mode 100644
index 68dfa09..0000000
--- a/library/src/main/java/com/hjq/toast/style/ViewToastStyle.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.hjq.toast.style;
-
-import android.content.Context;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.hjq.toast.config.IToastStyle;
-
-/**
- * author : Android 轮子哥
- * github : https://github.com/getActivity/ToastUtils
- * time : 2021/03/09
- * desc : Toast View 包装样式实现
- */
-public class ViewToastStyle implements IToastStyle {
-
- private final int mLayoutId;
- private final IToastStyle> mStyle;
-
- public ViewToastStyle(int id, IToastStyle> style) {
- mLayoutId = id;
- mStyle = style;
- }
-
- @Override
- public View createView(Context context) {
- return LayoutInflater.from(context).inflate(mLayoutId, null);
- }
-
- @Override
- public int getGravity() {
- if (mStyle == null) {
- return Gravity.CENTER;
- }
- return mStyle.getGravity();
- }
-
- @Override
- public int getXOffset() {
- if (mStyle == null) {
- return 0;
- }
- return mStyle.getXOffset();
- }
-
- @Override
- public int getYOffset() {
- if (mStyle == null) {
- return 0;
- }
- return mStyle.getYOffset();
- }
-
- @Override
- public float getHorizontalMargin() {
- if (mStyle == null) {
- return 0;
- }
- return mStyle.getHorizontalMargin();
- }
-
- @Override
- public float getVerticalMargin() {
- if (mStyle == null) {
- return 0;
- }
- return mStyle.getVerticalMargin();
- }
-}
\ No newline at end of file