From de577a27c315a469ad665dc92845eec7774232a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Android=20=E8=BD=AE=E5=AD=90=E5=93=A5?= Date: Sun, 19 Feb 2023 10:33:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A1=86=E6=9E=B6=E6=AD=A3=E5=BC=8F=E6=94=B9?= =?UTF-8?q?=E5=90=8D=E4=B8=BA=20Toaster=20=E6=96=B0=E5=A2=9E=E8=8B=B1?= =?UTF-8?q?=E6=96=87=E7=89=88=E6=9C=AC=E7=9A=84=E6=96=87=E6=A1=A3=E5=8F=8A?= =?UTF-8?q?=20Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ISSUE_TEMPLATE/issue_en_template_bug.md | 48 +++ .../issue_en_template_question.md | 19 ++ .../issue_en_template_suggest.md | 19 ++ ...mplate_bug.md => issue_zh_template_bug.md} | 10 +- ...stion.md => issue_zh_template_question.md} | 2 +- ...uggest.md => issue_zh_template_suggest.md} | 6 +- HelpDoc-en.md | 179 +++++++++++ HelpDoc.md => HelpDoc-zh.md | 40 ++- README-en.md | 279 ++++++++++++++++++ README.md | 126 ++++---- app/build.gradle | 12 +- app/src/main/AndroidManifest.xml | 1 - .../java/com/hjq/toast/demo/MainActivity.java | 97 +++--- .../com/hjq/toast/demo/ToastApplication.java | 8 +- app/src/main/res/drawable/toast_error_bg.xml | 7 + app/src/main/res/drawable/toast_error_ic.xml | 11 + app/src/main/res/drawable/toast_hint_bg.xml | 7 + app/src/main/res/drawable/toast_info_ic.xml | 12 + .../main/res/drawable/toast_success_bg.xml | 7 + .../main/res/drawable/toast_success_ic.xml | 12 + app/src/main/res/drawable/toast_warn_bg.xml | 7 + app/src/main/res/drawable/toast_warn_ic.xml | 12 + app/src/main/res/layout/activity_main.xml | 58 ++-- app/src/main/res/layout/toast_error.xml | 30 ++ app/src/main/res/layout/toast_info.xml | 30 ++ app/src/main/res/layout/toast_success.xml | 30 ++ app/src/main/res/layout/toast_warn.xml | 30 ++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3418 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4208 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4842 -> 5604 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6114 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 7718 -> 8920 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10056 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 12814 bytes app/src/main/res/values-zh/strings.xml | 41 +++ app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 40 ++- library/build.gradle | 4 +- .../java/com/hjq/toast/ActivityStack.java | 2 +- .../java/com/hjq/toast/ActivityToast.java | 2 +- .../java/com/hjq/toast/ApplicationToast.java | 2 +- .../main/java/com/hjq/toast/CustomToast.java | 6 +- .../hjq/toast/NotificationServiceProxy.java | 2 +- .../java/com/hjq/toast/NotificationToast.java | 2 +- .../main/java/com/hjq/toast/SafeHandler.java | 2 +- .../main/java/com/hjq/toast/SafeToast.java | 2 +- .../main/java/com/hjq/toast/SystemToast.java | 2 +- .../main/java/com/hjq/toast/ToastImpl.java | 2 +- .../com/hjq/toast/ToastLogInterceptor.java | 35 +-- .../main/java/com/hjq/toast/ToastParams.java | 4 +- .../java/com/hjq/toast/ToastStrategy.java | 6 +- .../toast/{ToastUtils.java => Toaster.java} | 24 +- .../java/com/hjq/toast/WindowLifecycle.java | 2 +- .../java/com/hjq/toast/config/IToast.java | 6 +- .../hjq/toast/config/IToastInterceptor.java | 2 +- .../com/hjq/toast/config/IToastStrategy.java | 2 +- .../com/hjq/toast/config/IToastStyle.java | 2 +- .../com/hjq/toast/style/BlackToastStyle.java | 4 +- ...wToastStyle.java => CustomToastStyle.java} | 12 +- .../hjq/toast/style/LocationToastStyle.java | 2 +- .../com/hjq/toast/style/WhiteToastStyle.java | 2 +- logo.png | Bin 0 -> 8034 bytes picture/demo_code.png | Bin 24152 -> 0 bytes picture/demo_page.jpg | Bin 93452 -> 0 bytes picture/en/demo_logcat_code.jpg | Bin 0 -> 147758 bytes picture/en/demo_toast_activity.jpg | Bin 0 -> 41410 bytes picture/en/demo_toast_layout_custom.jpg | Bin 0 -> 36067 bytes picture/en/demo_toast_layout_error.jpg | Bin 0 -> 39388 bytes picture/en/demo_toast_layout_info.jpg | Bin 0 -> 39049 bytes picture/en/demo_toast_layout_success.jpg | Bin 0 -> 39023 bytes picture/en/demo_toast_layout_warn.jpg | Bin 0 -> 38740 bytes picture/en/demo_toast_style_black.jpg | Bin 0 -> 38307 bytes picture/en/demo_toast_style_white.jpg | Bin 0 -> 37519 bytes picture/logcat_code.jpg | Bin 366147 -> 0 bytes picture/zh/demo_logcat_code.jpg | Bin 0 -> 149727 bytes picture/zh/demo_toast_activity.jpg | Bin 0 -> 33229 bytes picture/zh/demo_toast_layout_custom.jpg | Bin 0 -> 33689 bytes picture/zh/demo_toast_layout_error.jpg | Bin 0 -> 32995 bytes picture/zh/demo_toast_layout_info.jpg | Bin 0 -> 32758 bytes picture/zh/demo_toast_layout_success.jpg | Bin 0 -> 32535 bytes picture/zh/demo_toast_layout_warn.jpg | Bin 0 -> 32399 bytes picture/zh/demo_toast_style_black.jpg | Bin 0 -> 31237 bytes picture/zh/demo_toast_style_white.jpg | Bin 0 -> 30492 bytes picture/zh/download_demo_apk_qr_code.png | Bin 0 -> 8142 bytes picture/zh/help_doc_rename_vote.jpg | Bin 0 -> 155372 bytes 85 files changed, 1106 insertions(+), 210 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/issue_en_template_bug.md create mode 100644 .github/ISSUE_TEMPLATE/issue_en_template_question.md create mode 100644 .github/ISSUE_TEMPLATE/issue_en_template_suggest.md rename .github/ISSUE_TEMPLATE/{issue_template_bug.md => issue_zh_template_bug.md} (78%) rename .github/ISSUE_TEMPLATE/{issue_template_question.md => issue_zh_template_question.md} (79%) rename .github/ISSUE_TEMPLATE/{issue_template_suggest.md => issue_zh_template_suggest.md} (54%) create mode 100644 HelpDoc-en.md rename HelpDoc.md => HelpDoc-zh.md (72%) create mode 100644 README-en.md create mode 100644 app/src/main/res/drawable/toast_error_bg.xml create mode 100644 app/src/main/res/drawable/toast_error_ic.xml create mode 100644 app/src/main/res/drawable/toast_hint_bg.xml create mode 100644 app/src/main/res/drawable/toast_info_ic.xml create mode 100644 app/src/main/res/drawable/toast_success_bg.xml create mode 100644 app/src/main/res/drawable/toast_success_ic.xml create mode 100644 app/src/main/res/drawable/toast_warn_bg.xml create mode 100644 app/src/main/res/drawable/toast_warn_ic.xml create mode 100644 app/src/main/res/layout/toast_error.xml create mode 100644 app/src/main/res/layout/toast_info.xml create mode 100644 app/src/main/res/layout/toast_success.xml create mode 100644 app/src/main/res/layout/toast_warn.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values-zh/strings.xml rename library/src/main/java/com/hjq/toast/{ToastUtils.java => Toaster.java} (92%) rename library/src/main/java/com/hjq/toast/style/{CustomViewToastStyle.java => CustomToastStyle.java} (77%) create mode 100644 logo.png delete mode 100644 picture/demo_code.png delete mode 100644 picture/demo_page.jpg create mode 100644 picture/en/demo_logcat_code.jpg create mode 100644 picture/en/demo_toast_activity.jpg create mode 100644 picture/en/demo_toast_layout_custom.jpg create mode 100644 picture/en/demo_toast_layout_error.jpg create mode 100644 picture/en/demo_toast_layout_info.jpg create mode 100644 picture/en/demo_toast_layout_success.jpg create mode 100644 picture/en/demo_toast_layout_warn.jpg create mode 100644 picture/en/demo_toast_style_black.jpg create mode 100644 picture/en/demo_toast_style_white.jpg delete mode 100644 picture/logcat_code.jpg create mode 100644 picture/zh/demo_logcat_code.jpg create mode 100644 picture/zh/demo_toast_activity.jpg create mode 100644 picture/zh/demo_toast_layout_custom.jpg create mode 100644 picture/zh/demo_toast_layout_error.jpg create mode 100644 picture/zh/demo_toast_layout_info.jpg create mode 100644 picture/zh/demo_toast_layout_success.jpg create mode 100644 picture/zh/demo_toast_layout_warn.jpg create mode 100644 picture/zh/demo_toast_style_black.jpg create mode 100644 picture/zh/demo_toast_style_white.jpg create mode 100644 picture/zh/download_demo_apk_qr_code.png create mode 100644 picture/zh/help_doc_rename_vote.jpg diff --git a/.github/ISSUE_TEMPLATE/issue_en_template_bug.md b/.github/ISSUE_TEMPLATE/issue_en_template_bug.md new file mode 100644 index 0000000..e7133bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_en_template_bug.md @@ -0,0 +1,48 @@ +--- +name: Submit Bug +about: Please tell me the problem with the framework and I will help you fix it! +labels: bug +assignees: getActivity + +--- + + +## [Warning: Please be sure to fill in according to the issue template. Don't take any chances. Once you find that the issue is not filled in carefully according to the template, you will close it directly.] + +#### Description of the problem + +* Framework Version [Required]: Please enter the framework version you are using + +* Problem Description [Required]: Please enter your description of the problem + +* Reproduction step [Required]: Please enter the steps to reproduce the problem (Note: Bug without reproduction step is not accepted at present) + +* Whether the problem can be reproduced [Required]: Yes/No + +* Phone information in question [Required]: Please fill in the phone brand and model in question + +* Android version in question [Required]: Please fill in the Android version in question + +* Source of problem information [Required]: Please fill in the source of the problem (for example: encountered by yourself / see on firebase crashlytics / user feedback, etc.) + +#### Please respond + +* Is it a specific phone or all phones will appear [Must answer]: Some/All (for example: google phones, certain Android versions will appear) + +* Does the latest version of the framework have this problem [Must answer]: Yes/No (if you are using an older version, it is recommended to upgrade to see if the problem still exists) + +* Have you consulted the framework documentation but have not resolved [Must answer]: Yes/No (the documentation will provide answers to the most common questions, and you can see if there is anything you want) + +* Has anyone ever asked a similar question [Must answer]: Yes/No (see if anyone has ever asked a similar question, and refer to how others have solved it first) + +* Whether the question can be reproduced through Demo [Must answer]: Yes/No (it is used to check whether it is caused by a problem with the writing of your own project code.) + +* Does this issue also occur using the system-provided toast API [Must answer]: Yes/No (it is used to check whether there is a problem with the code of the framework.) + +#### Other + +* Provide the error stack (if it is a crash, it needs to be provided, be careful not to take the obfuscated code stack) + +* Provide screenshots or videos (if available, it is recommended to fill in) + +* Provide solutions (if any, it is recommended to fill in) \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_en_template_question.md b/.github/ISSUE_TEMPLATE/issue_en_template_question.md new file mode 100644 index 0000000..2b8975e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_en_template_question.md @@ -0,0 +1,19 @@ +--- +name: Ask questions +about: Ask questions and I'll answer them +labels: question +assignees: getActivity + +--- + +## [Warning: Please be sure to fill in according to the issue template. Don't take any chances. Once you find that the issue is not filled in carefully according to the template, you will close it directly.] + +#### Description of the problem + +* Problem Description [Required]: Please describe your problem (Note: If it is determined to be a framework bug, please do not mention it here, otherwise it will not be accepted) + +#### Please respond + +* Have you consulted the framework documentation but have not resolved [Must answer]: Yes/No (the documentation will provide answers to the most common questions, and you can see if there is anything you want) + +* Has anyone ever asked a similar question [Must answer]: Yes/No (see if anyone has ever asked a similar question, and refer to how others have solved it first) diff --git a/.github/ISSUE_TEMPLATE/issue_en_template_suggest.md b/.github/ISSUE_TEMPLATE/issue_en_template_suggest.md new file mode 100644 index 0000000..1c61a84 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue_en_template_suggest.md @@ -0,0 +1,19 @@ +--- +name: Submit a suggestion +about: Please tell me the shortcomings of the framework, so that I can do better! +labels: help wanted +assignees: getActivity + +--- + +## [Warning: Please be sure to fill in according to the issue template. Don't take any chances. Once you find that the issue is not filled in carefully according to the template, you will close it directly.] + +#### Suggest collection + +* Issue Has anyone asked a similar question before? [Must answer]: (Once there are repeated questions, I will not answer them again) + +* Does the framework documentation mention this? [Must answer]: Yes/No (please read the documentation of the framework before making suggestions) + +* What do you think the framework is lacking in? [Must answer]: (You can describe what makes you dissatisfied with the framework) + +* What do you think would be better to improve? [Optional]: (You can provide your own ideas or practices for the author's reference) \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/issue_template_bug.md b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md similarity index 78% rename from .github/ISSUE_TEMPLATE/issue_template_bug.md rename to .github/ISSUE_TEMPLATE/issue_zh_template_bug.md index 3ee27d2..5a28ccc 100644 --- a/.github/ISSUE_TEMPLATE/issue_template_bug.md +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_bug.md @@ -10,13 +10,13 @@ assignees: getActivity #### 问题描述 -* 框架版本【必填】:XXX +* 框架版本【必填】:请输入你使用的框架版本 -* 问题描述【必填】:XXX +* 问题描述【必填】:请输入你对这个问题的描述 -* 复现步骤【必填】:XXX(注意:目前不受理没有复现步骤的 Bug 单) +* 复现步骤【必填】:请输入问题的复现步骤(注意:目前不受理没有复现步骤的 Bug 单) -* 是否必现【必填】:填是/否 +* 是否必现【必填】:是/否 * 出现问题的手机信息【必填】:请填写出现问题的品牌和机型 @@ -30,7 +30,7 @@ assignees: getActivity * 框架最新的版本是否存在这个问题【必答】:是/否(如果用的是旧版本的话,建议升级看问题是否还存在) -* 是否已经查阅框架文档还未能解决的【必答】:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的) +* 是否已经查阅框架文档但还未能解决的【必答】:是/否(文档会提供最常见的问题解答,可以看看是否有自己想要的) * issue 是否有人曾提过类似的问题【必答】:是/否(看看曾经有人提过类似的问题,先参考一下别人是怎么解决的) diff --git a/.github/ISSUE_TEMPLATE/issue_template_question.md b/.github/ISSUE_TEMPLATE/issue_zh_template_question.md similarity index 79% rename from .github/ISSUE_TEMPLATE/issue_template_question.md rename to .github/ISSUE_TEMPLATE/issue_zh_template_question.md index 1101285..e1b6212 100644 --- a/.github/ISSUE_TEMPLATE/issue_template_question.md +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_question.md @@ -10,7 +10,7 @@ assignees: getActivity #### 问题描述 -* 请描述一下你的疑惑【必填】:XXX(注意:如果是框架 bug 请不要在这里提,否则一概不受理) +* 问题描述【必填】:请描述一下你的问题(注意:如果确定是框架 bug 请不要在这里提,否则一概不受理) #### 请回答 diff --git a/.github/ISSUE_TEMPLATE/issue_template_suggest.md b/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.md similarity index 54% rename from .github/ISSUE_TEMPLATE/issue_template_suggest.md rename to .github/ISSUE_TEMPLATE/issue_zh_template_suggest.md index 20da57b..d789f4c 100644 --- a/.github/ISSUE_TEMPLATE/issue_template_suggest.md +++ b/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.md @@ -10,10 +10,10 @@ assignees: getActivity #### 建议收集 -* issue 是否有人曾提过类似的问题?【必答】(一旦出现重复提问我将不会再次解答) +* issue 是否有人曾提过类似的问题?【必答】:(一旦出现重复提问我将不会再次解答) * 框架文档是否有提及到此问题?【必答】:是/否(请先看完框架的文档后再来提建议) -* 你觉得框架有什么不足之处?【必答】(你可以描述框架有什么令你不满意的地方) +* 你觉得框架有什么不足之处?【必答】:(你可以描述框架有什么令你不满意的地方) -* 你觉得该怎么去完善会比较好?【非必答】(你可以提供一下自己的想法或者做法供作者参考) \ No newline at end of file +* 你觉得该怎么去完善会比较好?【非必答】:(你可以提供一下自己的想法或者做法供作者参考) \ No newline at end of file diff --git a/HelpDoc-en.md b/HelpDoc-en.md new file mode 100644 index 0000000..dd4611d --- /dev/null +++ b/HelpDoc-en.md @@ -0,0 +1,179 @@ +#### Catalog + +* [How to customize toast display animation](#how-to-customize-toast-display-animation) + +* [How to customize toast display duration](#how-to-customize-toast-display-duration) + +* [How to customize toast layout style](#how-to-customize-toast-layout-style) + +* [How to switch to toast queue display strategy](#how-to-switch-to-toast-queue-display-strategy) + +* [What should I do if the framework cannot meet the scene I am currently using](#what-should-i-do-if-the-framework-cannot-meet-the-scene-i-am-currently-using) + +* [Why the framework prefers to use window manager to implement toast](#why-the-framework-prefers-to-use-window-manager-to-implement-toast) + +#### How to customize toast display animation + +* When toast is initialized, just modify the toast strategy + +```java +Toaster.init(this, new ToastStrategy() { + + @Override + public IToast createToast(IToastStyle style) { + if (toast instanceof CustomToast) { + CustomToast customToast = ((CustomToast) toast); + // Set the toast animation effect + customToast.setAnimationsId(R.anim.xxx); + } + return toast; + } +}); +``` + +* The disadvantage of this method is that it will only take effect when the application is in the foreground. This is because the toast in the foreground is implemented with a framework, which is essentially a WindowManager. The advantage is that it is very flexible and is not limited by the system toast mechanism. The disadvantage is that it cannot It is displayed in the background; while the toast in the background is implemented by the system, the advantage is that it can be displayed in the background, the disadvantage is that it is very limited and cannot be customized too deeply; and the framework uses two The advantages and disadvantages of the two methods are complementary. + +#### How to customize toast display duration + +* When toast is initialized, just modify the toast strategy + +```java +Toaster.init(this, new ToastStrategy() { + + @Override + public IToast createToast(IToastStyle style) { + IToast toast = super.createToast(style); + if (toast instanceof CustomToast) { + CustomToast customToast = ((CustomToast) toast); + // Set the display duration of the short toast (default is 2000 milliseconds) + customToast.setShortDuration(1000); + // Set the display duration of the long Toast (default is 3500 milliseconds) + customToast.setLongDuration(5000); + } + return toast; + } +}); +``` + +* The disadvantage of this method is that it will only take effect when the application is in the foreground. This is because the toast in the foreground is implemented with a framework, which is essentially a WindowManager. The advantage is that it is very flexible and is not limited by the system toast mechanism. The disadvantage is that it cannot It is displayed in the background; while the toast in the background is implemented by the system, the advantage is that it can be displayed in the background, the disadvantage is that it is very limited and cannot be customized too deeply; and the framework uses two The advantages and disadvantages of the two methods are complementary. + +#### How to customize toast layout style + +* If you want to set the global toast style, you can call it like this (choose any one) + +```java +// Modify toast layout +Toaster.setView(int id); +``` + +```java +// Modified toast layout, toast shows center of gravity, toast shows position offset +Toaster.setStyle(IToastStyle style); +``` + +* If you want to set a separate Toast display style for one occasion, you can do all of these (select either) + +```java +// Modify toast layout +ToastParams params = new ToastParams(); +params.text = "I am toast of custom layout (partial effect)"; +params.style = new CustomViewToastStyle(R.layout.toast_custom_view); +Toaster.show(params); +``` + +```java +// Modify the toast layout, toast display center of gravity, and toast display position offset +ToastParams params = new ToastParams(); +params.text = "I am toast of custom layout (partial effect)"; +params.style = new CustomViewToastStyle(R.layout.toast_custom_view, Gravity.CENTER, 10, 20); +Toaster.show(params); +``` + +* At this point, you may have a doubt, why setting a new toast style can only pass in the layout id instead of the View object? Because every time the framework displays toast, it will create a new toast object and View object. If the View object is passed in, it will not be able to create it every time it is displayed. As for why the framework does not reuse this View object, it is because if After reusing this View object, the following exceptions may be triggered: + +```text +java.lang.IllegalStateException: View android.widget.TextView{7ffea98 V.ED..... ......ID 0,0-396,153 #102000b android:id/message} +has already been added to the window manager. + at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:371) + at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131) + at android.widget.Toast$TN.handleShow(Toast.java:501) + at android.widget.Toast$TN$1.handleMessage(Toast.java:403) + at android.os.Handler.dispatchMessage(Handler.java:112) + at android.os.Looper.loop(Looper.java:216) + at android.app.ActivityThread.main(ActivityThread.java:7625) + at java.lang.reflect.Method.invoke(Native Method) + at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987) +``` + +* This is because WindowManager succeeded when addingView, but failed when removingView, which caused the View object of the previous toast to be unable to be reused when the next toast is displayed. Although this situation is relatively rare, there are still people who have reported this to me. Problem, in order to solve this problem, I decided not to reuse the View object. For specific adjustments to this piece, you can check the release record: [Toaster/releases/tag/9.0](https://github.com/getActivity/Toaster/releases/tag/9.0) + +#### How to switch to toast queue display strategy + +* You only need to modify the initialization method of the toast framework and manually pass in the toast strategy class. Here, you can use the ToastStrategy class that has been encapsulated by the framework. + +```java +// Initialize the toast framework +// Toaster.init(this); +Toaster.init(this, new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE)); +``` + +* Note that the constructor needs to pass in `ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE`. For an introduction to this field, see the code comments below + +```java +public class ToastStrategy { + + /** + * Instant display mode (default) + * + * In the case of multiple toast display requests, before displaying the next toast + * The previous toast will be canceled immediately to ensure that the currently displayed toast message is up to date + */ + public static final int SHOW_STRATEGY_TYPE_IMMEDIATELY = 0; + + /** + * No message loss mode + * + * In the case of multiple toast display requests, wait for the previous toast to be displayed for 1 second or 1.5 seconds + * Then display the next toast, not according to the display duration of the toast, because the waiting time will be very long + * This can not only ensure that the user can see every toast message, but also ensure that the user will not wait too long + */ + public static final int SHOW_STRATEGY_TYPE_QUEUE = 1; +} +``` + +#### What should I do if the framework cannot meet the scene I am currently using + +* The Toaster framework is intended to solve some toast requirements. If Toaster cannot meet your needs, you can consider using the [XToast](https://github.com/getActivity/XToast) floating window framework to achieve it. + +#### Why the framework prefers to use window manager to implement toast + +* There are too many pits in the system toast, the main problems are as follows: + + * System toast will cause some memory leaks + + * System toast cannot realize custom display animation and display duration control + + * Android 7.1 version will block the main thread and cause BadTokenException + + * Closing the permission of the notification bar below Android 10.0 will cause the problem that the system toast cannot be displayed + + * Android 11 and above, cannot customize the toast style (layout, position center of gravity, position offset) + +* Therefore, the framework prefers to use WindowManager instead of implementing toast display. The specific advantages and disadvantages are as follows: + + * advantage + + * There will be no memory leaks, and there will not be so many strange problems + + * High degree of customization, support custom animation and custom display duration + + * Break through Google's restrictions on toast in the new version of Android + + * shortcoming + + * WindowManager cannot pop up
in the background without floating window permission (frame solution: if it is displayed in the background, use the system's toast to display) + + * The WindowManager will be bound to the Activity and will disappear with the Activity being destroyed
(framework solution: the display is delayed by 200ms, thus waiting for the latest Activity to be created before calling the display, so WindowManager is bound to the latest Activity and does not have the problem of disappearing with the old Activity when it finishes) + +* Of course, it is not to say that using the system toast is not good. It must be good to use WindowManger. It depends on the specific usage scenario. I think the best way is: use WindowManager to display the application in the foreground, and use the system in the background. the best solution is to use WindowManager in the foreground state and system Toast in the background state. diff --git a/HelpDoc.md b/HelpDoc-zh.md similarity index 72% rename from HelpDoc.md rename to HelpDoc-zh.md index f9faa9f..3444c02 100644 --- a/HelpDoc.md +++ b/HelpDoc-zh.md @@ -1,5 +1,7 @@ #### 目录 +* [框架怎么改名了](#框架怎么改名了) + * [怎么自定义 Toast 显示动画](#怎么自定义-toast-显示动画) * [怎么自定义 Toast 显示时长](#怎么自定义-toast-显示时长) @@ -12,12 +14,26 @@ * [为什么框架优先使用 WindowManager 来实现 Toast](#为什么框架优先使用-windowManager-来实现-toast) +#### 框架怎么改名了 + +* 框架改名是一个重大的操作,我为什么选择在现在这个时候改,是基于以下思考: + + * 框架第一次提交是在 2018 年 9 月,不知不觉我已经维护了将近了 5 年的时间,我觉得 ToastUtils 这个名称已经配不上它的气质了,名字虽然好记,但是过于大众化,没有辨识度。 + + * 虽然名称叫 ToastUtils,但是经过无数次改造和重构后,它变得不像工具类了,比如它需要先调用 init 方法来初始化框架,才能使用 show 方法来显示 Toast,另外框架还对外提供了设置 Toast 策略类、Toast 拦截器、Toast 样式类,而这些方法不应该出现一个工具类中,工具类应该是只对外提供模板方法,而不应该把外部传入的对象作为静态持有着。 + + * 至于为什么选择在这个时候改名,这是框架基本稳定无 Bug 了,该解决的问题都已经解决完了,最近几个月已经没有人提 issue 了,相比前几年,一两个星期就能收到一个 issue 相比,框架已经非常稳定了,根据我以往的经验来讲,大家其实对框架的要求极其苛刻,如果这个框架在 Bugly 中有出现崩溃或者 ANR,哪怕是报一个用户一次异常,只要框架还在维护,就会有人找上门提 issue,而这次最近几个月没有人找上门,并不是用的人少了,更不是奇迹诞生了,大概率是调试阶段和线上阶段都没有找到框架的问题,框架的功能也能满足需求。 + +* 至于为什么改名叫 Toaster,很大一部分原因是大家的选择,我发起了一项投票,票数最多的就是这个名字,同时我也采纳了这一项,因为不仅仅是名字好记有辨识度,还具备了特殊的含义,我们都知道 Toast 中文翻译是面包的意思,而 Toaster 中文翻译是烤面包机的意思,吃 Toast 之前需要先用烤一下,口感会更加酥脆。 + +![](picture/zh/help_doc_rename_vote.jpg) + #### 怎么自定义 Toast 显示动画 * 在 Toast 初始化的时候,修改 Toast 策略即可 ```java -ToastUtils.init(this, new ToastStrategy() { +Toaster.init(this, new ToastStrategy() { @Override public IToast createToast(IToastStyle style) { @@ -38,7 +54,7 @@ ToastUtils.init(this, new ToastStrategy() { * 在 Toast 初始化的时候,修改 Toast 策略即可 ```java -ToastUtils.init(this, new ToastStrategy() { +Toaster.init(this, new ToastStrategy() { @Override public IToast createToast(IToastStyle style) { @@ -63,12 +79,12 @@ ToastUtils.init(this, new ToastStrategy() { ```java // 修改 Toast 布局 -ToastUtils.setView(int id); +Toaster.setView(int id); ``` ```java // 修改 Toast 布局,Toast 显示重心,Toast 显示位置偏移 -ToastUtils.setStyle(IToastStyle style); +Toaster.setStyle(IToastStyle style); ``` * 如果你想为某次 Toast 显示设置单独的样式,可以这样样用(选择任一一种即可) @@ -78,7 +94,7 @@ ToastUtils.setStyle(IToastStyle style); ToastParams params = new ToastParams(); params.text = "我是自定义布局的 Toast(局部生效)"; params.style = new CustomViewToastStyle(R.layout.toast_custom_view); -ToastUtils.show(params); +Toaster.show(params); ``` ```java @@ -86,7 +102,7 @@ ToastUtils.show(params); ToastParams params = new ToastParams(); params.text = "我是自定义布局的 Toast(局部生效)"; params.style = new CustomViewToastStyle(R.layout.toast_custom_view, Gravity.CENTER, 10, 20); -ToastUtils.show(params); +Toaster.show(params); ``` * 到此,大家可能有一个疑惑,为什么设置新的 Toast 样式只能传入布局 id 而不是 View 对象?因为框架每次显示 Toast 的时候,都会创建新的 Toast 对象和 View 对象,如果传入 View 对象将无法做到每次显示的时候都创建,至于框架为什么不复用这个 View 对象,这是因为如果复用了这个 View 对象,可能会触发以下异常: @@ -106,7 +122,7 @@ has already been added to the window manager. at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987) ``` -* 这是因为 WindowManager addView 的时候成功了,但是 removeView 的时候失败了,导致下一个 Toast 显示的时候,无法复用上一个 Toast 的 View 对象,虽然这种情况比较少见,但是仍然有人跟我反馈过这个问题,为了解决这一问题,所以决定不去复用 View 对象,具体对这块的调整可以查看发版记录:[ToastUtils/releases/tag/9.0](https://github.com/getActivity/ToastUtils/releases/tag/9.0) +* 这是因为 WindowManager addView 的时候成功了,但是 removeView 的时候失败了,导致下一个 Toast 显示的时候,无法复用上一个 Toast 的 View 对象,虽然这种情况比较少见,但是仍然有人跟我反馈过这个问题,为了解决这一问题,所以决定不去复用 View 对象,具体对这块的调整可以查看发版记录:[Toaster/releases/tag/9.0](https://github.com/getActivity/Toaster/releases/tag/9.0) #### 怎么切换成 Toast 排队显示的策略 @@ -114,8 +130,8 @@ has already been added to the window manager. ```java // 初始化 Toast 框架 -// ToastUtils.init(this); -ToastUtils.init(this, new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE)); +// Toaster.init(this); +Toaster.init(this, new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE)); ``` * 注意构造函数需要传入 `ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE`,关于这个字段的介绍可以看下面的代码注释 @@ -144,7 +160,7 @@ public class ToastStrategy { #### 框架无法满足我当前使用的场景怎么办 -* ToastUtils 框架意在解决一些的 Toast 需求,如果 ToastUtils 无法满足你的需求,你可以考虑使用 [XToast](https://github.com/getActivity/XToast) 悬浮窗框架来实现。 +* Toaster 框架意在解决一些的 Toast 需求,如果 Toaster 无法满足你的需求,你可以考虑使用 [XToast](https://github.com/getActivity/XToast) 悬浮窗框架来实现。 #### 为什么框架优先使用 WindowManager 来实现 Toast @@ -160,7 +176,7 @@ public class ToastStrategy { * Android 11 及以上版本,无法自定义 Toast 样式(布局、位置重心、位置偏移) -* 所以框架优先使用 WindowManager 来实现 Toast 显示,具体优缺点以下: +* 所以框架优先使用 WindowManager 来代替实现 Toast 显示,具体优缺点以下: * 优点 @@ -176,4 +192,4 @@ public class ToastStrategy { * WindowManager 会和 Activity 绑定,会随 Activity 销毁而消失
(框架的解决方案:延迟 200 毫秒显示,由此等待最新的 Activity 创建出来才调用显示,这样 WindowManager 就和最新 Activity 绑定在一起,就不会出现和旧 Activity finish 时一起消失的问题) -* 当然不是说用系统 Toast 就不好,用 WindowManger 一定就好,视具体的使用场景而定,我觉得最好的方式是:应用在前台的情况下使用 WindowManager 来显示,在后台的情况下使用系统 Toast 来显示,两者相结合,优势互补才是最佳方案。 +* 当然不是说用系统 Toast 就不好,用 WindowManger 一定就好,视具体的使用场景而定,我觉得最好的方式是:应用处于前台状态下使用 WindowManager 来显示,而处于后台状态下使用系统 Toast 来显示,两者相结合,优势互补才是最佳方案。 diff --git a/README-en.md b/README-en.md new file mode 100644 index 0000000..246d148 --- /dev/null +++ b/README-en.md @@ -0,0 +1,279 @@ +# [English Doc](README-en.md) + +# Toast Framework + +* Project address: [Github](https://github.com/getActivity/Toaster) + +* [Click here to download demo apk directly](https://github.com/getActivity/Toaster/releases/download/12.0/Toaster.apk) + +![](picture/en/demo_toast_activity.jpg) ![](picture/en/demo_toast_style_white.jpg) ![](picture/en/demo_toast_style_black.jpg) + +![](picture/en/demo_toast_layout_info.jpg) ![](picture/en/demo_toast_layout_warn.jpg) ![](picture/en/demo_toast_layout_success.jpg) + +![](picture/en/demo_toast_layout_error.jpg) ![](picture/en/demo_toast_layout_custom.jpg) + +#### Integration steps + +* If your project Gradle configuration is in `7.0` below, needs to be in `build.gradle` file added + +```groovy +allprojects { + repositories { + // JitPack remote repository:https://jitpack.io + maven { url 'https://jitpack.io' } + } +} +``` + +* If your Gradle configuration is `7.0` or above, needs to be in `settings.gradle` file added + +```groovy +dependencyResolutionManagement { + repositories { + // JitPack remote repository:https://jitpack.io + maven { url 'https://jitpack.io' } + } +} +``` + +* After configuring the remote warehouse, under the project app module `build.gradle` add remote dependencies to the file + +```groovy +android { + // Support JDK 1.8 + compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + // Toast framework:https://github.com/getActivity/Toaster + implementation 'com.github.getActivity:Toaster:12.0' +} +``` + +#### Initialize the framework + +```java +public class XxxApplication extends Application { + + @Override + public void onCreate() { + super.onCreate(); + + // Initialize the toast framework + Toaster.init(this); + } +} +``` + +#### Framework API introduction + +```java +// Show toast +Toaster.show(CharSequence text); +Toaster.show(int id); +Toaster.show(Object object); + +// Toast is displayed in debug mode +Toaster.debugShow(CharSequence text); +Toaster.debugShow(int id); +Toaster.debugShow(Object object); + +// Delayed display of toast +Toaster.delayedShow(CharSequence text, long delayMillis); +Toaster.delayedShow(int id, long delayMillis); +Toaster.delayedShow(Object object, long delayMillis); + +// Show short toast +Toaster.showShort(CharSequence text); +Toaster.showShort(int id); +Toaster.showShort(Object object); + +// Show long toast +Toaster.showLong(CharSequence text); +Toaster.showLong(int id); +Toaster.showLong(Object object); + +// Custom display toast +Toaster.show(ToastParams params); + +// Cancel toast +Toaster.cancel(); + +// Set toast layout (global effect) +Toaster.setView(int id); + +// Set toast style (global effect) +Toaster.setStyle(IToastStyle style); +// Get toast style +Toaster.getStyle() + +// Determine whether the current framework has been initialized +Toaster.isInit(); + +// Set toast strategy (global effect) +Toaster.setStrategy(IToastStrategy strategy); +// Get toast strategy +Toaster.getStrategy(); + +// Set toast center of gravity and offset +Toaster.setGravity(int gravity); +Toaster.setGravity(int gravity, int xOffset, int yOffset); + +// Set Toast interceptor (global effect) +Toaster.setInterceptor(IToastInterceptor interceptor); +// Get Toast interceptor +Toaster.getInterceptor(); +``` + +## [Please click here to view frequently asked questions](HelpDoc-en.md) + +#### Comparison between different Toast frameworks + +| Function or detail | [Toaster](https://github.com/getActivity/Toaster) |[ AndroidUtilCode-ToastUtils ](https://github.com/Blankj/AndroidUtilCode)| [Toasty](https://github.com/GrenderG/Toasty) | +| :----: | :------: | :-----: | :-----: | +| Corresponding version | 12.0 | 1.30.6 | 1.5.0 | +| Number of issues | [![](https://img.shields.io/github/issues/getActivity/Toaster.svg)](https://github.com/getActivity/Toaster/issues) |[![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues)| [![](https://img.shields.io/github/issues/GrenderG/Toasty.svg)](https://github.com/GrenderG/Toasty/issues) | +| Framework pack size | 31 KB | 500 KB | 50 KB | +| Framework maintenance status| 维护中 | 停止维护 | 停止维护 | +| Call code trace | ✅ | ❌ | ❌ | +| Support calling display in sub-threads | ✅ | ✅ | ❌ | +| Support setting partial Toast style | ✅ | ❌ | ❌ | +| Support setting global Toast style | ✅ | ❌ | ❌ | +| Support Toast Instant display | ✅ | ✅ | ❌ | +| Support Toast queue display | ✅ | ❌ | ✅ | +| Support Toast delayed display | ✅ | ❌ | ❌ | +| Solve the problem that Toast crashes on Android 7.1 | ✅ | ✅ | ❌ | +| Compatible with the problem that the Toast cannot be displayed after the permission of the notification bar is turned off | ✅ | ✅ | ❌ | +| Adapted to Android 11, the problem that Toast cannot be displayed in the background | ✅ | ❌ | ❌ | + +#### Introduction to calling code positioning function + +* The framework will output the location of the code called by Toast in the log printing, so that developers can directly click Log to locate which class and line of code is called, which can greatly improve the efficiency of our troubleshooting, especially if the Toast content is returned by the server, I believe that no one will reject such a function. + +![](picture/en/demo_logcat_code.jpg) + +#### Introduction to the problem of Toast crashing on Android 7.1 + +* This problem is caused by the addition of the WindowToken verification mechanism in Android 7.1, and this WindowToken is generated by NotificationManagerService. This WindowToken has a certain timeliness, and when the main thread of the application is blocked, WindowManager will calibrate the WindowToken when addingView However, the WindowToken has expired, and addView will throw an exception at this time. + +* Google fixed this problem in Android 8.0. The repair method is very simple and rude, which is to directly capture this exception. The repair idea of ​​the framework is similar to that of Google, but the repair method is different, because the framework cannot directly modify the system source code, so it is directly Exceptions are caught by means of Hook. + +#### Introduction to the problem that Toast cannot be displayed after the notification bar permission is turned off + +* This problem occurs because the display of the native Toast needs to pass through NMS (NotificationManagerService) to addView to the Window, and there is a `static final boolean ENABLE_BLOCKED_TOASTS = true` field in NMS. When the constant value is true, it will Trigger NMS to check the application notification bar permission. If there is no notification bar permission, then this Toast will be intercepted by NMS and output `Suppressing toast from package` log information. Xiaomi phones do not have this problem because they are Change the value of the `ENABLE_BLOCKED_TOASTS` field to `false`, so the check on the permission of the notification bar will not be triggered, and why do I know this? Because I once confirmed this with a MIUI engineer. + +* There are two ways for the framework to handle this problem. First, determine whether the current application is in the foreground state. If so, use a custom WindowManager instead of Toast to display it. If the current application is in the background state, it will use the INotificationManager interface in Hook Toast. The package name parameter passed by the enqueueToast method is changed to `android` to deceive NotificationManagerService, because NotificationManagerService has whitelisted the application with `android` package name, the system automatically permits. one thing to note is that, this method has expired on Android 10 and has been included in the reflection blacklist by the system, but the good news is that after checking and comparing the source code of NotificationManagerService, this problem (the problem of not being able to play Toast in the foreground after closing the notification bar permission) It has been fixed on Android 10.0, so the framework only goes to Hook INotificationManager when Android 9.0 and below and the notification bar permission is turned off. In addition, I also found the official code submission record about this piece:[ Always allow toasts from foreground apps ](https://cs.android.com/android/_/android/platform/frameworks/base/+/58b2453ed69197d765c7254241d9966ee49a3efb), you can take a look if you are interested, there is another question, if you want to still display Toast in the background in Android 10 and later versions, please ensure the notification bar permission or floating window permission of the application It is turned on. If you must require 100% display of Toast in the background state, please ensure that the application has the floating window permission, because on some mobile phones, even if there is a notification bar permission, it cannot display Toast in the background. For example, I use The HarmonyOS 2.0 test will not work, so it depends on how the product is considered. + +#### Android 11 cannot display Toast in the background + +* When we change the targetSdkVersion to 30 and above, we will find a problem. If the application is in the background process, and the Toast style of our application happens to be customized, then calling the show method of Toast in these cases will Surprisingly, Toast is not displayed. Please note that this problem is not a bug, but Android 11 prohibits this behavior. It is also noted in [Toast | Android Developers](https://developer.android.com/reference/android/widget/Toast#setView(android.view.View)), and it is not recommended to customize the style of Toast, and also tagged the `Toast.setView` method as `deprecated api`. + +* So how do we solve this problem? Is it really impossible to use custom style Toast? My answer is: Google only said that it cannot display custom Toast in the background, but it does not mean that it cannot be done in the foreground. The idea of ​​adapting the framework is that in the case of Android 11, it will first judge the current Toast Whether the application is in the foreground or the background, if it is in the foreground, it will display a custom-style Toast, if it is in the background, it will display a system-style Toast (by discarding the custom style to ensure that the Toast can be displayed normally), This can not only meet the requirements of Android 11, but also maximize the benefits of customized Toast. + +* It is worth noting that Toaster is currently the first and only framework of its kind to adapt to this feature of Android 11. + +#### Framework highlights + +* Take the lead: the first toast framework adapted to Android 11, developer do not need to care about the adaptation process + +* No permissions required: Regardless of whether the notification bar permission is granted or not, it does not affect the pop-up of the toast + +* Strong compatibility: Deal with the historical legacy of native Toast crashes in Android 7.1 + +* Powerful functions: Toast can be popped up regardless of primary and secondary threads, and resource id and int type can be automatically identified + +* Easy to use: just pass in the text, and the duration of the toast display will be automatically determined according to the length of the text + +* Best performance: use lazy loading mode, only create Toast when displaying, do not take up Application startup time + +* Best experience: Displaying the next Toast will cancel the display of the previous Toast, so that it can be displayed immediately + +* Global unity: You can initialize the Toast style in the Application to achieve a once-and-for-all effect + +#### How to replace the existing native Toast in the project + +* Right-click the pop-up menu in the project, Replace in path, check the Regex option, and click Replace + +```text +Toast\.makeText\([^,]+,\s*(.+),\s*[^,]+\)\.show\(\) +``` + +```text +Toaster.show($1) +``` + +* Replace the package name + +```text +import android.widget.Toast +``` + +```text +import com.hjq.toast.Toaster +``` + +* Then search globally and manually replace some that have not been replaced successfully + +```text +Toast.makeText +new Toast +``` + +#### Author's other open source projects + +* Android middle office: [AndroidProject](https://github.com/getActivity/AndroidProject)![](https://img.shields.io/github/stars/getActivity/AndroidProject.svg)![](https://img.shields.io/github/forks/getActivity/AndroidProject.svg) + +* Android middle office kt version: [AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)![](https://img.shields.io/github/stars/getActivity/AndroidProject-Kotlin.svg)![](https://img.shields.io/github/forks/getActivity/AndroidProject-Kotlin.svg) + +* Permissions framework: [XXPermissions](https://github.com/getActivity/XXPermissions) ![](https://img.shields.io/github/stars/getActivity/XXPermissions.svg) ![](https://img.shields.io/github/forks/getActivity/XXPermissions.svg) + +* Network framework: [EasyHttp](https://github.com/getActivity/EasyHttp)![](https://img.shields.io/github/stars/getActivity/EasyHttp.svg)![](https://img.shields.io/github/forks/getActivity/EasyHttp.svg) + +* Title bar framework: [TitleBar](https://github.com/getActivity/TitleBar)![](https://img.shields.io/github/stars/getActivity/TitleBar.svg)![](https://img.shields.io/github/forks/getActivity/TitleBar.svg) + +* Floating window framework: [XToast](https://github.com/getActivity/XToast)![](https://img.shields.io/github/stars/getActivity/XToast.svg)![](https://img.shields.io/github/forks/getActivity/XToast.svg) + +* Shape view framework: [ShapeView](https://github.com/getActivity/ShapeView)![](https://img.shields.io/github/stars/getActivity/ShapeView.svg)![](https://img.shields.io/github/forks/getActivity/ShapeView.svg) + +* Language switching framework: [Multi Languages](https://github.com/getActivity/MultiLanguages)![](https://img.shields.io/github/stars/getActivity/MultiLanguages.svg)![](https://img.shields.io/github/forks/getActivity/MultiLanguages.svg) + +* Gson parsing fault tolerance: [GsonFactory](https://github.com/getActivity/GsonFactory)![](https://img.shields.io/github/stars/getActivity/GsonFactory.svg)![](https://img.shields.io/github/forks/getActivity/GsonFactory.svg) + +* Logcat viewing framework: [Logcat](https://github.com/getActivity/Logcat)![](https://img.shields.io/github/stars/getActivity/Logcat.svg)![](https://img.shields.io/github/forks/getActivity/Logcat.svg) + +* Android version guide: [AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)![](https://img.shields.io/github/stars/getActivity/AndroidVersionAdapter.svg)![](https://img.shields.io/github/forks/getActivity/AndroidVersionAdapter.svg) + +* Android code standard: [AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)![](https://img.shields.io/github/stars/getActivity/AndroidCodeStandard.svg)![](https://img.shields.io/github/forks/getActivity/AndroidCodeStandard.svg) + +* Android resource summary:[AndroidIndex](https://github.com/getActivity/AndroidIndex) ![](https://img.shields.io/github/stars/getActivity/AndroidIndex.svg) ![](https://img.shields.io/github/forks/getActivity/AndroidIndex.svg) + +* Android open source leaderboard: [AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)![](https://img.shields.io/github/stars/getActivity/AndroidGithubBoss.svg)![](https://img.shields.io/github/forks/getActivity/AndroidGithubBoss.svg) + +* Studio boutique plugins: [StudioPlugins](https://github.com/getActivity/StudioPlugins)![](https://img.shields.io/github/stars/getActivity/StudioPlugins.svg)![](https://img.shields.io/github/forks/getActivity/StudioPlugins.svg) + +* Emoji collection: [emoji pa c shadow](https://github.com/getActivity/EmojiPackage)![](https://img.shields.io/github/stars/getActivity/EmojiPackage.svg)![](https://img.shields.io/github/forks/getActivity/EmojiPackage.svg) + +* China provinces json: [ProvinceJson](https://github.com/getActivity/ProvinceJson)![](https://img.shields.io/github/stars/getActivity/ProvinceJson.svg)![](https://img.shields.io/github/forks/getActivity/ProvinceJson.svg) + +* Markdown documentation:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc) ![](https://img.shields.io/github/stars/getActivity/MarkdownDoc.svg) ![](https://img.shields.io/github/forks/getActivity/MarkdownDoc.svg) + +## License + +```text +Copyright 2018 Huang JinQun + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` \ No newline at end of file diff --git a/README.md b/README.md index b27bde6..72271e8 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ +# [English Doc](README-en.md) + # 吐司框架 -* 项目地址:[Github](https://github.com/getActivity/ToastUtils) +* 项目地址:[Github](https://github.com/getActivity/Toaster) * 博客地址:[只需体验三分钟,你就会跟我一样,爱上这款 Toast](https://www.jianshu.com/p/9b174ee2c571) -* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/ToastUtils/releases/download/11.2/ToastUtils.apk) +* 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/Toaster/releases/download/12.0/Toaster.apk) + +![](picture/zh/download_demo_apk_qr_code.png) -![](picture/demo_code.png) +![](picture/zh/demo_toast_activity.jpg) ![](picture/zh/demo_toast_style_white.jpg) ![](picture/zh/demo_toast_style_black.jpg) -![](picture/demo_page.jpg) +![](picture/zh/demo_toast_layout_info.jpg) ![](picture/zh/demo_toast_layout_warn.jpg) ![](picture/zh/demo_toast_layout_success.jpg) + +![](picture/zh/demo_toast_layout_error.jpg) ![](picture/zh/demo_toast_layout_custom.jpg) #### 集成步骤 @@ -46,8 +52,8 @@ android { } dependencies { - // 吐司框架:https://github.com/getActivity/ToastUtils - implementation 'com.github.getActivity:ToastUtils:11.2' + // 吐司框架:https://github.com/getActivity/Toaster + implementation 'com.github.getActivity:Toaster:12.0' } ``` @@ -61,7 +67,7 @@ public class XxxApplication extends Application { super.onCreate(); // 初始化 Toast 框架 - ToastUtils.init(this); + Toaster.init(this); } } ``` @@ -70,88 +76,88 @@ public class XxxApplication extends Application { ```java // 显示 Toast -ToastUtils.show(CharSequence text); -ToastUtils.show(int id); -ToastUtils.show(Object object); +Toaster.show(CharSequence text); +Toaster.show(int id); +Toaster.show(Object object); // debug 模式下显示 Toast -ToastUtils.debugShow(CharSequence text); -ToastUtils.debugShow(int id); -ToastUtils.debugShow(Object object); +Toaster.debugShow(CharSequence text); +Toaster.debugShow(int id); +Toaster.debugShow(Object object); // 延迟显示 Toast -ToastUtils.delayedShow(CharSequence text, long delayMillis); -ToastUtils.delayedShow(int id, long delayMillis); -ToastUtils.delayedShow(Object object, long delayMillis); +Toaster.delayedShow(CharSequence text, long delayMillis); +Toaster.delayedShow(int id, long delayMillis); +Toaster.delayedShow(Object object, long delayMillis); // 显示短 Toast -ToastUtils.showShort(CharSequence text); -ToastUtils.showShort(int id); -ToastUtils.showShort(Object object); +Toaster.showShort(CharSequence text); +Toaster.showShort(int id); +Toaster.showShort(Object object); // 显示长 Toast -ToastUtils.showLong(CharSequence text); -ToastUtils.showLong(int id); -ToastUtils.showLong(Object object); +Toaster.showLong(CharSequence text); +Toaster.showLong(int id); +Toaster.showLong(Object object); // 自定义显示 Toast -ToastUtils.show(ToastParams params); +Toaster.show(ToastParams params); // 取消 Toast -ToastUtils.cancel(); +Toaster.cancel(); // 设置 Toast 布局(全局生效) -ToastUtils.setView(int id); +Toaster.setView(int id); -// 设置 Toast 布局样式(全局生效) -ToastUtils.setStyle(IToastStyle style); -// 获取 Toast 布局样式 -ToastUtils.getStyle() +// 设置 Toast 样式(全局生效) +Toaster.setStyle(IToastStyle style); +// 获取 Toast 样式 +Toaster.getStyle() // 判断当前框架是否已经初始化 -ToastUtils.isInit(); +Toaster.isInit(); // 设置 Toast 策略(全局生效) -ToastUtils.setStrategy(IToastStrategy strategy); +Toaster.setStrategy(IToastStrategy strategy); // 获取 Toast 策略 -ToastUtils.getStrategy(); +Toaster.getStrategy(); // 设置 Toast 重心和偏移 -ToastUtils.setGravity(int gravity); -ToastUtils.setGravity(int gravity, int xOffset, int yOffset); +Toaster.setGravity(int gravity); +Toaster.setGravity(int gravity, int xOffset, int yOffset); // 设置 Toast 拦截器(全局生效) -ToastUtils.setInterceptor(IToastInterceptor interceptor); +Toaster.setInterceptor(IToastInterceptor interceptor); // 获取 Toast 拦截器 -ToastUtils.getInterceptor(); +Toaster.getInterceptor(); ``` -## [常见疑问请点击此处查看](HelpDoc.md) +## [常见疑问请点击此处查看](HelpDoc-zh.md) #### 不同 Toast 框架之间的对比 -| 功能或细节 | [ToastUtils](https://github.com/getActivity/ToastUtils) | [AndroidUtilCode-ToastUtils](https://github.com/Blankj/AndroidUtilCode) | [Toasty](https://github.com/GrenderG/Toasty) | +| 功能或细节 | [Toaster](https://github.com/getActivity/Toaster) | [AndroidUtilCode-ToastUtils](https://github.com/Blankj/AndroidUtilCode) | [Toasty](https://github.com/GrenderG/Toasty) | | :----: | :------: | :-----: | :-----: | -| 对应版本 | 11.2 | 1.30.6 | 1.5.0 | -| issues 数 | [![](https://img.shields.io/github/issues/getActivity/ToastUtils.svg)](https://github.com/getActivity/ToastUtils/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/GrenderG/Toasty.svg)](https://github.com/GrenderG/Toasty/issues) | -| **aar 包大小** | 31 KB | 500 KB | 50 KB | -| 框架维护状态 | **维护中** | 停止维护 | 停止维护 | -| **调用代码定位** | ✅ | ❌ | ❌ | -| **支持在子线程中调用显示** | ✅ | ✅ | ❌ | -| 支持设置**局部** Toast 样式 | ✅ | ❌ | ❌ | -| 支持设置**全局** Toast 样式 | ✅ | ❌ | ❌ | -| 支持 Toast **即显即示** | ✅ | ✅ | ❌ | -| 支持 Toast **排队显示** | ✅ | ❌ | ✅ | -| 支持 Toast **延迟显示** | ✅ | ❌ | ❌ | -| **处理 Toast 在 Android 7.1 崩溃的问题** | ✅ | ✅ | ❌ | -| **兼容通知栏权限关闭后 Toast 显示不出来的问题** | ✅ | ✅ | ❌ | -| **适配 Android 11 不能在后台显示 Toast 的问题** | ✅ | ❌ | ❌ | +| 对应版本 | 12.0 | 1.30.6 | 1.5.0 | +| issues 数 | [![](https://img.shields.io/github/issues/getActivity/Toaster.svg)](https://github.com/getActivity/Toaster/issues) | [![](https://img.shields.io/github/issues/Blankj/AndroidUtilCode.svg)](https://github.com/Blankj/AndroidUtilCode/issues) | [![](https://img.shields.io/github/issues/GrenderG/Toasty.svg)](https://github.com/GrenderG/Toasty/issues) | +| 框架体积 | 31 KB | 500 KB | 50 KB | +| 框架维护状态 | 维护中 | 停止维护 | 停止维护 | +| 调用代码定位 | ✅ | ❌ | ❌ | +| 支持在子线程中调用显示 | ✅ | ✅ | ❌ | +| 支持设置局部 Toast 样式 | ✅ | ❌ | ❌ | +| 支持设置全局 Toast 样式 | ✅ | ❌ | ❌ | +| 支持 Toast 即显即示 | ✅ | ✅ | ❌ | +| 支持 Toast 排队显示 | ✅ | ❌ | ✅ | +| 支持 Toast 延迟显示 | ✅ | ❌ | ❌ | +| 处理 Toast 在 Android 7.1 崩溃的问题 | ✅ | ✅ | ❌ | +| 兼容通知栏权限关闭后 Toast 显示不出来的问题 | ✅ | ✅ | ❌ | +| 适配 Android 11 不能在后台显示 Toast 的问题 | ✅ | ❌ | ❌ | #### 调用代码定位功能介绍 -* 框架会在日志打印中输出在 Toast 调用的代码位置,这样开发者可以直接通过点击 Log 来定位是在哪个类哪行代码调用的,这样可以极大提升我们排查问题的效率,特别是 Toast 的内容是由后台返回的情况下,我相信没有任何一个人会拒绝这样的功能。 +* 框架会在日志打印中输出在 Toast 调用的代码位置,这样开发者可以直接通过点击 Log 来定位是在哪个类哪行代码调用的,这样可以极大提升我们排查问题的效率,特别是 Toast 的内容是由服务器返回的情况下,我相信没有任何一个人会拒绝这样的功能。 -![](picture/logcat_code.jpg) +![](picture/zh/demo_logcat_code.jpg) #### Toast 在 Android 7.1 崩溃的问题介绍 @@ -167,7 +173,7 @@ ToastUtils.getInterceptor(); * 这个问题的出现是因为原生 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,请保证应用的通知栏权限或者悬浮窗权限处于开启的状态,如果你一定要求在后台要 100% 能显示 Toast,请保证应用有悬浮窗权限,因为在某些厂商的手机上,就算有通知栏权限也是无法在后台显示 Toast,例如我用 HarmonyOS 2.0 测试就不行,所以具体要看产品怎么斟酌。 +* 框架处理这个问题的方式有两种,先判断当前应用是否处于前台状态,如果是则使用自定义的 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 的问题介绍 @@ -175,11 +181,11 @@ ToastUtils.getInterceptor(); * 那么我们如何解决这一问题呢?难道真的不能用自定义样式的 Toast 了?我的答案是:不,凡事不能一刀切,谷歌只说不能在后台显示自定义的 Toast,并不能代表不能在前台那么做,框架的适配思路是,在 Android 11 的情况下,会先判断当前应用是处于前台还是后台,如果是在前台的情况下就显示自定义样式的 Toast,如果是在后台的情况下就显示系统样式的 Toast(通过舍弃自定义样式来保证 Toast 能够正常显示出来),这样既能符合 Android 11 要求,同时又能将定制化 Toast 的权益最大化。 -* 值得注意的是:ToastUtils 是目前同类框架第一款也是唯一一款适配 Android 11 这一特性的框架。 +* 值得注意的是:Toaster 是目前同类框架第一款也是唯一一款适配 Android 11 这一特性的框架。 #### 框架亮点 -* 一马当先:首款适配 Android 11 的吐司框架,使用者无需关心适配过程 +* 一马当先:首款适配 Android 11 的吐司框架,开发者无需关心适配过程 * 无需权限:[不管有没有授予通知栏权限都不影响吐司的弹出](https://www.jianshu.com/p/1d64a5ccbc7c) @@ -204,7 +210,7 @@ Toast\.makeText\([^,]+,\s*(.+),\s*[^,]+\)\.show\(\) ``` ```text -ToastUtils.show($1) +Toaster.show($1) ``` * 对导包进行替换 @@ -214,7 +220,7 @@ import android.widget.Toast ``` ```text -import com.hjq.toast.ToastUtils +import com.hjq.toast.Toaster ``` * 再全局搜索,手动更换一些没有替换成功的 @@ -260,6 +266,8 @@ new Toast * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson) ![](https://img.shields.io/github/stars/getActivity/ProvinceJson.svg) ![](https://img.shields.io/github/forks/getActivity/ProvinceJson.svg) +* Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc) ![](https://img.shields.io/github/stars/getActivity/MarkdownDoc.svg) ![](https://img.shields.io/github/forks/getActivity/MarkdownDoc.svg) + #### 微信公众号:Android轮子哥 ![](https://raw.githubusercontent.com/getActivity/Donate/master/picture/official_ccount.png) diff --git a/app/build.gradle b/app/build.gradle index ed689d1..23d2757 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.hjq.toast.demo" minSdkVersion 16 targetSdkVersion 31 - versionCode 1120 - versionName "11.2" + versionCode 1200 + versionName "12.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } @@ -62,14 +62,14 @@ dependencies { implementation 'com.google.android.material:material:1.4.0' // 标题栏框架:https://github.com/getActivity/TitleBar - implementation 'com.github.getActivity:TitleBar:9.6' + implementation 'com.github.getActivity:TitleBar:10.0' // 权限请求框架:https://github.com/getActivity/XXPermissions - implementation 'com.github.getActivity:XXPermissions:16.2' + implementation 'com.github.getActivity:XXPermissions:16.6' // 悬浮窗框架:https://github.com/getActivity/XToast - implementation 'com.github.getActivity:XToast:8.6' + implementation 'com.github.getActivity:XToast:8.9' // 内存泄漏捕捉:https://github.com/square/leakcanary - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08e2645..89dcb88 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ android:name=".ToastApplication" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/AppTheme"> = Build.VERSION_CODES.R) { if (XXPermissions.isGranted(MainActivity.this, Permission.SYSTEM_ALERT_WINDOW)) { - ToastUtils.show("我是在后台显示的 Toast(有悬浮窗权限真的可以为所欲为)"); + Toaster.show(R.string.demo_show_toast_in_background_state_result_1); } else { - ToastUtils.show("我是在后台显示的 Toast(安卓 11 及以上在后台显示只能使用系统样式)"); + Toaster.show(R.string.demo_show_toast_in_background_state_result_2); } } else { - ToastUtils.show("我是在后台显示的 Toast"); + Toaster.show(R.string.demo_show_toast_in_background_state_result_3); } } }, 5000); @@ -147,10 +172,10 @@ public void run() { public void combinationXToastShow(View v) { new XToast<>(this) .setDuration(1000) - // 将 ToastUtils 中的 View 转移给 XToast 来显示 - .setContentView(ToastUtils.getStyle().createView(getApplication())) + // 将 Toaster 中的 View 转移给 XToast 来显示 + .setContentView(Toaster.getStyle().createView(getApplication())) .setAnimStyle(android.R.style.Animation_Translucent) - .setText(android.R.id.message, "就问你溜不溜") + .setText(android.R.id.message, R.string.demo_combining_xtoast_use_result) .setGravity(Gravity.BOTTOM) .setYOffset((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics())) .show(); diff --git a/app/src/main/java/com/hjq/toast/demo/ToastApplication.java b/app/src/main/java/com/hjq/toast/demo/ToastApplication.java index 55dfd63..d8f1ecc 100644 --- a/app/src/main/java/com/hjq/toast/demo/ToastApplication.java +++ b/app/src/main/java/com/hjq/toast/demo/ToastApplication.java @@ -2,13 +2,13 @@ import android.app.Application; -import com.hjq.toast.ToastUtils; +import com.hjq.toast.Toaster; /** * author : Android 轮子哥 - * github : https://github.com/getActivity/ToastUtils + * github : https://github.com/getActivity/Toaster * time : 2018/09/01 - * desc : ToastUtils 初始化 + * desc : Toaster 初始化 */ public final class ToastApplication extends Application { @@ -17,6 +17,6 @@ public void onCreate() { super.onCreate(); // 初始化 Toast 框架 - ToastUtils.init(this); + Toaster.init(this); } } \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_error_bg.xml b/app/src/main/res/drawable/toast_error_bg.xml new file mode 100644 index 0000000..da7e431 --- /dev/null +++ b/app/src/main/res/drawable/toast_error_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_error_ic.xml b/app/src/main/res/drawable/toast_error_ic.xml new file mode 100644 index 0000000..4fd5942 --- /dev/null +++ b/app/src/main/res/drawable/toast_error_ic.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/drawable/toast_hint_bg.xml b/app/src/main/res/drawable/toast_hint_bg.xml new file mode 100644 index 0000000..9e176e5 --- /dev/null +++ b/app/src/main/res/drawable/toast_hint_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_info_ic.xml b/app/src/main/res/drawable/toast_info_ic.xml new file mode 100644 index 0000000..d8a77f2 --- /dev/null +++ b/app/src/main/res/drawable/toast_info_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/toast_success_bg.xml b/app/src/main/res/drawable/toast_success_bg.xml new file mode 100644 index 0000000..66603a3 --- /dev/null +++ b/app/src/main/res/drawable/toast_success_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_success_ic.xml b/app/src/main/res/drawable/toast_success_ic.xml new file mode 100644 index 0000000..e66525e --- /dev/null +++ b/app/src/main/res/drawable/toast_success_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/toast_warn_bg.xml b/app/src/main/res/drawable/toast_warn_bg.xml new file mode 100644 index 0000000..df7b049 --- /dev/null +++ b/app/src/main/res/drawable/toast_warn_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/toast_warn_ic.xml b/app/src/main/res/drawable/toast_warn_ic.xml new file mode 100644 index 0000000..bd5389c --- /dev/null +++ b/app/src/main/res/drawable/toast_warn_ic.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 998402e..3e13ead 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -13,7 +13,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:leftIcon="@null" - app:title="https://github.com/getActivity/ToastUtils" /> + app:title="https://github.com/getActivity/Toaster" /> + android:text="@string/demo_show_toast" />