├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── issue_en_template_bug.yml
│ ├── issue_en_template_question.yml
│ ├── issue_en_template_suggest.yml
│ ├── issue_zh_template_bug.yml
│ ├── issue_zh_template_question.yml
│ └── issue_zh_template_suggest.yml
└── workflows
│ └── android.yml
├── .gitignore
├── HelpDoc-en.md
├── HelpDoc-zh.md
├── LICENSE
├── README-en.md
├── README.md
├── app
├── AppSignature.jks
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── hjq
│ │ └── toast
│ │ └── demo
│ │ ├── MainActivity.java
│ │ └── ToastApplication.java
│ └── res
│ ├── drawable
│ ├── shape_gradient.xml
│ ├── toast_error_bg.xml
│ ├── toast_error_ic.xml
│ ├── toast_hint_bg.xml
│ ├── toast_info_ic.xml
│ ├── toast_success_bg.xml
│ ├── toast_success_ic.xml
│ ├── toast_warn_bg.xml
│ └── toast_warn_ic.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── toast_custom_view.xml
│ ├── toast_error.xml
│ ├── toast_info.xml
│ ├── toast_success.xml
│ └── toast_warn.xml
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-v23
│ └── styles.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── hjq
│ └── toast
│ ├── ActivityStack.java
│ ├── ActivityToast.java
│ ├── CustomToast.java
│ ├── GlobalToast.java
│ ├── NotificationServiceProxy.java
│ ├── NotificationToast.java
│ ├── SafeHandler.java
│ ├── SafeToast.java
│ ├── SystemToast.java
│ ├── ToastImpl.java
│ ├── ToastLogInterceptor.java
│ ├── ToastParams.java
│ ├── ToastStrategy.java
│ ├── Toaster.java
│ ├── WindowLifecycle.java
│ ├── config
│ ├── IToast.java
│ ├── IToastInterceptor.java
│ ├── IToastStrategy.java
│ └── IToastStyle.java
│ └── style
│ ├── BlackToastStyle.java
│ ├── CustomToastStyle.java
│ ├── LocationToastStyle.java
│ └── WhiteToastStyle.java
├── logo.png
├── picture
├── en
│ ├── demo_logcat_code.jpg
│ ├── demo_toast_activity.jpg
│ ├── demo_toast_layout_custom.jpg
│ ├── demo_toast_layout_error.jpg
│ ├── demo_toast_layout_info.jpg
│ ├── demo_toast_layout_success.jpg
│ ├── demo_toast_layout_warn.jpg
│ ├── demo_toast_style_black.jpg
│ └── demo_toast_style_white.jpg
└── zh
│ ├── demo_logcat_code.jpg
│ ├── demo_toast_activity.jpg
│ ├── demo_toast_layout_custom.jpg
│ ├── demo_toast_layout_error.jpg
│ ├── demo_toast_layout_info.jpg
│ ├── demo_toast_layout_success.jpg
│ ├── demo_toast_layout_warn.jpg
│ ├── demo_toast_style_black.jpg
│ ├── demo_toast_style_white.jpg
│ ├── download_demo_apk_qr_code.png
│ └── help_doc_rename_vote.jpg
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://raw.githubusercontent.com/getActivity/Donate/master/picture/pay_ali.png
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_bug.yml:
--------------------------------------------------------------------------------
1 | name: Submit Bug
2 | description: Please let me know the issues with the framework, and I will assist you in resolving them!
3 | title: "[Bug]:"
4 | labels: ["bug"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: input
12 | id: input_id_1
13 | attributes:
14 | label: Framework Version [Required]
15 | description: Please enter the version of the framework you are using.
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: input_id_2
20 | attributes:
21 | label: Issue Description [Required]
22 | description: Please describe the issue you are facing.
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: input_id_3
27 | attributes:
28 | label: Steps to Reproduce [Required]
29 | description: Please provide steps to reproduce the issue.
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: input_id_4
34 | attributes:
35 | label: Is the Issue Reproducible? [Required]
36 | multiple: false
37 | options:
38 | - "Not Selected"
39 | - "Yes"
40 | - "No"
41 | validations:
42 | required: true
43 | - type: input
44 | id: input_id_5
45 | attributes:
46 | label: Project targetSdkVersion [Required]
47 | validations:
48 | required: true
49 | - type: input
50 | id: input_id_6
51 | attributes:
52 | label: Device Information [Required]
53 | description: Please provide the brand and model of the device where the issue occurred.
54 | validations:
55 | required: true
56 | - type: input
57 | id: input_id_7
58 | attributes:
59 | label: Android Version [Required]
60 | description: Please provide the Android version where the issue occurred.
61 | validations:
62 | required: true
63 | - type: dropdown
64 | id: input_id_8
65 | attributes:
66 | label: Issue Source Channel [Required]
67 | multiple: true
68 | options:
69 | - Encountered by myself
70 | - Identified in Bugly
71 | - User feedback
72 | - Other channels
73 | - type: input
74 | id: input_id_9
75 | attributes:
76 | label: Is it specific to certain device models? [Required]
77 | description: Specify whether the issue is specific to certain devices (e.g., specific brand or Android version).
78 | validations:
79 | required: true
80 | - type: dropdown
81 | id: input_id_10
82 | attributes:
83 | label: Does the latest version of the framework have this issue? [Required]
84 | description: If you are using an older version, it is recommended to upgrade and check if the issue still persists.
85 | multiple: false
86 | options:
87 | - "Not Selected"
88 | - "Yes"
89 | - "No"
90 | validations:
91 | required: true
92 | - type: dropdown
93 | id: input_id_11
94 | attributes:
95 | label: Is the issue mentioned in the framework documentation? [Required]
96 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
97 | multiple: false
98 | options:
99 | - "Not Selected"
100 | - "Yes"
101 | - "No"
102 | validations:
103 | required: true
104 | - type: dropdown
105 | id: input_id_12
106 | attributes:
107 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
108 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
109 | multiple: false
110 | options:
111 | - "Not Selected"
112 | - "Yes"
113 | - "No"
114 | validations:
115 | required: true
116 | - type: dropdown
117 | id: input_id_13
118 | attributes:
119 | label: Has a similar issue been reported in the issue list? [Required]
120 | description: You can search the issue list for keywords related to your problem and refer to the solutions provided by others.
121 | multiple: false
122 | options:
123 | - "Not Selected"
124 | - "Yes"
125 | - "No"
126 | validations:
127 | required: true
128 | - type: dropdown
129 | id: input_id_14
130 | attributes:
131 | label: Have you searched the issue list but couldn't find a solution? [Required]
132 | description: If you have searched the issue list and couldn't find a solution, you can select "Yes."
133 | multiple: false
134 | options:
135 | - "Not Selected"
136 | - "Yes"
137 | - "No"
138 | validations:
139 | required: true
140 | - type: dropdown
141 | id: input_id_15
142 | attributes:
143 | label: Can the issue be reproduced with a demo project? [Required]
144 | description: Check if the issue can be reproduced in a minimal demo project to isolate potential issues in your own code.
145 | multiple: false
146 | options:
147 | - "Not Selected"
148 | - "Yes"
149 | - "No"
150 | validations:
151 | required: true
152 | - type: textarea
153 | id: input_id_16
154 | attributes:
155 | label: Provide Error Stack Trace
156 | description: If there is an error, please provide the stack trace. Note, Do not include obfuscated code in the stack trace.
157 | render: text
158 | validations:
159 | required: false
160 | - type: textarea
161 | id: input_id_17
162 | attributes:
163 | label: Provide Screenshots or Videos
164 | description: Provide screenshots or videos if necessary. This field is optional.
165 | validations:
166 | required: false
167 | - type: textarea
168 | id: input_id_18
169 | attributes:
170 | label: Provide a Solution
171 | description: If you have already found a solution, this field is optional.
172 | validations:
173 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_question.yml:
--------------------------------------------------------------------------------
1 | name: Ask a Question
2 | description: Ask your questions, and I will provide you with answers.
3 | title: "[Question]:"
4 | labels: ["question"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: Question Description [Required]
15 | description: Please describe your question (Note, If it is a framework bug, please do not raise it here, as it will not be accepted).
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: Is the issue mentioned in the framework documentation? [Required]
22 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
23 | multiple: false
24 | options:
25 | - "Not Selected"
26 | - "Yes"
27 | - "No"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
34 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
35 | multiple: false
36 | options:
37 | - "Not Selected"
38 | - "Yes"
39 | - "No"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: Has a similar issue been reported in the issue list? [Required]
46 | description: You can search the issue list for keywords related to your problem and refer to the solutions provided by others.
47 | multiple: false
48 | options:
49 | - "Not Selected"
50 | - "Yes"
51 | - "No"
52 | validations:
53 | required: true
54 | - type: dropdown
55 | id: input_id_5
56 | attributes:
57 | label: Have you searched the issue list but couldn't find a solution? [Required]
58 | description: If you have searched the issue list and couldn't find a solution, you can select "Yes."
59 | multiple: false
60 | options:
61 | - "Not Selected"
62 | - "Yes"
63 | - "No"
64 | validations:
65 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_en_template_suggest.yml:
--------------------------------------------------------------------------------
1 | name: Submit Suggestion
2 | description: Please let me know the shortcomings of the framework, so that I can improve it!
3 | title: "[Suggestion]:"
4 | labels: ["help wanted"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [Warning: Please make sure to fill in the issue template accurately. If an issue is found to be filled incorrectly, it will be closed without further notice.](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: What are the shortcomings you have noticed in the framework? [Required]
15 | description: You can describe any aspects of the framework that you are not satisfied with.
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: Has a similar suggestion been made in the issue list? [Required]
22 | description: If a similar suggestion has already been made, I will not address it again.
23 | multiple: false
24 | options:
25 | - "Not Selected"
26 | - "Yes"
27 | - "No"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: Is the suggestion mentioned in the framework documentation? [Required]
34 | description: The documentation provides answers to frequently asked questions. Please check if the information you are looking for is already provided.
35 | multiple: false
36 | options:
37 | - "Not Selected"
38 | - "Yes"
39 | - "No"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: Did you consult the framework documentation but couldn't find a solution? [Required]
46 | description: If you have consulted the documentation but still couldn't find a solution, you can select "Yes."
47 | multiple: false
48 | options:
49 | - "Not Selected"
50 | - "Yes"
51 | - "No"
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: input_id_5
56 | attributes:
57 | label: How do you suggest improving it? [Optional]
58 | description: You can provide your ideas or approaches for the author's reference.
59 | validations:
60 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_bug.yml:
--------------------------------------------------------------------------------
1 | name: 提交 Bug
2 | description: 请告诉我框架存在的问题,我会协助你解决此问题!
3 | title: "[Bug]:"
4 | labels: ["bug"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: input
12 | id: input_id_1
13 | attributes:
14 | label: 框架版本【必填】
15 | description: 请输入你使用的框架版本
16 | validations:
17 | required: true
18 | - type: textarea
19 | id: input_id_2
20 | attributes:
21 | label: 问题描述【必填】
22 | description: 请输入你对这个问题的描述
23 | validations:
24 | required: true
25 | - type: textarea
26 | id: input_id_3
27 | attributes:
28 | label: 复现步骤【必填】
29 | description: 请输入问题的复现步骤
30 | validations:
31 | required: true
32 | - type: dropdown
33 | id: input_id_4
34 | attributes:
35 | label: 是否必现【必填】
36 | multiple: false
37 | options:
38 | - "未选择"
39 | - "是"
40 | - "否"
41 | validations:
42 | required: true
43 | - type: input
44 | id: input_id_5
45 | attributes:
46 | label: 项目 targetSdkVersion【必填】
47 | validations:
48 | required: true
49 | - type: input
50 | id: input_id_6
51 | attributes:
52 | label: 出现问题的手机信息【必填】
53 | description: 请填写出现问题的品牌和机型
54 | validations:
55 | required: true
56 | - type: input
57 | id: input_id_7
58 | attributes:
59 | label: 出现问题的安卓版本【必填】
60 | description: 请填写出现问题的 Android 版本
61 | validations:
62 | required: true
63 | - type: dropdown
64 | id: input_id_8
65 | attributes:
66 | label: 问题信息的来源渠道【必填】
67 | multiple: true
68 | options:
69 | - 自己遇到的
70 | - Bugly 看到的
71 | - 用户反馈
72 | - 其他渠道
73 | - type: input
74 | id: input_id_9
75 | attributes:
76 | label: 是部分机型还是所有机型都会出现【必答】
77 | description: 部分/全部(例如:某为,某 Android 版本会出现)
78 | validations:
79 | required: true
80 | - type: dropdown
81 | id: input_id_10
82 | attributes:
83 | label: 框架最新的版本是否存在这个问题【必答】
84 | description: 如果用的是旧版本的话,建议升级看问题是否还存在
85 | multiple: false
86 | options:
87 | - "未选择"
88 | - "是"
89 | - "否"
90 | validations:
91 | required: true
92 | - type: dropdown
93 | id: input_id_11
94 | attributes:
95 | label: 框架文档是否提及了该问题【必答】
96 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
97 | multiple: false
98 | options:
99 | - "未选择"
100 | - "是"
101 | - "否"
102 | validations:
103 | required: true
104 | - type: dropdown
105 | id: input_id_12
106 | attributes:
107 | label: 是否已经查阅框架文档但还未能解决的【必答】
108 | description: 如果查阅了文档但还是没有解决的话,可以选择是
109 | multiple: false
110 | options:
111 | - "未选择"
112 | - "是"
113 | - "否"
114 | validations:
115 | required: true
116 | - type: dropdown
117 | id: input_id_13
118 | attributes:
119 | label: issue 列表中是否有人曾提过类似的问题【必答】
120 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
121 | multiple: false
122 | options:
123 | - "未选择"
124 | - "是"
125 | - "否"
126 | validations:
127 | required: true
128 | - type: dropdown
129 | id: input_id_14
130 | attributes:
131 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
132 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
133 | multiple: false
134 | options:
135 | - "未选择"
136 | - "是"
137 | - "否"
138 | validations:
139 | required: true
140 | - type: dropdown
141 | id: input_id_15
142 | attributes:
143 | label: 是否可以通过 Demo 来复现该问题【必答】
144 | description: 排查一下是不是自己的项目代码写得有问题导致的
145 | multiple: false
146 | options:
147 | - "未选择"
148 | - "是"
149 | - "否"
150 | validations:
151 | required: true
152 | - type: textarea
153 | id: input_id_16
154 | attributes:
155 | label: 提供报错堆栈
156 | description: 如果有报错的话必填,注意不要拿被混淆过的代码堆栈上来
157 | render: text
158 | validations:
159 | required: false
160 | - type: textarea
161 | id: input_id_17
162 | attributes:
163 | label: 提供截图或视频
164 | description: 根据需要提供,此项不强制
165 | validations:
166 | required: false
167 | - type: textarea
168 | id: input_id_18
169 | attributes:
170 | label: 提供解决方案
171 | description: 如果已经解决了的话,此项不强制
172 | validations:
173 | required: false
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_question.yml:
--------------------------------------------------------------------------------
1 | name: 提出疑问
2 | description: 提出你的困惑,我会给你解答
3 | title: "[疑惑]:"
4 | labels: ["question"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 问题描述【必填】
15 | description: 请描述一下你的问题(注意:如果确定是框架 bug 请不要在这里提,否则一概不受理)
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: 框架文档是否提及了该问题【必答】
22 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
23 | multiple: false
24 | options:
25 | - "未选择"
26 | - "是"
27 | - "否"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 是否已经查阅框架文档但还未能解决的【必答】
34 | description: 如果查阅了文档但还是没有解决的话,可以选择是
35 | multiple: false
36 | options:
37 | - "未选择"
38 | - "是"
39 | - "否"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: issue 列表中是否有人曾提过类似的问题【必答】
46 | description: 可以在 issue 列表在搜索问题关键字,参考一下别人的解决方案
47 | multiple: false
48 | options:
49 | - "未选择"
50 | - "是"
51 | - "否"
52 | validations:
53 | required: true
54 | - type: dropdown
55 | id: input_id_5
56 | attributes:
57 | label: 是否已经搜索过了 issue 列表但还未能解决的【必答】
58 | description: 如果搜索过了 issue 列表但是问题没有解决的话,可以选择是
59 | multiple: false
60 | options:
61 | - "未选择"
62 | - "是"
63 | - "否"
64 | validations:
65 | required: true
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue_zh_template_suggest.yml:
--------------------------------------------------------------------------------
1 | name: 提交建议
2 | description: 请告诉我框架的不足之处,让我做得更好!
3 | title: "[建议]:"
4 | labels: ["help wanted"]
5 |
6 | body:
7 | - type: markdown
8 | attributes:
9 | value: |
10 | ## [【警告:请务必按照 issue 模板填写,不要抱有侥幸心理,一旦发现 issue 没有按照模板认真填写,一律直接关闭】](https://github.com/getActivity/IssueTemplateGuide)
11 | - type: textarea
12 | id: input_id_1
13 | attributes:
14 | label: 你觉得框架有什么不足之处?【必答】
15 | description: 你可以描述框架有什么令你不满意的地方
16 | validations:
17 | required: true
18 | - type: dropdown
19 | id: input_id_2
20 | attributes:
21 | label: issue 是否有人曾提过类似的建议?【必答】
22 | description: 一旦出现重复提问我将不会再次解答
23 | multiple: false
24 | options:
25 | - "未选择"
26 | - "是"
27 | - "否"
28 | validations:
29 | required: true
30 | - type: dropdown
31 | id: input_id_3
32 | attributes:
33 | label: 框架文档是否提及了该问题【必答】
34 | description: 文档会提供最常见的问题解答,可以先看看是否有自己想要的
35 | multiple: false
36 | options:
37 | - "未选择"
38 | - "是"
39 | - "否"
40 | validations:
41 | required: true
42 | - type: dropdown
43 | id: input_id_4
44 | attributes:
45 | label: 是否已经查阅框架文档但还未能解决的【必答】
46 | description: 如果查阅了文档但还是没有解决的话,可以选择是
47 | multiple: false
48 | options:
49 | - "未选择"
50 | - "是"
51 | - "否"
52 | validations:
53 | required: true
54 | - type: textarea
55 | id: input_id_5
56 | attributes:
57 | label: 你觉得该怎么去完善会比较好?【非必答】
58 | description: 你可以提供一下自己的想法或者做法供作者参考
59 | validations:
60 | required: false
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: set up JDK 1.8
13 | uses: actions/setup-java@v1
14 | with:
15 | java-version: 1.8
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | .idea
3 | .cxx
4 | .externalNativeBuild
5 | build
6 | captures
7 |
8 | ._*
9 | *.iml
10 | .DS_Store
11 | local.properties
--------------------------------------------------------------------------------
/HelpDoc-en.md:
--------------------------------------------------------------------------------
1 | #### Catalog
2 |
3 | * [How to customize toast display animation](#how-to-customize-toast-display-animation)
4 |
5 | * [How to customize toast display duration](#how-to-customize-toast-display-duration)
6 |
7 | * [How to customize toast layout style](#how-to-customize-toast-layout-style)
8 |
9 | * [How to switch to toast queue display strategy](#how-to-switch-to-toast-queue-display-strategy)
10 |
11 | * [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)
12 |
13 | * [How to implement cross page display of toast in the framework](#how-to-implement-cross-page-display-of-toast-in-the-framework)
14 |
15 | * [Why the framework prefers to use window manager to implement toast](#why-the-framework-prefers-to-use-window-manager-to-implement-toast)
16 |
17 | #### How to customize toast display animation
18 |
19 | * When toast is initialized, just modify the toast strategy
20 |
21 | ```java
22 | Toaster.init(this, new ToastStrategy() {
23 |
24 | @Override
25 | public IToast createToast(IToastStyle> style) {
26 | IToast toast = super.createToast(style);
27 | if (toast instanceof CustomToast) {
28 | CustomToast customToast = ((CustomToast) toast);
29 | // Set the toast animation effect
30 | customToast.setAnimationsId(R.anim.xxx);
31 | }
32 | return toast;
33 | }
34 | });
35 | ```
36 |
37 | * 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.
38 |
39 | #### How to customize toast display duration
40 |
41 | * When toast is initialized, just modify the toast strategy
42 |
43 | ```java
44 | Toaster.init(this, new ToastStrategy() {
45 |
46 | @Override
47 | public IToast createToast(IToastStyle> style) {
48 | IToast toast = super.createToast(style);
49 | if (toast instanceof CustomToast) {
50 | CustomToast customToast = ((CustomToast) toast);
51 | // Set the display duration of the short toast (default is 2000 milliseconds)
52 | customToast.setShortDuration(1000);
53 | // Set the display duration of the long Toast (default is 3500 milliseconds)
54 | customToast.setLongDuration(5000);
55 | }
56 | return toast;
57 | }
58 | });
59 | ```
60 |
61 | * 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.
62 |
63 | #### How to customize toast layout style
64 |
65 | * If you want to set the global toast style, you can call it like this (choose any one)
66 |
67 | ```java
68 | // Modify toast layout
69 | Toaster.setView(int id);
70 | ```
71 |
72 | ```java
73 | // Modified toast layout, toast shows center of gravity, toast shows position offset
74 | Toaster.setStyle(IToastStyle> style);
75 | ```
76 |
77 | * If you want to set a separate Toast display style for one occasion, you can do all of these (select either)
78 |
79 | ```java
80 | // Modify toast layout
81 | ToastParams params = new ToastParams();
82 | params.text = "I am toast of custom layout (partial effect)";
83 | params.style = new CustomViewToastStyle(R.layout.toast_custom_view);
84 | Toaster.show(params);
85 | ```
86 |
87 | ```java
88 | // Modify the toast layout, toast display center of gravity, and toast display position offset
89 | ToastParams params = new ToastParams();
90 | params.text = "I am toast of custom layout (partial effect)";
91 | params.style = new CustomViewToastStyle(R.layout.toast_custom_view, Gravity.CENTER, 10, 20);
92 | Toaster.show(params);
93 | ```
94 |
95 | * 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:
96 |
97 | ```text
98 | java.lang.IllegalStateException: View android.widget.TextView{7ffea98 V.ED..... ......ID 0,0-396,153 #102000b android:id/message}
99 | has already been added to the window manager.
100 | at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:371)
101 | at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131)
102 | at android.widget.Toast$TN.handleShow(Toast.java:501)
103 | at android.widget.Toast$TN$1.handleMessage(Toast.java:403)
104 | at android.os.Handler.dispatchMessage(Handler.java:112)
105 | at android.os.Looper.loop(Looper.java:216)
106 | at android.app.ActivityThread.main(ActivityThread.java:7625)
107 | at java.lang.reflect.Method.invoke(Native Method)
108 | at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
109 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
110 | ```
111 |
112 | * 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)
113 |
114 | #### How to switch to toast queue display strategy
115 |
116 | * 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.
117 |
118 | ```java
119 | // Initialize the toast framework
120 | // Toaster.init(this);
121 | Toaster.init(this, new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE));
122 | ```
123 |
124 | * Note that the constructor needs to pass in `ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE`. For an introduction to this field, see the code comments below
125 |
126 | ```java
127 | public class ToastStrategy {
128 |
129 | /**
130 | * Instant display mode (default)
131 | *
132 | * In the case of multiple toast display requests, before displaying the next toast
133 | * The previous toast will be canceled immediately to ensure that the currently displayed toast message is up to date
134 | */
135 | public static final int SHOW_STRATEGY_TYPE_IMMEDIATELY = 0;
136 |
137 | /**
138 | * No message loss mode
139 | *
140 | * In the case of multiple toast display requests, wait for the previous toast to be displayed for 1 second or 1.5 seconds
141 | * Then display the next toast, not according to the display duration of the toast, because the waiting time will be very long
142 | * This can not only ensure that the user can see every toast message, but also ensure that the user will not wait too long
143 | */
144 | public static final int SHOW_STRATEGY_TYPE_QUEUE = 1;
145 | }
146 | ```
147 |
148 | #### What should I do if the framework cannot meet the scene I am currently using
149 |
150 | * The Toaster framework is intended to solve some toast requirements. If Toaster cannot meet your needs, you can consider using the [EasyWindow](https://github.com/getActivity/EasyWindow) floating window framework to achieve it.
151 |
152 | #### How to implement cross page display of toast in the framework
153 |
154 | * By default, Toasts in the Toaster framework are only displayed on the current Activity. If an Activity switch occurs, the Toast becomes invisible as the current Activity is destroyed, making it impossible to display on the new Activity. The framework added support for this feature in version [12.5](https://github.com/getActivity/Toaster/releases/tag/12.5). The following is an example of how to use it:
155 |
156 | ```java
157 | ToastParams params = new ToastParams();
158 | params.text = "I am a Toast that can be displayed across pages";
159 | // Indicates that this Toast needs to be displayed across pages
160 | params.crossPageShow = true;
161 | Toaster.show(params);
162 |
163 | #### Why the framework prefers to use window manager to implement toast
164 |
165 | * There are too many pits in the system toast, the main problems are as follows:
166 |
167 | * System toast will cause some memory leaks
168 |
169 | * System toast cannot realize custom display animation and display duration control
170 |
171 | * Android 7.1 version will block the main thread and cause BadTokenException
172 |
173 | * Closing the permission of the notification bar below Android 10.0 will cause the problem that the system toast cannot be displayed
174 |
175 | * Android 11 and above, cannot customize the toast style (layout, position center of gravity, position offset)
176 |
177 | * Therefore, the framework prefers to use WindowManager instead of implementing toast display. The specific advantages and disadvantages are as follows:
178 |
179 | * advantage
180 |
181 | * There will be no memory leaks, and there will not be so many strange problems
182 |
183 | * High degree of customization, support custom animation and custom display duration
184 |
185 | * Break through Google's restrictions on toast in the new version of Android
186 |
187 | * shortcoming
188 |
189 | * 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)
190 |
191 | * 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)
192 |
193 | * 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.
194 |
--------------------------------------------------------------------------------
/HelpDoc-zh.md:
--------------------------------------------------------------------------------
1 | #### 目录
2 |
3 | * [框架怎么改名了](#框架怎么改名了)
4 |
5 | * [怎么自定义 Toast 显示动画](#怎么自定义-toast-显示动画)
6 |
7 | * [怎么自定义 Toast 显示时长](#怎么自定义-toast-显示时长)
8 |
9 | * [怎么自定义 Toast 布局样式](#怎么自定义-toast-布局样式)
10 |
11 | * [怎么切换成 Toast 排队显示的策略](#怎么切换成-toast-排队显示的策略)
12 |
13 | * [框架无法满足我当前使用的场景怎么办](#框架无法满足我当前使用的场景怎么办)
14 |
15 | * [框架中的 Toast 如何实现跨页面显示](#框架中的-toast-如何实现跨页面显示)
16 |
17 | * [为什么框架优先使用 WindowManager 来实现 Toast](#为什么框架优先使用-windowManager-来实现-toast)
18 |
19 | #### 框架怎么改名了
20 |
21 | * 框架改名是一个重大的操作,我为什么选择在现在这个时候改,是基于以下思考:
22 |
23 | * 框架第一次提交是在 2018 年 9 月,不知不觉我已经维护了将近了 5 年的时间,我觉得 ToastUtils 这个名称已经配不上它的气质了,名字虽然好记,但是过于大众化,没有辨识度。
24 |
25 | * 虽然名称叫 ToastUtils,但是经过无数次改造和重构后,它变得不像工具类了,比如它需要先调用 init 方法来初始化框架,才能使用 show 方法来显示 Toast,另外框架还对外提供了设置 Toast 策略类、Toast 拦截器、Toast 样式类,而这些方法不应该出现一个工具类中,工具类应该是只对外提供模板方法,而不应该把外部传入的对象作为静态持有着。
26 |
27 | * 至于为什么选择在这个时候改名,这是框架基本稳定无 Bug 了,该解决的问题都已经解决完了,最近几个月已经没有人提 issue 了,相比前几年,一两个星期就能收到一个 issue 相比,框架已经非常稳定了,根据我以往的经验来讲,大家其实对框架的要求极其苛刻,如果这个框架在 Bugly 中有出现崩溃或者 ANR,哪怕是报一个用户一次异常,只要框架还在维护,就会有人找上门提 issue,而这次最近几个月没有人找上门,并不是用的人少了,更不是奇迹诞生了,大概率是调试阶段和线上阶段都没有找到框架的问题,框架的功能也能满足需求。
28 |
29 | * 至于为什么改名叫 Toaster,很大一部分原因是大家的选择,我发起了一项投票,票数最多的就是这个名字,同时我也采纳了这一项,因为不仅仅是名字好记有辨识度,还具备了特殊的含义,我们都知道 Toast 中文翻译是面包的意思,而 Toaster 中文翻译是烤面包机的意思,吃 Toast 之前需要先用烤一下,口感会更加酥脆。
30 |
31 | 
32 |
33 | #### 怎么自定义 Toast 显示动画
34 |
35 | * 在 Toast 初始化的时候,修改 Toast 策略即可
36 |
37 | ```java
38 | Toaster.init(this, new ToastStrategy() {
39 |
40 | @Override
41 | public IToast createToast(IToastStyle> style) {
42 | IToast toast = super.createToast(style);
43 | if (toast instanceof CustomToast) {
44 | CustomToast customToast = ((CustomToast) toast);
45 | // 设置 Toast 动画效果
46 | customToast.setAnimationsId(R.anim.xxx);
47 | }
48 | return toast;
49 | }
50 | });
51 | ```
52 |
53 | * 这种方式的缺点是只有应用在前台的情况下才会生效,这是因为前台的 Toast 是用框架实现的,本质上是一个 WindowManager,优点是非常灵活,不受系统 Toast 机制限制,缺点是无法在后台的情况下显示;而后台的 Toast 是用系统来实现的,优点是能在后台的情况下显示,缺点是局限性非常大,无法做太深的定制化;而框架正是利用了两种方式的优缺点进行了互补。
54 |
55 | #### 怎么自定义 Toast 显示时长
56 |
57 | * 在 Toast 初始化的时候,修改 Toast 策略即可
58 |
59 | ```java
60 | Toaster.init(this, new ToastStrategy() {
61 |
62 | @Override
63 | public IToast createToast(IToastStyle> style) {
64 | IToast toast = super.createToast(style);
65 | if (toast instanceof CustomToast) {
66 | CustomToast customToast = ((CustomToast) toast);
67 | // 设置短 Toast 的显示时长(默认是 2000 毫秒)
68 | customToast.setShortDuration(1000);
69 | // 设置长 Toast 的显示时长(默认是 3500 毫秒)
70 | customToast.setLongDuration(5000);
71 | }
72 | return toast;
73 | }
74 | });
75 | ```
76 |
77 | * 这种方式的缺点是只有应用在前台的情况下才会生效,这是因为前台的 Toast 是用框架实现的,本质上是一个 WindowManager,优点是非常灵活,不受系统 Toast 机制限制,缺点是无法在后台的情况下显示;而后台的 Toast 是用系统来实现的,优点是能在后台的情况下显示,缺点是局限性非常大,无法做太深的定制化;而框架正是利用了两种方式的优缺点进行了互补。
78 |
79 | #### 怎么自定义 Toast 布局样式
80 |
81 | * 如果你想设置全局的 Toast 样式,可以这样调用(选择任一一种即可)
82 |
83 | ```java
84 | // 修改 Toast 布局
85 | Toaster.setView(int id);
86 | ```
87 |
88 | ```java
89 | // 修改 Toast 布局,Toast 显示重心,Toast 显示位置偏移
90 | Toaster.setStyle(IToastStyle> style);
91 | ```
92 |
93 | * 如果你想为某次 Toast 显示设置单独的样式,可以这样样用(选择任一一种即可)
94 |
95 | ```java
96 | // 修改 Toast 布局
97 | ToastParams params = new ToastParams();
98 | params.text = "我是自定义布局的 Toast(局部生效)";
99 | params.style = new CustomViewToastStyle(R.layout.toast_custom_view);
100 | Toaster.show(params);
101 | ```
102 |
103 | ```java
104 | // 修改 Toast 布局、Toast 显示重心、Toast 显示位置偏移
105 | ToastParams params = new ToastParams();
106 | params.text = "我是自定义布局的 Toast(局部生效)";
107 | params.style = new CustomViewToastStyle(R.layout.toast_custom_view, Gravity.CENTER, 10, 20);
108 | Toaster.show(params);
109 | ```
110 |
111 | * 到此,大家可能有一个疑惑,为什么设置新的 Toast 样式只能传入布局 id 而不是 View 对象?因为框架每次显示 Toast 的时候,都会创建新的 Toast 对象和 View 对象,如果传入 View 对象将无法做到每次显示的时候都创建,至于框架为什么不复用这个 View 对象,这是因为如果复用了这个 View 对象,可能会触发以下异常:
112 |
113 | ```text
114 | java.lang.IllegalStateException: View android.widget.TextView{7ffea98 V.ED..... ......ID 0,0-396,153 #102000b android:id/message}
115 | has already been added to the window manager.
116 | at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:371)
117 | at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131)
118 | at android.widget.Toast$TN.handleShow(Toast.java:501)
119 | at android.widget.Toast$TN$1.handleMessage(Toast.java:403)
120 | at android.os.Handler.dispatchMessage(Handler.java:112)
121 | at android.os.Looper.loop(Looper.java:216)
122 | at android.app.ActivityThread.main(ActivityThread.java:7625)
123 | at java.lang.reflect.Method.invoke(Native Method)
124 | at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524)
125 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)
126 | ```
127 |
128 | * 这是因为 WindowManager addView 的时候成功了,但是 removeView 的时候失败了,导致下一个 Toast 显示的时候,无法复用上一个 Toast 的 View 对象,虽然这种情况比较少见,但是仍然有人跟我反馈过这个问题,为了解决这一问题,所以决定不去复用 View 对象,具体对这块的调整可以查看发版记录:[Toaster/releases/tag/9.0](https://github.com/getActivity/Toaster/releases/tag/9.0)
129 |
130 | #### 怎么切换成 Toast 排队显示的策略
131 |
132 | * 只需要修改 Toast 框架的初始化方式,手动传入 Toast 策略类,这里使用框架已经封装好的 ToastStrategy 类即可,
133 |
134 | ```java
135 | // 初始化 Toast 框架
136 | // Toaster.init(this);
137 | Toaster.init(this, new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE));
138 | ```
139 |
140 | * 注意构造函数需要传入 `ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE`,关于这个字段的介绍可以看下面的代码注释
141 |
142 | ```java
143 | public class ToastStrategy {
144 |
145 | /**
146 | * 即显即示模式(默认)
147 | *
148 | * 在发起多次 Toast 的显示请求情况下,显示下一个 Toast 之前
149 | * 会先立即取消上一个 Toast,保证当前显示 Toast 消息是最新的
150 | */
151 | public static final int SHOW_STRATEGY_TYPE_IMMEDIATELY = 0;
152 |
153 | /**
154 | * 不丢消息模式
155 | *
156 | * 在发起多次 Toast 的显示请求情况下,等待上一个 Toast 显示 1 秒或者 1.5 秒后
157 | * 然后再显示下一个 Toast,不按照 Toast 的显示时长来,因为那样等待时间会很长
158 | * 这样既能保证用户能看到每一条 Toast 消息,又能保证用户不会等得太久,速战速决
159 | */
160 | public static final int SHOW_STRATEGY_TYPE_QUEUE = 1;
161 | }
162 | ```
163 |
164 | #### 框架无法满足我当前使用的场景怎么办
165 |
166 | * Toaster 框架意在解决一些的 Toast 需求,如果 Toaster 无法满足你的需求,你可以考虑使用 [EasyWindow](https://github.com/getActivity/EasyWindow) 悬浮窗框架来实现。
167 |
168 | #### 框架中的 Toast 如何实现跨页面显示
169 |
170 | * Toaster 中默认 Toast 是只在当前 Activity 上面展示的,如果遇到 Activity 切换,那么在 Toast 随着当前 Activity 销毁而不可见,导致无法在新 Activity 上面展示,框架在 [12.5](https://github.com/getActivity/Toaster/releases/tag/12.5) 版本新增支持了这一功能,具体调用示例如下:
171 |
172 | ```java
173 | ToastParams params = new ToastParams();
174 | params.text = "我是一个能跨页面展示的 Toast";
175 | // 表示这个 Toast 需要跨页面展示
176 | params.crossPageShow = true;
177 | Toaster.show(params);
178 | ```
179 |
180 | #### 为什么框架优先使用 WindowManager 来实现 Toast
181 |
182 | * 系统 Toast 的坑太多了,主要问题表现如下:
183 |
184 | * 系统 Toast 会引发一些内存泄漏的问题
185 |
186 | * 系统 Toast 无法实现自定义显示动画、显示时长控制
187 |
188 | * Android 7.1 版本会主线程阻塞会出现 BadTokenException 的问题
189 |
190 | * Android 10.0 以下关闭通知栏权限会导致系统 Toast 显示不出来的问题
191 |
192 | * Android 11 及以上版本,无法自定义 Toast 样式(布局、位置重心、位置偏移)
193 |
194 | * 所以框架优先使用 WindowManager 来代替实现 Toast 显示,具体优缺点以下:
195 |
196 | * 优点
197 |
198 | * 不会出现内存泄漏,也不会有那么多奇奇怪怪的问题
199 |
200 | * 可定制程度高,支持自定义动画和自定义显示时长
201 |
202 | * 突破 Google 在新版本 Android 对 Toast 的一些限制
203 |
204 | * 缺点
205 |
206 | * WindowManager 无法在没有悬浮窗权限情况下在后台弹出
(框架的解决方案:如果是在后台的情况下显示,则使用系统的 Toast 来显示)
207 |
208 | * WindowManager 会和 Activity 绑定,会随 Activity 销毁而消失
(框架的解决方案:延迟 200 毫秒显示,由此等待最新的 Activity 创建出来才调用显示,这样 WindowManager 就和最新 Activity 绑定在一起,就不会出现和旧 Activity finish 时一起消失的问题)
209 |
210 | * 当然不是说用系统 Toast 就不好,用 WindowManger 一定就好,视具体的使用场景而定,我觉得最好的方式是:应用处于前台状态下使用 WindowManager 来显示,而处于后台状态下使用系统 Toast 来显示,两者相结合,优势互补才是最佳方案。
211 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, September 2018
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright 2018 Huang JinQun
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README-en.md:
--------------------------------------------------------------------------------
1 | # [English Doc](README-en.md)
2 |
3 | # Toast Framework
4 |
5 | * Project address: [Github](https://github.com/getActivity/Toaster)
6 |
7 | * [Click here to download demo apk directly](https://github.com/getActivity/Toaster/releases/download/12.8/Toaster.apk)
8 |
9 |   
10 |
11 |   
12 |
13 |  
14 |
15 | #### Integration steps
16 |
17 | * If your project Gradle configuration is in `7.0` below, needs to be in `build.gradle` file added
18 |
19 | ```groovy
20 | allprojects {
21 | repositories {
22 | // JitPack remote repository:https://jitpack.io
23 | maven { url 'https://jitpack.io' }
24 | }
25 | }
26 | ```
27 |
28 | * If your Gradle configuration is `7.0` or above, needs to be in `settings.gradle` file added
29 |
30 | ```groovy
31 | dependencyResolutionManagement {
32 | repositories {
33 | // JitPack remote repository:https://jitpack.io
34 | maven { url 'https://jitpack.io' }
35 | }
36 | }
37 | ```
38 |
39 | * After configuring the remote warehouse, under the project app module `build.gradle` add remote dependencies to the file
40 |
41 | ```groovy
42 | android {
43 | // Support JDK 1.8
44 | compileOptions {
45 | targetCompatibility JavaVersion.VERSION_1_8
46 | sourceCompatibility JavaVersion.VERSION_1_8
47 | }
48 | }
49 |
50 | dependencies {
51 | // Toast framework:https://github.com/getActivity/Toaster
52 | implementation 'com.github.getActivity:Toaster:12.8'
53 | }
54 | ```
55 |
56 | #### Initialize the framework
57 |
58 | ```java
59 | public class XxxApplication extends Application {
60 |
61 | @Override
62 | public void onCreate() {
63 | super.onCreate();
64 |
65 | // Initialize the toast framework
66 | Toaster.init(this);
67 | }
68 | }
69 | ```
70 |
71 | #### Framework API introduction
72 |
73 | ```java
74 | // Show toast
75 | Toaster.show(CharSequence text);
76 | Toaster.show(int id);
77 | Toaster.show(Object object);
78 |
79 | // Toast is displayed in debug mode
80 | Toaster.debugShow(CharSequence text);
81 | Toaster.debugShow(int id);
82 | Toaster.debugShow(Object object);
83 |
84 | // Delayed display of toast
85 | Toaster.delayedShow(CharSequence text, long delayMillis);
86 | Toaster.delayedShow(int id, long delayMillis);
87 | Toaster.delayedShow(Object object, long delayMillis);
88 |
89 | // Show short toast
90 | Toaster.showShort(CharSequence text);
91 | Toaster.showShort(int id);
92 | Toaster.showShort(Object object);
93 |
94 | // Show long toast
95 | Toaster.showLong(CharSequence text);
96 | Toaster.showLong(int id);
97 | Toaster.showLong(Object object);
98 |
99 | // Custom display toast
100 | Toaster.show(ToastParams params);
101 |
102 | // Cancel toast
103 | Toaster.cancel();
104 |
105 | // Set toast layout (global effect)
106 | Toaster.setView(int id);
107 |
108 | // Set toast style (global effect)
109 | Toaster.setStyle(IToastStyle> style);
110 | // Get toast style
111 | Toaster.getStyle()
112 |
113 | // Determine whether the current framework has been initialized
114 | Toaster.isInit();
115 |
116 | // Set toast strategy (global effect)
117 | Toaster.setStrategy(IToastStrategy strategy);
118 | // Get toast strategy
119 | Toaster.getStrategy();
120 |
121 | // Set toast center of gravity and offset
122 | Toaster.setGravity(int gravity);
123 | Toaster.setGravity(int gravity, int xOffset, int yOffset);
124 |
125 | // Set Toast interceptor (global effect)
126 | Toaster.setInterceptor(IToastInterceptor interceptor);
127 | // Get Toast interceptor
128 | Toaster.getInterceptor();
129 | ```
130 |
131 | ## [Please click here to view frequently asked questions](HelpDoc-en.md)
132 |
133 | #### Comparison between different Toast frameworks
134 |
135 | | Function or detail | [Toaster](https://github.com/getActivity/Toaster) |[ AndroidUtilCode-ToastUtils ](https://github.com/Blankj/AndroidUtilCode)| [Toasty](https://github.com/GrenderG/Toasty) |
136 | | :----: | :------: | :-----: | :-----: |
137 | | Corresponding version | 12.8 | 1.30.6 | 1.5.0 |
138 | | Number of issues | [](https://github.com/getActivity/Toaster/issues) |[](https://github.com/Blankj/AndroidUtilCode/issues)| [](https://github.com/GrenderG/Toasty/issues) |
139 | | Framework pack size | 31 KB | 500 KB | 50 KB |
140 | | Framework maintenance status| 维护中 | 停止维护 | 停止维护 |
141 | | Call code trace | ✅ | ❌ | ❌ |
142 | | Support calling display in sub-threads | ✅ | ✅ | ❌ |
143 | | Support setting partial Toast style | ✅ | ❌ | ❌ |
144 | | Support setting global Toast style | ✅ | ❌ | ❌ |
145 | | Support Toast Instant display | ✅ | ✅ | ❌ |
146 | | Support Toast queue display | ✅ | ❌ | ✅ |
147 | | Support Toast delayed display | ✅ | ❌ | ❌ |
148 | | Solve the problem that Toast crashes on Android 7.1 | ✅ | ✅ | ❌ |
149 | | Compatible with the problem that the Toast cannot be displayed after the permission of the notification bar is turned off | ✅ | ✅ | ❌ |
150 | | Adapted to Android 11, the problem that Toast cannot be displayed in the background | ✅ | ❌ | ❌ |
151 |
152 | #### Introduction to calling code positioning function
153 |
154 | * 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.
155 |
156 | 
157 |
158 | #### Introduction to the problem of Toast crashing on Android 7.1
159 |
160 | * 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.
161 |
162 | * 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.
163 |
164 | #### Introduction to the problem that Toast cannot be displayed after the notification bar permission is turned off
165 |
166 | * 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.
167 |
168 | * 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.
169 |
170 | #### Android 11 cannot display Toast in the background
171 |
172 | * 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`.
173 |
174 | * 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.
175 |
176 | * It is worth noting that Toaster is currently the first and only framework of its kind to adapt to this feature of Android 11.
177 |
178 | #### Framework highlights
179 |
180 | * Take the lead: the first toast framework adapted to Android 11, developer do not need to care about the adaptation process
181 |
182 | * No permissions required: Regardless of whether the notification bar permission is granted or not, it does not affect the pop-up of the toast
183 |
184 | * Strong compatibility: Deal with the historical legacy of native Toast crashes in Android 7.1
185 |
186 | * Powerful functions: Toast can be popped up regardless of primary and secondary threads, and resource id and int type can be automatically identified
187 |
188 | * 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
189 |
190 | * Best performance: use lazy loading mode, only create Toast when displaying, do not take up Application startup time
191 |
192 | * Best experience: Displaying the next Toast will cancel the display of the previous Toast, so that it can be displayed immediately
193 |
194 | * Global unity: You can initialize the Toast style in the Application to achieve a once-and-for-all effect
195 |
196 | #### How to replace the existing native Toast in the project
197 |
198 | * Right-click the pop-up menu in the project, Replace in path, check the Regex option, and click Replace
199 |
200 | ```text
201 | Toast\.makeText\([^,]+,\s*(.+),\s*[^,]+\)\.show\(\)
202 | ```
203 |
204 | ```text
205 | Toaster.show($1)
206 | ```
207 |
208 | * Replace the package name
209 |
210 | ```text
211 | import android.widget.Toast
212 | ```
213 |
214 | ```text
215 | import com.hjq.toast.Toaster
216 | ```
217 |
218 | * Then search globally and manually replace some that have not been replaced successfully
219 |
220 | ```text
221 | Toast.makeText
222 | new Toast
223 | ```
224 |
225 | #### Author's other open source projects
226 |
227 | * Android middle office: [AndroidProject](https://github.com/getActivity/AndroidProject)
228 |
229 | * Android middle office kt version: [AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)
230 |
231 | * Permissions framework: [XXPermissions](https://github.com/getActivity/XXPermissions)  
232 |
233 | * Network framework: [EasyHttp](https://github.com/getActivity/EasyHttp)
234 |
235 | * Title bar framework: [TitleBar](https://github.com/getActivity/TitleBar)
236 |
237 | * Floating window framework: [EasyWindow](https://github.com/getActivity/EasyWindow)
238 |
239 | * Shape view framework: [ShapeView](https://github.com/getActivity/ShapeView)
240 |
241 | * Shape drawable framework: [ShapeDrawable](https://github.com/getActivity/ShapeDrawable)
242 |
243 | * Language switching framework: [Multi Languages](https://github.com/getActivity/MultiLanguages)
244 |
245 | * Gson parsing fault tolerance: [GsonFactory](https://github.com/getActivity/GsonFactory)
246 |
247 | * Logcat viewing framework: [Logcat](https://github.com/getActivity/Logcat)
248 |
249 | * Nested scrolling layout framework:[NestedScrollLayout](https://github.com/getActivity/NestedScrollLayout)  
250 |
251 | * Android version guide: [AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)
252 |
253 | * Android code standard: [AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)
254 |
255 | * Android resource summary:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
256 |
257 | * Android open source leaderboard: [AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)
258 |
259 | * Studio boutique plugins: [StudioPlugins](https://github.com/getActivity/StudioPlugins)
260 |
261 | * Emoji collection: [emoji pa c shadow](https://github.com/getActivity/EmojiPackage)
262 |
263 | * China provinces json: [ProvinceJson](https://github.com/getActivity/ProvinceJson)
264 |
265 | * Markdown documentation:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc)  
266 |
267 | ## License
268 |
269 | ```text
270 | Copyright 2018 Huang JinQun
271 |
272 | Licensed under the Apache License, Version 2.0 (the "License");
273 | you may not use this file except in compliance with the License.
274 | You may obtain a copy of the License at
275 |
276 | http://www.apache.org/licenses/LICENSE-2.0
277 |
278 | Unless required by applicable law or agreed to in writing, software
279 | distributed under the License is distributed on an "AS IS" BASIS,
280 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
281 | See the License for the specific language governing permissions and
282 | limitations under the License.
283 | ```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [English Doc](README-en.md)
2 |
3 | # 吐司框架
4 |
5 | * 项目地址:[Github](https://github.com/getActivity/Toaster)
6 |
7 | * 博客地址:[只需体验三分钟,你就会跟我一样,爱上这款 Toast](https://www.jianshu.com/p/9b174ee2c571)
8 |
9 | * 可以扫码下载 Demo 进行演示或者测试,如果扫码下载不了的,[点击此处可直接下载](https://github.com/getActivity/Toaster/releases/download/12.8/Toaster.apk)
10 |
11 | 
12 |
13 |   
14 |
15 |   
16 |
17 |  
18 |
19 | #### 集成步骤
20 |
21 | * 如果你的项目 Gradle 配置是在 `7.0 以下`,需要在 `build.gradle` 文件中加入
22 |
23 | ```groovy
24 | allprojects {
25 | repositories {
26 | // JitPack 远程仓库:https://jitpack.io
27 | maven { url 'https://jitpack.io' }
28 | }
29 | }
30 | ```
31 |
32 | * 如果你的 Gradle 配置是 `7.0 及以上`,则需要在 `settings.gradle` 文件中加入
33 |
34 | ```groovy
35 | dependencyResolutionManagement {
36 | repositories {
37 | // JitPack 远程仓库:https://jitpack.io
38 | maven { url 'https://jitpack.io' }
39 | }
40 | }
41 | ```
42 |
43 | * 配置完远程仓库后,在项目 app 模块下的 `build.gradle` 文件中加入远程依赖
44 |
45 | ```groovy
46 | android {
47 | // 支持 JDK 1.8
48 | compileOptions {
49 | targetCompatibility JavaVersion.VERSION_1_8
50 | sourceCompatibility JavaVersion.VERSION_1_8
51 | }
52 | }
53 |
54 | dependencies {
55 | // 吐司框架:https://github.com/getActivity/Toaster
56 | implementation 'com.github.getActivity:Toaster:12.8'
57 | }
58 | ```
59 |
60 | #### 初始化框架
61 |
62 | ```java
63 | public class XxxApplication extends Application {
64 |
65 | @Override
66 | public void onCreate() {
67 | super.onCreate();
68 |
69 | // 初始化 Toast 框架
70 | Toaster.init(this);
71 | }
72 | }
73 | ```
74 |
75 | #### 框架 API 介绍
76 |
77 | ```java
78 | // 显示 Toast
79 | Toaster.show(CharSequence text);
80 | Toaster.show(int id);
81 | Toaster.show(Object object);
82 |
83 | // debug 模式下显示 Toast
84 | Toaster.debugShow(CharSequence text);
85 | Toaster.debugShow(int id);
86 | Toaster.debugShow(Object object);
87 |
88 | // 延迟显示 Toast
89 | Toaster.delayedShow(CharSequence text, long delayMillis);
90 | Toaster.delayedShow(int id, long delayMillis);
91 | Toaster.delayedShow(Object object, long delayMillis);
92 |
93 | // 显示短 Toast
94 | Toaster.showShort(CharSequence text);
95 | Toaster.showShort(int id);
96 | Toaster.showShort(Object object);
97 |
98 | // 显示长 Toast
99 | Toaster.showLong(CharSequence text);
100 | Toaster.showLong(int id);
101 | Toaster.showLong(Object object);
102 |
103 | // 自定义显示 Toast
104 | Toaster.show(ToastParams params);
105 |
106 | // 取消 Toast
107 | Toaster.cancel();
108 |
109 | // 设置 Toast 布局(全局生效)
110 | Toaster.setView(int id);
111 |
112 | // 设置 Toast 样式(全局生效)
113 | Toaster.setStyle(IToastStyle> style);
114 | // 获取 Toast 样式
115 | Toaster.getStyle()
116 |
117 | // 判断当前框架是否已经初始化
118 | Toaster.isInit();
119 |
120 | // 设置 Toast 策略(全局生效)
121 | Toaster.setStrategy(IToastStrategy strategy);
122 | // 获取 Toast 策略
123 | Toaster.getStrategy();
124 |
125 | // 设置 Toast 重心和偏移
126 | Toaster.setGravity(int gravity);
127 | Toaster.setGravity(int gravity, int xOffset, int yOffset);
128 |
129 | // 设置 Toast 拦截器(全局生效)
130 | Toaster.setInterceptor(IToastInterceptor interceptor);
131 | // 获取 Toast 拦截器
132 | Toaster.getInterceptor();
133 | ```
134 |
135 | ## [常见疑问请点击此处查看](HelpDoc-zh.md)
136 |
137 | #### 不同 Toast 框架之间的对比
138 |
139 | | 功能或细节 | [Toaster](https://github.com/getActivity/Toaster) | [AndroidUtilCode-ToastUtils](https://github.com/Blankj/AndroidUtilCode) | [Toasty](https://github.com/GrenderG/Toasty) |
140 | | :----: | :------: | :-----: | :-----: |
141 | | 对应版本 | 12.8 | 1.30.6 | 1.5.0 |
142 | | issues 数 | [](https://github.com/getActivity/Toaster/issues) | [](https://github.com/Blankj/AndroidUtilCode/issues) | [](https://github.com/GrenderG/Toasty/issues) |
143 | | 框架体积 | 32 KB | 500 KB | 50 KB |
144 | | 框架维护状态 | 维护中 | 停止维护 | 停止维护 |
145 | | 调用代码定位 | ✅ | ❌ | ❌ |
146 | | 支持在子线程中调用显示 | ✅ | ✅ | ❌ |
147 | | 支持设置局部 Toast 样式 | ✅ | ❌ | ❌ |
148 | | 支持设置全局 Toast 样式 | ✅ | ❌ | ❌ |
149 | | 支持 Toast 即显即示 | ✅ | ✅ | ❌ |
150 | | 支持 Toast 排队显示 | ✅ | ❌ | ✅ |
151 | | 支持 Toast 延迟显示 | ✅ | ❌ | ❌ |
152 | | 处理 Toast 在 Android 7.1 崩溃的问题 | ✅ | ✅ | ❌ |
153 | | 兼容通知栏权限关闭后 Toast 显示不出来的问题 | ✅ | ✅ | ❌ |
154 | | 适配 Android 11 不能在后台显示 Toast 的问题 | ✅ | ❌ | ❌ |
155 |
156 | #### 调用代码定位功能介绍
157 |
158 | * 框架会在日志打印中输出在 Toast 调用的代码位置,这样开发者可以直接通过点击 Log 来定位是在哪个类哪行代码调用的,这样可以极大提升我们排查问题的效率,特别是 Toast 的内容是由服务器返回的情况下,我相信没有任何一个人会拒绝这样的功能。
159 |
160 | 
161 |
162 | #### Toast 在 Android 7.1 崩溃的问题介绍
163 |
164 | > [Toast 在 Android 7.1 崩溃排查及修复](https://www.jianshu.com/p/437f473017d6)
165 |
166 | * 这个问题是由于 Android 7.1 加入 WindowToken 校验机制导致的,而这个 WindowToken 是 NotificationManagerService 生成的,这个 WindowToken 是存在一定时效性的,而当应用的主线程被阻塞时,WindowManager 在 addView 时会对 WindowToken 进行校验,但是 WindowToken 已经过期了,这个时候 addView 就会抛出异常。
167 |
168 | * 谷歌在 Android 8.0 就修复了这个问题,修复方式十分简单粗暴,就是直接捕获这个异常,而框架的修复思路跟谷歌类似,只不过修复方式不太一样,因为框架无法直接修改系统源码,所以是直接通过 Hook 的方式对异常进行捕获。
169 |
170 | #### 通知栏权限关闭后 Toast 显示不出来的问题介绍
171 |
172 | > [Toast通知栏权限填坑指南](https://www.jianshu.com/p/1d64a5ccbc7c)
173 |
174 | * 这个问题的出现是因为原生 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 工程师一起确认过这个事情。
175 |
176 | * 框架处理这个问题的方式有两种,先判断当前应用是否处于前台状态,如果是则使用自定义的 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 测试就不行,所以具体要看产品怎么斟酌。
177 |
178 | #### Android 11 不能在后台显示 Toast 的问题介绍
179 |
180 | * 当我们将 targetSdkVersion 改成 30 及以上的版本时,会发现一个问题,如果应用处于后台进程的情况下,而恰好我们的应用 Toast 样式是经过定制的,那么在这些情况下调用 Toast 的 show 方法会惊奇的发现,Toast 没有显示出来,请注意这个问题不是 Bug,而是 Android 11 禁止了这种行为,在 [Toast 官方文档](https://developer.android.google.cn/reference/android/widget/Toast#setView(android.view.View)) 中也有注明,不建议对 Toast 的样式做定制化,并且还对 `Toast.setView` 方法进行了标记过时处理。
181 |
182 | * 那么我们如何解决这一问题呢?难道真的不能用自定义样式的 Toast 了?我的答案是:不,凡事不能一刀切,谷歌只说不能在后台显示自定义的 Toast,并不能代表不能在前台那么做,框架的适配思路是,在 Android 11 的情况下,会先判断当前应用是处于前台还是后台,如果是在前台的情况下就显示自定义样式的 Toast,如果是在后台的情况下就显示系统样式的 Toast(通过舍弃自定义样式来保证 Toast 能够正常显示出来),这样既能符合 Android 11 要求,同时又能将定制化 Toast 的权益最大化。
183 |
184 | * 值得注意的是:Toaster 是目前同类框架第一款也是唯一一款适配 Android 11 这一特性的框架。
185 |
186 | #### 框架亮点
187 |
188 | * 一马当先:首款适配 Android 11 的吐司框架,开发者无需关心适配过程
189 |
190 | * 无需权限:[不管有没有授予通知栏权限都不影响吐司的弹出](https://www.jianshu.com/p/1d64a5ccbc7c)
191 |
192 | * 兼容性强:[处理原生 Toast 在 Android 7.1 产生崩溃的历史遗留问题](https://www.jianshu.com/p/437f473017d6)
193 |
194 | * 功能强大:不分主次线程都可以弹出Toast,自动识别资源 id 和 int 类型
195 |
196 | * 使用简单:只需传入文本,会自动根据文本长度决定吐司显示的时长
197 |
198 | * 性能最佳:使用懒加载模式,只在显示时创建 Toast,不占用 Application 启动时间
199 |
200 | * 体验最佳:显示下一个 Toast 会取消上一个 Toast 的显示,真正做到即显即示
201 |
202 | * 全局统一:可以在 Application 中初始化 Toast 样式,达到一劳永逸的效果
203 |
204 | #### 如何替换项目中已有的原生 Toast
205 |
206 | * 在项目中右击弹出菜单,Replace in path,勾选 Regex 选项,点击替换
207 |
208 | ```text
209 | Toast\.makeText\([^,]+,\s*(.+),\s*[^,]+\)\.show\(\)
210 | ```
211 |
212 | ```text
213 | Toaster.show($1)
214 | ```
215 |
216 | * 对导包进行替换
217 |
218 | ```text
219 | import android.widget.Toast
220 | ```
221 |
222 | ```text
223 | import com.hjq.toast.Toaster
224 | ```
225 |
226 | * 再全局搜索,手动更换一些没有替换成功的
227 |
228 | ```text
229 | Toast.makeText
230 | new Toast
231 | ```
232 |
233 | #### 作者的其他开源项目
234 |
235 | * 安卓技术中台:[AndroidProject](https://github.com/getActivity/AndroidProject)  
236 |
237 | * 安卓技术中台 Kt 版:[AndroidProject-Kotlin](https://github.com/getActivity/AndroidProject-Kotlin)  
238 |
239 | * 权限框架:[XXPermissions](https://github.com/getActivity/XXPermissions)  
240 |
241 | * 网络框架:[EasyHttp](https://github.com/getActivity/EasyHttp)  
242 |
243 | * 标题栏框架:[TitleBar](https://github.com/getActivity/TitleBar)  
244 |
245 | * 悬浮窗框架:[EasyWindow](https://github.com/getActivity/EasyWindow)  
246 |
247 | * ShapeView 框架:[ShapeView](https://github.com/getActivity/ShapeView)  
248 |
249 | * ShapeDrawable 框架:[ShapeDrawable](https://github.com/getActivity/ShapeDrawable)  
250 |
251 | * 语种切换框架:[MultiLanguages](https://github.com/getActivity/MultiLanguages)  
252 |
253 | * Gson 解析容错:[GsonFactory](https://github.com/getActivity/GsonFactory)  
254 |
255 | * 日志查看框架:[Logcat](https://github.com/getActivity/Logcat)  
256 |
257 | * 嵌套滚动布局框架:[NestedScrollLayout](https://github.com/getActivity/NestedScrollLayout)  
258 |
259 | * Android 版本适配:[AndroidVersionAdapter](https://github.com/getActivity/AndroidVersionAdapter)  
260 |
261 | * Android 代码规范:[AndroidCodeStandard](https://github.com/getActivity/AndroidCodeStandard)  
262 |
263 | * Android 资源大汇总:[AndroidIndex](https://github.com/getActivity/AndroidIndex)  
264 |
265 | * Android 开源排行榜:[AndroidGithubBoss](https://github.com/getActivity/AndroidGithubBoss)  
266 |
267 | * Studio 精品插件:[StudioPlugins](https://github.com/getActivity/StudioPlugins)  
268 |
269 | * 表情包大集合:[EmojiPackage](https://github.com/getActivity/EmojiPackage)  
270 |
271 | * AI 资源大汇总:[AiIndex](https://github.com/getActivity/AiIndex)  
272 |
273 | * 省市区 Json 数据:[ProvinceJson](https://github.com/getActivity/ProvinceJson)  
274 |
275 | * Markdown 语法文档:[MarkdownDoc](https://github.com/getActivity/MarkdownDoc)  
276 |
277 | #### 微信公众号:Android轮子哥
278 |
279 | 
280 |
281 | #### Android 技术 Q 群:10047167
282 |
283 | #### 如果您觉得我的开源库帮你节省了大量的开发时间,请扫描下方的二维码随意打赏,要是能打赏个 10.24 :monkey_face:就太:thumbsup:了。您的支持将鼓励我继续创作:octocat:([点击查看捐赠列表](https://github.com/getActivity/Donate))
284 |
285 |  
286 |
287 | ## License
288 |
289 | ```text
290 | Copyright 2018 Huang JinQun
291 |
292 | Licensed under the Apache License, Version 2.0 (the "License");
293 | you may not use this file except in compliance with the License.
294 | You may obtain a copy of the License at
295 |
296 | http://www.apache.org/licenses/LICENSE-2.0
297 |
298 | Unless required by applicable law or agreed to in writing, software
299 | distributed under the License is distributed on an "AS IS" BASIS,
300 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
301 | See the License for the specific language governing permissions and
302 | limitations under the License.
303 | ```
--------------------------------------------------------------------------------
/app/AppSignature.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/app/AppSignature.jks
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 34
5 |
6 | defaultConfig {
7 | applicationId "com.hjq.toast.demo"
8 | minSdkVersion 16
9 | targetSdkVersion 34
10 | versionCode 1280
11 | versionName "12.8"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 |
15 | // 使用 JDK 1.8
16 | compileOptions {
17 | targetCompatibility JavaVersion.VERSION_1_8
18 | sourceCompatibility JavaVersion.VERSION_1_8
19 | }
20 |
21 | // Apk 签名的那些事:https://www.jianshu.com/p/a1f8e5896aa2
22 | signingConfigs {
23 | config {
24 | storeFile file(StoreFile)
25 | storePassword StorePassword
26 | keyAlias KeyAlias
27 | keyPassword KeyPassword
28 | }
29 | }
30 |
31 | buildTypes {
32 | debug {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
35 | signingConfig signingConfigs.config
36 | }
37 |
38 | release {
39 | minifyEnabled true
40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
41 | signingConfig signingConfigs.config
42 | }
43 | }
44 |
45 | applicationVariants.configureEach { variant ->
46 | // apk 输出文件名配置
47 | variant.outputs.configureEach { output ->
48 | outputFileName = rootProject.getName() + '.apk'
49 | }
50 | }
51 | }
52 |
53 | dependencies {
54 | // 依赖 libs 目录下所有的 jar 和 aar 包
55 | implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
56 |
57 | implementation project(':library')
58 |
59 | // AndroidX 库:https://github.com/androidx/androidx
60 | implementation 'androidx.appcompat:appcompat:1.4.0'
61 | // Material 库:https://github.com/material-components/material-components-android
62 | implementation 'com.google.android.material:material:1.4.0'
63 |
64 | // 标题栏框架:https://github.com/getActivity/TitleBar
65 | implementation 'com.github.getActivity:TitleBar:10.5'
66 |
67 | // 权限请求框架:https://github.com/getActivity/XXPermissions
68 | implementation 'com.github.getActivity:XXPermissions:20.0'
69 |
70 | // 悬浮窗框架:https://github.com/getActivity/EasyWindow
71 | implementation 'com.github.getActivity:EasyWindow:10.3'
72 |
73 | // 内存泄漏捕捉:https://github.com/square/leakcanary
74 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
75 | }
--------------------------------------------------------------------------------
/app/gradle.properties:
--------------------------------------------------------------------------------
1 | StoreFile = AppSignature.jks
2 | StorePassword = AndroidProject
3 | KeyAlias = AndroidProject
4 | KeyPassword = AndroidProject
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | -keep class com.hjq.toast.** {*;}
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/toast/demo/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.demo;
2 |
3 | import android.content.Intent;
4 | import android.net.Uri;
5 | import android.os.Build;
6 | import android.os.Bundle;
7 | import android.util.TypedValue;
8 | import android.view.Gravity;
9 | import android.view.View;
10 | import androidx.appcompat.app.AppCompatActivity;
11 | import com.google.android.material.snackbar.Snackbar;
12 | import com.hjq.bar.OnTitleBarListener;
13 | import com.hjq.bar.TitleBar;
14 | import com.hjq.permissions.Permission;
15 | import com.hjq.permissions.XXPermissions;
16 | import com.hjq.toast.ToastParams;
17 | import com.hjq.toast.ToastStrategy;
18 | import com.hjq.toast.Toaster;
19 | import com.hjq.toast.style.BlackToastStyle;
20 | import com.hjq.toast.style.CustomToastStyle;
21 | import com.hjq.toast.style.WhiteToastStyle;
22 | import com.hjq.window.EasyWindow;
23 |
24 | /**
25 | * author : Android 轮子哥
26 | * github : https://github.com/getActivity/Toaster
27 | * time : 2018/09/01
28 | * desc : Toaster 使用案例
29 | */
30 | public final class MainActivity extends AppCompatActivity {
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 | setContentView(R.layout.activity_main);
36 |
37 | TitleBar titleBar = findViewById(R.id.tb_main_bar);
38 | titleBar.setOnTitleBarListener(new OnTitleBarListener() {
39 | @Override
40 | public void onTitleClick(TitleBar titleBar) {
41 | Intent intent = new Intent(Intent.ACTION_VIEW);
42 | intent.setData(Uri.parse(titleBar.getTitle().toString()));
43 | startActivity(intent);
44 | }
45 | });
46 | }
47 |
48 | public void showToast(View v) {
49 | Toaster.show(R.string.demo_show_toast_result);
50 | }
51 |
52 | public void showShortToast(View v) {
53 | Toaster.showShort(R.string.demo_show_short_toast_result);
54 | }
55 |
56 | public void showLongToast(View v) {
57 | Toaster.showLong(R.string.demo_show_long_toast_result);
58 | }
59 |
60 | public void showCrossPageToast(View v) {
61 | ToastParams params = new ToastParams();
62 | params.text = getString(R.string.demo_show_cross_page_toast_result);
63 | params.crossPageShow = true;
64 | Toaster.show(params);
65 | }
66 |
67 | public void delayShowToast(View v) {
68 | Toaster.delayedShow(R.string.demo_show_toast_with_two_second_delay_result, 2000);
69 | }
70 |
71 | @SuppressWarnings("AlibabaAvoidManuallyCreateThread")
72 | public void threadShowToast(View v) {
73 | new Thread(new Runnable() {
74 |
75 | @Override
76 | public void run() {
77 | Toaster.show(R.string.demo_show_toast_in_the_subthread_result);
78 | }
79 | }).start();
80 | }
81 |
82 | public void switchToastStyleToWhite(View v) {
83 | ToastParams params = new ToastParams();
84 | params.text = getString(R.string.demo_switch_to_white_style_result);
85 | params.style = new WhiteToastStyle();
86 | Toaster.show(params);
87 | }
88 |
89 | public void switchToastStyleToBlack(View v) {
90 | ToastParams params = new ToastParams();
91 | params.text = getString(R.string.demo_switch_to_black_style_result);
92 | params.style = new BlackToastStyle();
93 | Toaster.show(params);
94 | }
95 |
96 | public void switchToastStyleToInfo(View v) {
97 | ToastParams params = new ToastParams();
98 | params.text = getString(R.string.demo_switch_to_info_style_result);
99 | params.style = new CustomToastStyle(R.layout.toast_info);
100 | Toaster.show(params);
101 | }
102 |
103 | public void switchToastStyleToWarn(View v) {
104 | ToastParams params = new ToastParams();
105 | params.text = getString(R.string.demo_switch_to_warn_style_result);
106 | params.style = new CustomToastStyle(R.layout.toast_warn);
107 | Toaster.show(params);
108 | }
109 |
110 | public void switchToastStyleToSuccess(View v) {
111 | ToastParams params = new ToastParams();
112 | params.text = getString(R.string.demo_switch_to_success_style_result);
113 | params.style = new CustomToastStyle(R.layout.toast_success);
114 | Toaster.show(params);
115 | }
116 |
117 | public void switchToastStyleToError(View v) {
118 | ToastParams params = new ToastParams();
119 | params.text = getString(R.string.demo_switch_to_error_style_result);
120 | params.style = new CustomToastStyle(R.layout.toast_error);
121 | Toaster.show(params);
122 | }
123 |
124 | public void customGlobalToastStyle(View v) {
125 | Toaster.setView(R.layout.toast_custom_view);
126 | Toaster.setGravity(Gravity.CENTER);
127 | Toaster.show(R.string.demo_custom_toast_layout_result);
128 | }
129 |
130 | public void switchToastStrategy(View v) {
131 | Toaster.setStrategy(new ToastStrategy(ToastStrategy.SHOW_STRATEGY_TYPE_QUEUE));
132 | Toaster.show(R.string.demo_switch_to_toast_queuing_strategy_result);
133 | findViewById(R.id.tv_main_thrice_show).setVisibility(View.VISIBLE);
134 | }
135 |
136 | public void showThriceToast(View v) {
137 | for (int i = 0; i < 3; i++) {
138 | Toaster.show(String.format(getString(R.string.demo_show_three_toast_copywriting), i + 1));
139 | }
140 | }
141 |
142 | public void toBackgroundShowToast(View v) {
143 | Snackbar.make(getWindow().getDecorView(), getString(R.string.demo_show_toast_in_background_state_hint), Snackbar.LENGTH_SHORT).show();
144 |
145 | v.postDelayed(new Runnable() {
146 | @Override
147 | public void run() {
148 | Snackbar.make(getWindow().getDecorView(), getString(R.string.demo_show_toast_in_background_state_snack_bar), Snackbar.LENGTH_SHORT).show();
149 | }
150 | }, 2000);
151 |
152 | v.postDelayed(new Runnable() {
153 | @Override
154 | public void run() {
155 | Intent intent = new Intent(Intent.ACTION_MAIN);
156 | intent.addCategory(Intent.CATEGORY_HOME);
157 | startActivity(intent);
158 | }
159 | }, 4000);
160 |
161 | v.postDelayed(new Runnable() {
162 | @Override
163 | public void run() {
164 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
165 | if (XXPermissions.isGranted(MainActivity.this, Permission.SYSTEM_ALERT_WINDOW)) {
166 | Toaster.show(R.string.demo_show_toast_in_background_state_result_1);
167 | } else {
168 | Toaster.show(R.string.demo_show_toast_in_background_state_result_2);
169 | }
170 | } else {
171 | Toaster.show(R.string.demo_show_toast_in_background_state_result_3);
172 | }
173 | }
174 | }, 5000);
175 | }
176 |
177 | public void combinationEasyWindowShow(View v) {
178 | new EasyWindow<>(this)
179 | .setDuration(1000)
180 | // 将 Toaster 中的 View 转移给 EasyWindow 来显示
181 | .setContentView(Toaster.getStyle().createView(getApplication()))
182 | .setAnimStyle(android.R.style.Animation_Translucent)
183 | .setText(android.R.id.message, R.string.demo_combining_window_framework_use_result)
184 | .setGravity(Gravity.BOTTOM)
185 | .setYOffset((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, getResources().getDisplayMetrics()))
186 | .show();
187 | }
188 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/hjq/toast/demo/ToastApplication.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.demo;
2 |
3 | import android.app.Application;
4 |
5 | import com.hjq.toast.Toaster;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/Toaster
10 | * time : 2018/09/01
11 | * desc : Toaster 初始化
12 | */
13 | public final class ToastApplication extends Application {
14 |
15 | @Override
16 | public void onCreate() {
17 | super.onCreate();
18 |
19 | // 初始化 Toast 框架
20 | Toaster.init(this);
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_error_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_error_ic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_hint_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_info_ic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_success_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_success_ic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_warn_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/toast_warn_ic.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
22 |
23 |
26 |
27 |
32 |
33 |
40 |
41 |
48 |
49 |
56 |
57 |
64 |
65 |
72 |
73 |
80 |
81 |
88 |
89 |
96 |
97 |
104 |
105 |
112 |
113 |
120 |
121 |
128 |
129 |
136 |
137 |
144 |
145 |
155 |
156 |
163 |
164 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toast_custom_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
14 |
15 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toast_error.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toast_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toast_success.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toast_warn.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v23/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Toaster
3 |
4 | 简单显示一个 Toast
5 | 显示一个短 Toast
6 | 显示一个长 Toast
7 | 显示一个跨页面的 Toast
8 | 延迟 2 秒显示 Toast
9 | 在子线程中显示吐司
10 | 切换为白色样式(局部生效)
11 | 切换为黑色样式(局部生效)
12 | 切换为提示样式(局部生效)
13 | 切换为警告样式(局部生效)
14 | 切换为成功样式(局部生效)
15 | 切换为错误样式(局部生效)
16 | 自定义 Toast 布局(全局生效)
17 | 切换成 Toast 排队显示策略
18 | 点我连续显示 3 个 Toast
19 | 在后台状态下显示 Toast
20 | 搭配 EasyWindow 悬浮窗框架
21 |
22 | 我是普通的 Toast
23 | 我是一个短 Toast
24 | 我是一个长 Toast
25 | 我是跨页面显示的 Toast
26 | 我是延迟 2 秒显示的 Toast
27 | 我是子线程中弹出的吐司
28 | 我是白色样式的 Toast
29 | 我是黑色样式的 Toast
30 | 我是提示样式的 Toast
31 | 我是警告样式的 Toast
32 | 我是成功样式的 Toast
33 | 我是错误样式的 Toast
34 | 我是自定义布局的 Toast(全局生效)
35 | 切换到排队显示策略成功,请点击下方文本来体验效果
36 | 我是第 %d 个 Toast
37 | 请系好安全带,即将跳转到手机桌面
38 | 温馨提示:安卓 10 在后台显示 Toast 需要有通知栏权限或者悬浮窗权限的情况下才可以显示
39 | 我是在后台显示的 Toast(有悬浮窗权限真的可以为所欲为)
40 | 我是在后台显示的 Toast(安卓 11 及以上在后台显示只能使用系统样式)
41 | 我是在后台显示的 Toast
42 | 就问你溜不溜
43 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #000000
5 | #FF0033
6 |
7 | #3F51B5
8 | #388E3C
9 | #FFA900
10 | #D50000
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Toaster
4 |
5 | Simple show a toast
6 | Show a short toast
7 | Show a long toast
8 | Show a cross page toast
9 | Show toast with 2 second delay
10 | Show toast in the subthread
11 | Switch to white style (local effect)
12 | Switch to black style (local effect)
13 | Switch to hint style (local effect)
14 | Switch to warn style (local effect)
15 | Switch to success style (local effect)
16 | Switch to error style (local effect)
17 | Custom toast layout (global effect)
18 | Switch to toast queuing display strategy
19 | Click and I show 3 toast in a row
20 | Show toast in background state
21 | Combining EasyWindow suspension window frames
22 |
23 | I am an normal toast
24 | I am a short toast
25 | I am a long toast
26 | I am a cross page toast
27 | I am a toast displayed with a delay of 2 seconds
28 | I am the toast that pops up in the subthread
29 | I am toast in white style
30 | I am toast in black style
31 | I am a toast of hint style
32 | I am a toast of warn style
33 | I am a toast of success style
34 | I am a toast of error style
35 | I am toast with a custom layout (global effect)
36 | Switch to queuing display strategy success, please click the text below to experience the effect
37 | I am the %d toast
38 | Fasten your seat belt, you are about to jump to the phone desktop
39 | Reminder: android 10 needs to have notification bar permission or floating window permission to display toast in the background
40 | I display the toast in the background (you can really do whatever you want with the floating window permission)
41 | I am displaying the toast in the background (Android 11 and above can only use the system style in the background display)
42 | I am displaying toast in the background
43 | Hello Word
44 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | // 阿里云云效仓库:https://maven.aliyun.com/mvn/guide
6 | maven { url 'https://maven.aliyun.com/repository/public' }
7 | maven { url 'https://maven.aliyun.com/repository/google' }
8 | // 华为开源镜像:https://mirrors.huaweicloud.com/
9 | maven { url 'https://repo.huaweicloud.com/repository/maven/' }
10 | // JitPack 远程仓库:https://jitpack.io
11 | maven { url 'https://jitpack.io' }
12 | mavenCentral()
13 | google()
14 | // noinspection JcenterRepositoryObsolete
15 | jcenter()
16 | }
17 | dependencies {
18 | classpath 'com.android.tools.build:gradle:4.1.2'
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | maven { url 'https://maven.aliyun.com/repository/public' }
25 | maven { url 'https://maven.aliyun.com/repository/google' }
26 | maven { url 'https://repo.huaweicloud.com/repository/maven/' }
27 | maven { url 'https://jitpack.io' }
28 | mavenCentral()
29 | google()
30 | // noinspection JcenterRepositoryObsolete
31 | jcenter()
32 | }
33 |
34 | // 读取 local.properties 文件配置
35 | def properties = new Properties()
36 | def localPropertiesFile = rootProject.file("local.properties")
37 | if (localPropertiesFile.exists()) {
38 | localPropertiesFile.withInputStream { inputStream ->
39 | properties.load(inputStream)
40 | }
41 | }
42 |
43 | String buildDirPath = properties.getProperty("build.dir")
44 | if (buildDirPath != null && buildDirPath != "") {
45 | // 将构建文件统一输出到指定的目录下
46 | setBuildDir(new File(buildDirPath, rootProject.name + "/build/${path.replaceAll(':', '/')}"))
47 | } else {
48 | // 将构建文件统一输出到项目根目录下的 build 文件夹
49 | setBuildDir(new File(rootDir, "build/${path.replaceAll(':', '/')}"))
50 | }
51 | }
52 |
53 | tasks.register('clean', Delete) {
54 | delete rootProject.buildDir
55 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | # 表示使用 AndroidX
20 | android.useAndroidX = true
21 | # 表示将第三方库迁移到 AndroidX
22 | android.enableJetifier = true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | zipStoreBase = GRADLE_USER_HOME
2 | zipStorePath = wrapper/dists
3 | distributionBase = GRADLE_USER_HOME
4 | distributionPath = wrapper/dists
5 | distributionUrl = https\://services.gradle.org/distributions/gradle-6.5-all.zip
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 33
5 |
6 | defaultConfig {
7 | minSdkVersion 14
8 | versionCode 1280
9 | versionName "12.8"
10 | }
11 |
12 | // 使用 JDK 1.8
13 | compileOptions {
14 | targetCompatibility JavaVersion.VERSION_1_8
15 | sourceCompatibility JavaVersion.VERSION_1_8
16 | }
17 |
18 | packagingOptions {
19 | // 剔除这个包下的所有文件(不会移除签名信息)
20 | exclude 'META-INF/*******'
21 | }
22 |
23 | android.libraryVariants.configureEach { variant ->
24 | // aar 输出文件名配置
25 | variant.outputs.all { output ->
26 | outputFileName = "${rootProject.name}-${android.defaultConfig.versionName}.aar"
27 | }
28 | }
29 | }
30 |
31 | afterEvaluate {
32 | // 排除 BuildConfig.class 和 R.class
33 | generateReleaseBuildConfig.enabled = false
34 | generateDebugBuildConfig.enabled = false
35 | generateReleaseResValues.enabled = false
36 | generateDebugResValues.enabled = false
37 | }
38 |
39 | tasks.withType(Javadoc).configureEach {
40 | options.addStringOption('Xdoclint:none', '-quiet')
41 | options.addStringOption('encoding', 'UTF-8')
42 | options.addStringOption('charSet', 'UTF-8')
43 | }
44 |
45 | tasks.register('sourcesJar', Jar) {
46 | from android.sourceSets.main.java.srcDirs
47 | classifier = 'sources'
48 | }
49 |
50 | tasks.register('javadoc', Javadoc) {
51 | source = android.sourceSets.main.java.srcDirs
52 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
53 | }
54 |
55 | tasks.register('javadocJar', Jar) {
56 | dependsOn javadoc
57 | classifier = 'javadoc'
58 | from javadoc.destinationDir
59 | }
60 |
61 | artifacts {
62 | archives javadocJar
63 | archives sourcesJar
64 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ActivityStack.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.Application;
6 | import android.os.Bundle;
7 |
8 | /**
9 | * author : Android 轮子哥
10 | * github : https://github.com/getActivity/Toaster
11 | * time : 2021/04/07
12 | * desc : Activity 生命周期监控
13 | */
14 | final class ActivityStack implements Application.ActivityLifecycleCallbacks {
15 |
16 | @SuppressLint("StaticFieldLeak")
17 | private static volatile ActivityStack sInstance;
18 |
19 | public static ActivityStack getInstance() {
20 | if(sInstance == null) {
21 | synchronized (ActivityStack.class) {
22 | if (sInstance == null) {
23 | sInstance = new ActivityStack();
24 | }
25 | }
26 | }
27 | return sInstance;
28 | }
29 |
30 | /** 私有化构造函数 */
31 | private ActivityStack() {}
32 |
33 | /**
34 | * 注册 Activity 生命周期监听
35 | */
36 | public void register(Application application) {
37 | if (application == null) {
38 | return;
39 | }
40 | application.registerActivityLifecycleCallbacks(this);
41 | }
42 |
43 | /** 前台 Activity 对象 */
44 | private Activity mForegroundActivity;
45 |
46 | public Activity getForegroundActivity() {
47 | return mForegroundActivity;
48 | }
49 |
50 | @Override
51 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
52 |
53 | @Override
54 | public void onActivityStarted(Activity activity) {}
55 |
56 | @Override
57 | public void onActivityResumed(Activity activity) {
58 | mForegroundActivity = activity;
59 | }
60 |
61 | @Override
62 | public void onActivityPaused(Activity activity) {
63 | if (mForegroundActivity != activity) {
64 | return;
65 | }
66 | mForegroundActivity = null;
67 | }
68 |
69 | @Override
70 | public void onActivityStopped(Activity activity) {}
71 |
72 | @Override
73 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
74 |
75 | @Override
76 | public void onActivityDestroyed(Activity activity) {}
77 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ActivityToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.app.Activity;
4 |
5 | /**
6 | * author : Android 轮子哥
7 | * github : https://github.com/getActivity/Toaster
8 | * time : 2021/11/30
9 | * desc : 利用 Activity 弹出 Toast
10 | */
11 | public class ActivityToast extends CustomToast {
12 |
13 | /** Toast 实现类 */
14 | private final ToastImpl mToastImpl;
15 |
16 | public ActivityToast(Activity activity) {
17 | mToastImpl = new ToastImpl(activity, this);
18 | }
19 |
20 | @Override
21 | public void show() {
22 | // 替换成 WindowManager 来显示
23 | mToastImpl.show();
24 | }
25 |
26 | @Override
27 | public void cancel() {
28 | // 取消 WindowManager 的显示
29 | mToastImpl.cancel();
30 | }
31 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/CustomToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | import com.hjq.toast.config.IToast;
7 |
8 | /**
9 | * author : Android 轮子哥
10 | * github : https://github.com/getActivity/Toaster
11 | * time : 2018/11/02
12 | * desc : 自定义 Toast 基类
13 | */
14 | public abstract class CustomToast implements IToast {
15 |
16 | /** Toast 布局 */
17 | private View mView;
18 | /** Toast 消息 View */
19 | private TextView mMessageView;
20 | /** Toast 显示重心 */
21 | private int mGravity;
22 | /** Toast 显示时长 */
23 | private int mDuration;
24 | /** 水平偏移 */
25 | private int mXOffset;
26 | /** 垂直偏移 */
27 | private int mYOffset;
28 | /** 水平间距 */
29 | private float mHorizontalMargin;
30 | /** 垂直间距 */
31 | private float mVerticalMargin;
32 | /** Toast 动画 */
33 | private int mAnimations = android.R.style.Animation_Toast;
34 | /** 短吐司显示的时长,参考至 NotificationManagerService.SHORT_DELAY */
35 | private int mShortDuration = 2000;
36 | /** 长吐司显示的时长,参考至 NotificationManagerService.LONG_DELAY */
37 | private int mLongDuration = 3500;
38 |
39 | @Override
40 | public void setText(int id) {
41 | if (mView == null) {
42 | return;
43 | }
44 | setText(mView.getResources().getString(id));
45 | }
46 |
47 | @Override
48 | public void setText(CharSequence text) {
49 | if (mMessageView == null) {
50 | return;
51 | }
52 | mMessageView.setText(text);
53 | }
54 |
55 | @Override
56 | public void setView(View view) {
57 | mView = view;
58 | if (mView == null) {
59 | mMessageView = null;
60 | return;
61 | }
62 | mMessageView = findMessageView(view);
63 | }
64 |
65 | @Override
66 | public View getView() {
67 | return mView;
68 | }
69 |
70 | @Override
71 | public void setDuration(int duration) {
72 | mDuration = duration;
73 | }
74 |
75 | @Override
76 | public int getDuration() {
77 | return mDuration;
78 | }
79 |
80 | @Override
81 | public void setGravity(int gravity, int xOffset, int yOffset) {
82 | mGravity = gravity;
83 | mXOffset = xOffset;
84 | mYOffset = yOffset;
85 | }
86 |
87 | @Override
88 | public int getGravity() {
89 | return mGravity;
90 | }
91 |
92 | @Override
93 | public int getXOffset() {
94 | return mXOffset;
95 | }
96 |
97 | @Override
98 | public int getYOffset() {
99 | return mYOffset;
100 | }
101 |
102 | @Override
103 | public void setMargin(float horizontalMargin, float verticalMargin) {
104 | mHorizontalMargin = horizontalMargin;
105 | mVerticalMargin = verticalMargin;
106 | }
107 |
108 | @Override
109 | public float getHorizontalMargin() {
110 | return mHorizontalMargin;
111 | }
112 |
113 | @Override
114 | public float getVerticalMargin() {
115 | return mVerticalMargin;
116 | }
117 |
118 | public void setAnimationsId(int animationsId) {
119 | mAnimations = animationsId;
120 | }
121 |
122 | public int getAnimationsId() {
123 | return mAnimations;
124 | }
125 |
126 | public void setShortDuration(int duration) {
127 | mShortDuration = duration;
128 | }
129 |
130 | public int getShortDuration() {
131 | return mShortDuration;
132 | }
133 |
134 | public void setLongDuration(int duration) {
135 | mLongDuration = duration;
136 | }
137 |
138 | public int getLongDuration() {
139 | return mLongDuration;
140 | }
141 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/GlobalToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * author : Android 轮子哥
7 | * github : https://github.com/getActivity/Toaster
8 | * time : 2021/11/30
9 | * desc : 利用悬浮窗权限弹出全局 Toast
10 | */
11 | public class GlobalToast extends CustomToast {
12 |
13 | /** Toast 实现类 */
14 | private final ToastImpl mToastImpl;
15 |
16 | public GlobalToast(Application application) {
17 | mToastImpl = new ToastImpl(application, this);
18 | }
19 |
20 | @Override
21 | public void show() {
22 | // 替换成 WindowManager 来显示
23 | mToastImpl.show();
24 | }
25 |
26 | @Override
27 | public void cancel() {
28 | // 取消 WindowManager 的显示
29 | mToastImpl.cancel();
30 | }
31 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/NotificationServiceProxy.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import java.lang.reflect.InvocationHandler;
4 | import java.lang.reflect.Method;
5 |
6 | /**
7 | * author : Android 轮子哥
8 | * github : https://github.com/getActivity/Toaster
9 | * time : 2021/11/13
10 | * desc : 通知服务代理对象
11 | */
12 | final class NotificationServiceProxy implements InvocationHandler {
13 |
14 | /** 被代理的对象 */
15 | private final Object mRealObject;
16 |
17 | public NotificationServiceProxy(Object realObject) {
18 | mRealObject = realObject;
19 | }
20 |
21 | @Override
22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
23 | switch (method.getName()) {
24 | case "enqueueToast":
25 | case "enqueueToastEx":
26 | case "cancelToast":
27 | // 将包名修改成系统包名,这样就可以绕过系统的拦截
28 | // 部分华为机将 enqueueToast 方法名修改成了 enqueueToastEx
29 | args[0] = "android";
30 | break;
31 | default:
32 | break;
33 | }
34 | // 使用动态代理
35 | return method.invoke(mRealObject, args);
36 | }
37 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/NotificationToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Application;
5 | import android.widget.Toast;
6 |
7 | import java.lang.reflect.Field;
8 | import java.lang.reflect.Method;
9 | import java.lang.reflect.Proxy;
10 |
11 | /**
12 | * author : Android 轮子哥
13 | * github : https://github.com/getActivity/Toaster
14 | * time : 2021/11/24
15 | * desc : 处理 Toast 关闭通知栏权限之后无法弹出的问题
16 | */
17 | public class NotificationToast extends SystemToast {
18 |
19 | /** 是否已经 Hook 了一次通知服务 */
20 | private static boolean sHookService;
21 |
22 | public NotificationToast(Application application) {
23 | super(application);
24 | }
25 |
26 | @Override
27 | public void show() {
28 | hookNotificationService();
29 | super.show();
30 | }
31 |
32 | @SuppressLint({"DiscouragedPrivateApi", "PrivateApi"})
33 | @SuppressWarnings({"JavaReflectionMemberAccess", "SoonBlockedPrivateApi"})
34 | private static void hookNotificationService() {
35 | if (sHookService) {
36 | return;
37 | }
38 | sHookService = true;
39 | try {
40 | // 获取到 Toast 中的 getService 静态方法
41 | Method getService = Toast.class.getDeclaredMethod("getService");
42 | getService.setAccessible(true);
43 | // 执行方法,会返回一个 INotificationManager$Stub$Proxy 类型的对象
44 | final Object iNotificationManager = getService.invoke(null);
45 | if (iNotificationManager == null) {
46 | return;
47 | }
48 | // 如果这个对象已经被动态代理过了,并且已经 Hook 过了,则不需要重复 Hook
49 | if (Proxy.isProxyClass(iNotificationManager.getClass()) &&
50 | Proxy.getInvocationHandler(iNotificationManager) instanceof NotificationServiceProxy) {
51 | return;
52 | }
53 | Object iNotificationManagerProxy = Proxy.newProxyInstance(
54 | Thread.currentThread().getContextClassLoader(),
55 | new Class[]{Class.forName("android.app.INotificationManager")},
56 | new NotificationServiceProxy(iNotificationManager));
57 | // 将原来的 INotificationManager$Stub$Proxy 替换掉
58 | Field sService = Toast.class.getDeclaredField("sService");
59 | sService.setAccessible(true);
60 | sService.set(null, iNotificationManagerProxy);
61 | } catch (Exception e) {
62 | e.printStackTrace();
63 | }
64 | }
65 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/SafeHandler.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 | import android.view.WindowManager;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/Toaster
10 | * time : 2018/12/06
11 | * desc : Toast 显示安全处理
12 | */
13 | @SuppressWarnings("deprecation")
14 | final class SafeHandler extends Handler {
15 |
16 | private final Handler mHandler;
17 |
18 | SafeHandler(Handler handler) {
19 | mHandler = handler;
20 | }
21 |
22 | @Override
23 | public void handleMessage(final Message msg) {
24 | // 捕获这个异常,避免程序崩溃
25 | try {
26 | // 目前发现在 Android 7.1 主线程被阻塞之后弹吐司会导致崩溃,可使用 Thread.sleep(5000) 进行复现
27 | // 查看源码得知 Google 已经在 Android 8.0 已经修复了此问题
28 | // 主线程阻塞之后 Toast 也会被阻塞,Toast 因为显示超时导致 Window Token 失效
29 | mHandler.handleMessage(msg);
30 | } catch (WindowManager.BadTokenException | IllegalStateException e) {
31 | // android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy is not valid; is your activity running?
32 | // java.lang.IllegalStateException:View android.widget.TextView has already been added to the window manager.
33 | e.printStackTrace();
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/SafeToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Application;
5 | import android.os.Build;
6 | import android.os.Handler;
7 | import android.widget.Toast;
8 |
9 | import java.lang.reflect.Field;
10 |
11 | /**
12 | * author : Android 轮子哥
13 | * github : https://github.com/getActivity/Toaster
14 | * time : 2018/12/06
15 | * desc : Toast 显示安全处理
16 | */
17 | @TargetApi(Build.VERSION_CODES.KITKAT)
18 | @SuppressWarnings("all")
19 | public class SafeToast extends NotificationToast {
20 |
21 | /** 是否已经 Hook 了一次 TN 内部类 */
22 | private boolean mHookTN;
23 |
24 | public SafeToast(Application application) {
25 | super(application);
26 | }
27 |
28 | @Override
29 | public void show() {
30 | hookToastTN();
31 | super.show();
32 | }
33 |
34 | private void hookToastTN() {
35 | if (mHookTN) {
36 | return;
37 | }
38 | mHookTN = true;
39 |
40 | try {
41 | // 获取 Toast.mTN 字段对象
42 | Field mTNField = Toast.class.getDeclaredField("mTN");
43 | mTNField.setAccessible(true);
44 | Object mTN = mTNField.get(this);
45 |
46 | // 获取 mTN 中的 mHandler 字段对象
47 | Field mHandlerField = mTNField.getType().getDeclaredField("mHandler");
48 | mHandlerField.setAccessible(true);
49 | Handler mHandler = (Handler) mHandlerField.get(mTN);
50 |
51 | // 如果这个对象已经被反射替换过了
52 | if (mHandler instanceof SafeHandler) {
53 | return;
54 | }
55 |
56 | // 偷梁换柱
57 | mHandlerField.set(mTN, new SafeHandler(mHandler));
58 |
59 | } catch (IllegalAccessException | NoSuchFieldException e) {
60 | // Android 9.0 上反射会出现报错
61 | // Accessing hidden field Landroid/widget/Toast;->mTN:Landroid/widget/Toast$TN;
62 | // java.lang.NoSuchFieldException: No field mTN in class Landroid/widget/Toast;
63 | e.printStackTrace();
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/SystemToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.app.Application;
4 | import android.view.View;
5 | import android.widget.TextView;
6 | import android.widget.Toast;
7 |
8 | import com.hjq.toast.config.IToast;
9 |
10 | /**
11 | * author : Android 轮子哥
12 | * github : https://github.com/getActivity/Toaster
13 | * time : 2018/11/03
14 | * desc : 系统 Toast
15 | */
16 | @SuppressWarnings("deprecation")
17 | public class SystemToast extends Toast implements IToast {
18 |
19 | /** 吐司消息 View */
20 | private TextView mMessageView;
21 |
22 | public SystemToast(Application application) {
23 | super(application);
24 | }
25 |
26 | @Override
27 | public void setView(View view) {
28 | super.setView(view);
29 | if (view == null) {
30 | mMessageView = null;
31 | return;
32 | }
33 | mMessageView = findMessageView(view);
34 | }
35 |
36 | @Override
37 | public void setText(CharSequence text) {
38 | super.setText(text);
39 | if (mMessageView == null) {
40 | return;
41 | }
42 | mMessageView.setText(text);
43 | }
44 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ToastImpl.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.Application;
6 | import android.content.Context;
7 | import android.graphics.PixelFormat;
8 | import android.os.Build;
9 | import android.os.Handler;
10 | import android.os.Looper;
11 | import android.view.View;
12 | import android.view.WindowManager;
13 | import android.view.accessibility.AccessibilityEvent;
14 | import android.view.accessibility.AccessibilityManager;
15 | import android.widget.Toast;
16 |
17 | /**
18 | * author : Android 轮子哥
19 | * github : https://github.com/getActivity/Toaster
20 | * time : 2018/11/02
21 | * desc : 自定义 Toast 实现类
22 | */
23 | final class ToastImpl {
24 |
25 | private static final String WINDOW_TITLE = "Toast";
26 |
27 | private static final Handler HANDLER = new Handler(Looper.getMainLooper());
28 |
29 | /** 当前的吐司对象 */
30 | private final CustomToast mToast;
31 |
32 | /** WindowManager 辅助类 */
33 | private WindowLifecycle mWindowLifecycle;
34 |
35 | /** 当前应用的包名 */
36 | private final String mPackageName;
37 |
38 | /** 当前是否已经显示 */
39 | private boolean mShow;
40 |
41 | /** 当前是否全局显示 */
42 | private boolean mGlobalShow;
43 |
44 | ToastImpl(Activity activity, CustomToast toast) {
45 | this((Context) activity, toast);
46 | mGlobalShow = false;
47 | mWindowLifecycle = new WindowLifecycle(activity);
48 | }
49 |
50 | ToastImpl(Application application, CustomToast toast) {
51 | this((Context) application, toast);
52 | mGlobalShow = true;
53 | mWindowLifecycle = new WindowLifecycle(application);
54 | }
55 |
56 | private ToastImpl(Context context, CustomToast toast) {
57 | mToast = toast;
58 | mPackageName = context.getPackageName();
59 | }
60 |
61 | boolean isShow() {
62 | return mShow;
63 | }
64 |
65 | void setShow(boolean show) {
66 | mShow = show;
67 | }
68 |
69 | /***
70 | * 显示吐司弹窗
71 | */
72 | void show() {
73 | if (isShow()) {
74 | return;
75 | }
76 | if (isMainThread()) {
77 | mShowRunnable.run();
78 | } else {
79 | HANDLER.removeCallbacks(mShowRunnable);
80 | HANDLER.post(mShowRunnable);
81 | }
82 | }
83 |
84 | /**
85 | * 取消吐司弹窗
86 | */
87 | void cancel() {
88 | if (!isShow()) {
89 | return;
90 | }
91 | HANDLER.removeCallbacks(mShowRunnable);
92 | if (isMainThread()) {
93 | mCancelRunnable.run();
94 | } else {
95 | HANDLER.removeCallbacks(mCancelRunnable);
96 | HANDLER.post(mCancelRunnable);
97 | }
98 | }
99 |
100 | /**
101 | * 判断当前是否在主线程
102 | */
103 | private boolean isMainThread() {
104 | return Looper.myLooper() == Looper.getMainLooper();
105 | }
106 |
107 | /**
108 | * 发送无障碍事件
109 | */
110 | @SuppressWarnings("deprecation")
111 | private void sendAccessibilityEvent(View view) {
112 | final Context context = view.getContext();
113 | AccessibilityManager accessibilityManager =
114 | (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
115 | if (!accessibilityManager.isEnabled()) {
116 | return;
117 | }
118 | int eventType = AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED;
119 | AccessibilityEvent event;
120 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
121 | event = new AccessibilityEvent();
122 | event.setEventType(eventType);
123 | } else {
124 | event = AccessibilityEvent.obtain(eventType);
125 | }
126 | event.setClassName(Toast.class.getName());
127 | event.setPackageName(context.getPackageName());
128 | view.dispatchPopulateAccessibilityEvent(event);
129 | // 将 Toast 视为通知,因为它们用于向用户宣布短暂的信息
130 | accessibilityManager.sendAccessibilityEvent(event);
131 | }
132 |
133 | private final Runnable mShowRunnable = new Runnable() {
134 |
135 | @SuppressLint("WrongConstant")
136 | @Override
137 | public void run() {
138 |
139 | WindowManager windowManager = mWindowLifecycle.getWindowManager();
140 | if (windowManager == null) {
141 | return;
142 | }
143 |
144 | final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
145 | params.height = WindowManager.LayoutParams.WRAP_CONTENT;
146 | params.width = WindowManager.LayoutParams.WRAP_CONTENT;
147 | params.format = PixelFormat.TRANSLUCENT;
148 | params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
149 | | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
150 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
151 | params.packageName = mPackageName;
152 | params.gravity = mToast.getGravity();
153 | params.x = mToast.getXOffset();
154 | params.y = mToast.getYOffset();
155 | params.verticalMargin = mToast.getVerticalMargin();
156 | params.horizontalMargin = mToast.getHorizontalMargin();
157 | params.windowAnimations = mToast.getAnimationsId();
158 | params.setTitle(WINDOW_TITLE);
159 |
160 | // 指定 WindowManager 忽略系统窗口可见性的影响
161 | // 例如下面这些的显示和隐藏都会影响当前 WindowManager 的显示(触发位置调整)
162 | // WindowInsets.Type.statusBars():状态栏
163 | // WindowInsets.Type.navigationBars():导航栏
164 | // WindowInsets.Type.ime():输入法(软键盘)
165 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
166 | params.setFitInsetsIgnoringVisibility(true);
167 | }
168 |
169 | // 如果是全局显示
170 | if (mGlobalShow) {
171 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
172 | params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
173 | // 在 type 等于 TYPE_APPLICATION_OVERLAY 的时候
174 | // 不能添加 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 标记
175 | // 否则会导致在 Android 13 上面会出现 Toast 布局被半透明化的效果
176 | // Github issue 地址:https://github.com/getActivity/Toaster/issues/108
177 | params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
178 | } else {
179 | params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
180 | }
181 | }
182 |
183 | try {
184 | windowManager.addView(mToast.getView(), params);
185 | // 添加一个移除吐司的任务
186 | HANDLER.postDelayed(() -> cancel(), mToast.getDuration() == Toast.LENGTH_LONG ?
187 | mToast.getLongDuration() : mToast.getShortDuration());
188 | // 注册生命周期管控
189 | mWindowLifecycle.register(ToastImpl.this);
190 | // 当前已经显示
191 | setShow(true);
192 | // 发送无障碍事件
193 | sendAccessibilityEvent(mToast.getView());
194 | } catch (IllegalStateException | WindowManager.BadTokenException e) {
195 | // 如果这个 View 对象被重复添加到 WindowManager 则会抛出异常
196 | // java.lang.IllegalStateException: View android.widget.TextView has already been added to the window manager.
197 | // 如果 WindowManager 绑定的 Activity 已经销毁,则会抛出异常
198 | // android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@ef1ccb6 is not valid; is your activity running?
199 | e.printStackTrace();
200 | }
201 | }
202 | };
203 |
204 | private final Runnable mCancelRunnable = new Runnable() {
205 |
206 | @Override
207 | public void run() {
208 |
209 | try {
210 | WindowManager windowManager = mWindowLifecycle.getWindowManager();
211 | if (windowManager == null) {
212 | return;
213 | }
214 |
215 | windowManager.removeViewImmediate(mToast.getView());
216 |
217 | } catch (IllegalArgumentException e) {
218 | // 如果当前 WindowManager 没有附加这个 View 则会抛出异常
219 | // java.lang.IllegalArgumentException: View=android.widget.TextView not attached to window manager
220 | e.printStackTrace();
221 | } finally {
222 | // 反注册生命周期管控
223 | mWindowLifecycle.unregister();
224 | // 当前没有显示
225 | setShow(false);
226 | }
227 | }
228 | };
229 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ToastLogInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.util.Log;
4 |
5 | import com.hjq.toast.config.IToastInterceptor;
6 |
7 | import java.lang.reflect.Modifier;
8 |
9 | /**
10 | * author : Android 轮子哥
11 | * github : https://github.com/getActivity/Toaster
12 | * time : 2020/11/04
13 | * desc : 自定义 Toast 拦截器(用于追踪 Toast 调用的位置)
14 | */
15 | public class ToastLogInterceptor implements IToastInterceptor {
16 |
17 | @Override
18 | public boolean intercept(ToastParams params) {
19 | printToast(params.text);
20 | return false;
21 | }
22 |
23 | protected void printToast(CharSequence text) {
24 | if (!isLogEnable()) {
25 | return;
26 | }
27 |
28 | // 获取调用的堆栈信息
29 | StackTraceElement[] stackTraces = new Throwable().getStackTrace();
30 | for (StackTraceElement stackTrace : stackTraces) {
31 | // 获取代码行数
32 | int lineNumber = stackTrace.getLineNumber();
33 | if (lineNumber <= 0) {
34 | continue;
35 | }
36 |
37 | // 获取类的全路径
38 | String className = stackTrace.getClassName();
39 | try {
40 | Class> clazz = Class.forName(className);
41 | if (!filterClass(clazz)) {
42 | printLog("(" + stackTrace.getFileName() + ":" + lineNumber + ") " + text.toString());
43 | // 跳出循环
44 | break;
45 | }
46 | } catch (ClassNotFoundException e) {
47 | e.printStackTrace();
48 | }
49 | }
50 | }
51 |
52 | protected boolean isLogEnable() {
53 | return Toaster.isDebugMode();
54 | }
55 |
56 | protected void printLog(String msg) {
57 | // 这里解释一下,为什么不用 Log.d,而用 Log.i,因为 Log.d 在魅族 16th 手机上面无法输出日志
58 | Log.i("Toaster", msg);
59 | }
60 |
61 | protected boolean filterClass(Class> clazz) {
62 | // 排查以下几种情况:
63 | // 1. 排除自身及其子类
64 | // 2. 排除 Toaster 类
65 | // 3. 排除接口类
66 | // 4. 排除抽象类
67 | return IToastInterceptor.class.isAssignableFrom(clazz) ||
68 | Toaster.class.equals(clazz) ||
69 | clazz.isInterface() ||
70 | Modifier.isAbstract(clazz.getModifiers());
71 | }
72 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ToastParams.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import com.hjq.toast.config.IToastInterceptor;
4 | import com.hjq.toast.config.IToastStrategy;
5 | import com.hjq.toast.config.IToastStyle;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/Toaster
10 | * time : 2022/10/31
11 | * desc : Toast 参数类
12 | */
13 | public class ToastParams {
14 |
15 | /** 显示的文本 */
16 | public CharSequence text;
17 |
18 | /**
19 | * Toast 显示时长,有两种值可选
20 | *
21 | * 短吐司:{@link android.widget.Toast#LENGTH_SHORT}
22 | * 长吐司:{@link android.widget.Toast#LENGTH_LONG}
23 | */
24 | public int duration = -1;
25 |
26 | /** 延迟显示时间 */
27 | public long delayMillis = 0;
28 |
29 | /** 是否跨页面展示(如果为 true 则优先用系统 Toast 实现) */
30 | public boolean crossPageShow;
31 |
32 | /** Toast 样式 */
33 | public IToastStyle> style;
34 |
35 | /** Toast 处理策略 */
36 | public IToastStrategy strategy;
37 |
38 | /** Toast 拦截器 */
39 | public IToastInterceptor interceptor;
40 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/ToastStrategy.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.AppOpsManager;
6 | import android.app.Application;
7 | import android.app.NotificationManager;
8 | import android.content.Context;
9 | import android.os.Build;
10 | import android.os.Build.VERSION;
11 | import android.os.Build.VERSION_CODES;
12 | import android.os.Handler;
13 | import android.os.Looper;
14 | import android.os.SystemClock;
15 | import android.provider.Settings;
16 | import android.widget.Toast;
17 | import com.hjq.toast.config.IToast;
18 | import com.hjq.toast.config.IToastStrategy;
19 | import com.hjq.toast.config.IToastStyle;
20 | import java.lang.ref.WeakReference;
21 | import java.lang.reflect.Field;
22 | import java.lang.reflect.InvocationTargetException;
23 | import java.lang.reflect.Method;
24 |
25 | /**
26 | * author : Android 轮子哥
27 | * github : https://github.com/getActivity/Toaster
28 | * time : 2018/11/12
29 | * desc : Toast 默认处理器
30 | * doc : https://developer.android.google.cn/reference/android/widget/Toast
31 | */
32 | public class ToastStrategy implements IToastStrategy {
33 |
34 | /**
35 | * 即显即示模式(默认)
36 | *
37 | * 在发起多次 Toast 的显示请求情况下,显示下一个 Toast 之前
38 | * 会先立即取消上一个 Toast,保证当前显示 Toast 消息是最新的
39 | */
40 | public static final int SHOW_STRATEGY_TYPE_IMMEDIATELY = 0;
41 |
42 | /**
43 | * 不丢消息模式
44 | *
45 | * 在发起多次 Toast 的显示请求情况下,等待上一个 Toast 显示 1 秒或者 1.5 秒后
46 | * 然后再显示下一个 Toast,不按照 Toast 的显示时长来,因为那样等待时间会很长
47 | * 这样既能保证用户能看到每一条 Toast 消息,又能保证用户不会等得太久,速战速决
48 | */
49 | public static final int SHOW_STRATEGY_TYPE_QUEUE = 1;
50 |
51 | /** Handler 对象 */
52 | private static final Handler HANDLER = new Handler(Looper.getMainLooper());
53 |
54 | /**
55 | * 默认延迟时间
56 | *
57 | * 延迟一段时间之后再执行,因为在没有通知栏权限的情况下,Toast 只能显示在当前 Activity 上面
58 | * 如果当前 Activity 在 showToast 之后立马进行 finish 了,那么这个时候 Toast 可能会显示不出来
59 | * 因为 Toast 会显示在销毁 Activity 界面上,而不会显示在新跳转的 Activity 上面
60 | */
61 | private static final int DEFAULT_DELAY_TIMEOUT = 200;
62 |
63 | /** 应用上下文 */
64 | private Application mApplication;
65 |
66 | /** Toast 对象 */
67 | private WeakReference mToastReference;
68 |
69 | /** 吐司显示策略 */
70 | private final int mShowStrategyType;
71 |
72 | /** 显示消息 Token */
73 | private final Object mShowMessageToken = new Object();
74 | /** 取消消息 Token */
75 | private final Object mCancelMessageToken = new Object();
76 |
77 | /** 上一个 Toast 显示的时间 */
78 | private volatile long mLastShowToastMillis;
79 |
80 | public ToastStrategy() {
81 | this(ToastStrategy.SHOW_STRATEGY_TYPE_IMMEDIATELY);
82 | }
83 |
84 | public ToastStrategy(int type) {
85 | mShowStrategyType = type;
86 | switch (mShowStrategyType) {
87 | case SHOW_STRATEGY_TYPE_IMMEDIATELY:
88 | case SHOW_STRATEGY_TYPE_QUEUE:
89 | break;
90 | default:
91 | throw new IllegalArgumentException("Please don't pass non-existent toast show strategy");
92 | }
93 | }
94 |
95 | @Override
96 | public void registerStrategy(Application application) {
97 | mApplication = application;
98 | }
99 |
100 | @Override
101 | public int computeShowDuration(CharSequence text) {
102 | return text.length() > 20 ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
103 | }
104 |
105 | @Override
106 | public IToast createToast(ToastParams params) {
107 | Activity foregroundActivity = getForegroundActivity();
108 | IToast toast;
109 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
110 | Settings.canDrawOverlays(mApplication)) {
111 | // 如果有悬浮窗权限,就开启全局的 Toast
112 | toast = new GlobalToast(mApplication);
113 | } else if (!params.crossPageShow && isActivityAvailable(foregroundActivity)) {
114 | // 如果没有悬浮窗权限,就开启一个依附于 Activity 的 Toast
115 | toast = new ActivityToast(foregroundActivity);
116 | } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
117 | // 处理 Android 7.1 上 Toast 在主线程被阻塞后会导致报错的问题
118 | toast = new SafeToast(mApplication);
119 | } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q &&
120 | !areNotificationsEnabled(mApplication)) {
121 | // 处理 Toast 关闭通知栏权限之后无法弹出的问题
122 | // 通过查看和对比 NotificationManagerService 的源码
123 | // 发现这个问题已经在 Android 10 版本上面修复了
124 | // 但是 Toast 只能在前台显示,没有通知栏权限后台 Toast 仍然无法显示
125 | // 并且 Android 10 刚好禁止了 Hook 通知服务
126 | // 已经有通知栏权限,不需要 Hook 系统通知服务也能正常显示系统 Toast
127 | toast = new NotificationToast(mApplication);
128 | } else {
129 | toast = new SystemToast(mApplication);
130 | }
131 | if (areSupportCustomToastStyle(toast) || !onlyShowSystemToastStyle()) {
132 | diyToastStyle(toast, params.style);
133 | }
134 | return toast;
135 | }
136 |
137 | @Override
138 | public void showToast(ToastParams params) {
139 | switch (mShowStrategyType) {
140 | case SHOW_STRATEGY_TYPE_IMMEDIATELY: {
141 | // 移除之前未显示的 Toast 消息
142 | cancelToast();
143 | long uptimeMillis = SystemClock.uptimeMillis() + params.delayMillis + (params.crossPageShow ? 0 : DEFAULT_DELAY_TIMEOUT);
144 | HANDLER.postAtTime(new ShowToastRunnable(params), mShowMessageToken, uptimeMillis);
145 | break;
146 | }
147 | case SHOW_STRATEGY_TYPE_QUEUE: {
148 | // 计算出这个 Toast 显示时间
149 | long showToastMillis = SystemClock.uptimeMillis() + params.delayMillis + (params.crossPageShow ? 0 : DEFAULT_DELAY_TIMEOUT);
150 | // 根据吐司的长短计算出等待时间
151 | long waitMillis = generateToastWaitMillis(params);
152 | // 如果当前显示的时间在上一个 Toast 的显示范围之内
153 | // 那么就重新计算 Toast 的显示时间
154 | if (showToastMillis < (mLastShowToastMillis + waitMillis)) {
155 | showToastMillis = mLastShowToastMillis + waitMillis;
156 | }
157 | HANDLER.postAtTime(new ShowToastRunnable(params), mShowMessageToken, showToastMillis);
158 | mLastShowToastMillis = showToastMillis;
159 | break;
160 | }
161 | default:
162 | break;
163 | }
164 | }
165 |
166 | @Override
167 | public void cancelToast() {
168 | long uptimeMillis = SystemClock.uptimeMillis();
169 | HANDLER.postAtTime(new CancelToastRunnable(), mCancelMessageToken, uptimeMillis);
170 | }
171 |
172 | /**
173 | * 是否支持设置自定义 Toast 样式
174 | */
175 | protected boolean areSupportCustomToastStyle(IToast toast) {
176 | // sdk 版本 >= 30 的情况下在后台显示自定义样式的 Toast 会被系统屏蔽,并且日志会输出以下警告:
177 | // Blocking custom toast from package com.xxx.xxx due to package not in the foreground
178 | // sdk 版本 < 30 的情况下 new Toast,并且不设置视图显示,系统会抛出以下异常:
179 | // java.lang.RuntimeException: This Toast was not created with Toast.makeText()
180 | return toast instanceof CustomToast || Build.VERSION.SDK_INT < Build.VERSION_CODES.R;
181 | }
182 |
183 | /**
184 | * 定制 Toast 的样式
185 | */
186 | protected void diyToastStyle(IToast toast, IToastStyle> style) {
187 | toast.setView(style.createView(mApplication));
188 | toast.setGravity(style.getGravity(), style.getXOffset(), style.getYOffset());
189 | toast.setMargin(style.getHorizontalMargin(), style.getVerticalMargin());
190 | }
191 |
192 | /**
193 | * 生成 Toast 等待时间
194 | */
195 | protected int generateToastWaitMillis(ToastParams params) {
196 | if (params.duration == Toast.LENGTH_SHORT) {
197 | return 1000;
198 | } else if (params.duration == Toast.LENGTH_LONG) {
199 | return 1500;
200 | }
201 | return 0;
202 | }
203 |
204 | /**
205 | * 显示任务
206 | */
207 | private class ShowToastRunnable implements Runnable {
208 |
209 | private final ToastParams mToastParams;
210 |
211 | private ShowToastRunnable(ToastParams params) {
212 | mToastParams = params;
213 | }
214 |
215 | @Override
216 | public void run() {
217 | IToast toast = null;
218 | if (mToastReference != null) {
219 | toast = mToastReference.get();
220 | }
221 |
222 | if (toast != null) {
223 | // 取消上一个 Toast 的显示,避免出现重叠的效果
224 | toast.cancel();
225 | }
226 | toast = createToast(mToastParams);
227 | // 为什么用 WeakReference,而不用 SoftReference ?
228 | // https://github.com/getActivity/Toaster/issues/79
229 | mToastReference = new WeakReference<>(toast);
230 | toast.setDuration(mToastParams.duration);
231 | toast.setText(mToastParams.text);
232 | toast.show();
233 | }
234 | }
235 |
236 | /**
237 | * 取消任务
238 | */
239 | private class CancelToastRunnable implements Runnable {
240 |
241 | @Override
242 | public void run() {
243 | IToast toast = null;
244 | if (mToastReference != null) {
245 | toast = mToastReference.get();
246 | }
247 |
248 | if (toast == null) {
249 | return;
250 | }
251 | toast.cancel();
252 | }
253 | }
254 |
255 | /**
256 | * 当前是否只能显示系统 Toast 样式
257 | */
258 | protected boolean onlyShowSystemToastStyle() {
259 | // Github issue 地址:https://github.com/getActivity/Toaster/issues/103
260 | // Toast.CHANGE_TEXT_TOASTS_IN_THE_SYSTEM = 147798919L
261 | return isChangeEnabledCompat(147798919L);
262 | }
263 |
264 | @SuppressLint("PrivateApi")
265 | protected boolean isChangeEnabledCompat(long changeId) {
266 | // 需要注意的是这个 api 是在 android 11 的时候出现的,反射前需要先判断好版本
267 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
268 | return false;
269 | }
270 | try {
271 | // 因为 Compatibility.isChangeEnabled() 普通应用根本调用不到,反射也不行
272 | // 通过 Toast.isSystemRenderedTextToast 也没有办法反射到
273 | // 最后发现反射 CompatChanges.isChangeEnabled 是可以的
274 | Class> clazz = Class.forName("android.app.compat.CompatChanges");
275 | Method method = clazz.getMethod("isChangeEnabled", long.class);
276 | method.setAccessible(true);
277 | return Boolean.parseBoolean(String.valueOf(method.invoke(null, changeId)));
278 | } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
279 | e.printStackTrace();
280 | return false;
281 | }
282 | }
283 |
284 | /**
285 | * 是否有通知栏权限
286 | */
287 | @SuppressWarnings("ConstantConditions")
288 | @SuppressLint("PrivateApi")
289 | protected boolean areNotificationsEnabled(Context context) {
290 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
291 | return context.getSystemService(NotificationManager.class).areNotificationsEnabled();
292 | }
293 |
294 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
295 | // 参考 Support 库中的方法: NotificationManagerCompat.from(context).areNotificationsEnabled()
296 | AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
297 | try {
298 | Method method = appOps.getClass().getMethod("checkOpNoThrow",
299 | Integer.TYPE, Integer.TYPE, String.class);
300 | Field field = appOps.getClass().getDeclaredField("OP_POST_NOTIFICATION");
301 | int value = (int) field.get(Integer.class);
302 | return ((int) method.invoke(appOps, value, context.getApplicationInfo().uid,
303 | context.getPackageName())) == AppOpsManager.MODE_ALLOWED;
304 | } catch (NoSuchMethodException | NoSuchFieldException | InvocationTargetException |
305 | IllegalAccessException | RuntimeException e) {
306 | e.printStackTrace();
307 | return true;
308 | }
309 | }
310 | return true;
311 | }
312 |
313 | /**
314 | * 获取前台的 Activity
315 | */
316 | protected Activity getForegroundActivity() {
317 | return ActivityStack.getInstance().getForegroundActivity();
318 | }
319 |
320 | /**
321 | * Activity 是否可用
322 | */
323 | protected boolean isActivityAvailable(Activity activity) {
324 | if (activity == null) {
325 | return false;
326 | }
327 |
328 | if (activity.isFinishing()) {
329 | return false;
330 | }
331 |
332 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
333 | return !activity.isDestroyed();
334 | }
335 | return true;
336 | }
337 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/Toaster.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.app.Application;
4 | import android.content.pm.ApplicationInfo;
5 | import android.content.res.Resources;
6 | import android.widget.Toast;
7 | import com.hjq.toast.config.IToastInterceptor;
8 | import com.hjq.toast.config.IToastStrategy;
9 | import com.hjq.toast.config.IToastStyle;
10 | import com.hjq.toast.style.BlackToastStyle;
11 | import com.hjq.toast.style.CustomToastStyle;
12 | import com.hjq.toast.style.LocationToastStyle;
13 | import com.hjq.toast.style.WhiteToastStyle;
14 |
15 | /**
16 | * author : Android 轮子哥
17 | * github : https://github.com/getActivity/Toaster
18 | * time : 2018/09/01
19 | * desc : Toast 框架(专治 Toast 疑难杂症)
20 | */
21 | @SuppressWarnings("unused")
22 | public final class Toaster {
23 |
24 | /** Application 对象 */
25 | private static Application sApplication;
26 |
27 | /** Toast 处理策略 */
28 | private static IToastStrategy sToastStrategy;
29 |
30 | /** Toast 样式 */
31 | private static IToastStyle> sToastStyle;
32 |
33 | /** Toast 拦截器(可空) */
34 | private static IToastInterceptor sToastInterceptor;
35 |
36 | /** 调试模式 */
37 | private static Boolean sDebugMode;
38 |
39 | /**
40 | * 不允许被外部实例化
41 | */
42 | private Toaster() {}
43 |
44 | /**
45 | * 初始化 Toast,需要在 Application.create 中初始化
46 | *
47 | * @param application 应用的上下文
48 | */
49 | public static void init(Application application) {
50 | init(application, sToastStyle);
51 | }
52 |
53 | public static void init(Application application, IToastStrategy strategy) {
54 | init(application, strategy, null);
55 | }
56 |
57 | public static void init(Application application, IToastStyle> style) {
58 | init(application, null, style);
59 | }
60 |
61 | /**
62 | * 初始化 Toast
63 | *
64 | * @param application 应用的上下文
65 | * @param strategy Toast 策略
66 | * @param style Toast 样式
67 | */
68 | public static void init(Application application, IToastStrategy strategy, IToastStyle> style) {
69 | // 如果当前已经初始化过了,就不要再重复初始化了
70 | if (isInit()) {
71 | return;
72 | }
73 |
74 | sApplication = application;
75 | ActivityStack.getInstance().register(application);
76 |
77 | // 初始化 Toast 策略
78 | if (strategy == null) {
79 | strategy = new ToastStrategy();
80 | }
81 | setStrategy(strategy);
82 |
83 | // 设置 Toast 样式
84 | if (style == null) {
85 | style = new BlackToastStyle();
86 | }
87 | setStyle(style);
88 | }
89 |
90 | /**
91 | * 判断当前框架是否已经初始化
92 | */
93 | public static boolean isInit() {
94 | return sApplication != null && sToastStrategy != null && sToastStyle != null;
95 | }
96 |
97 | /**
98 | * 延迟显示 Toast
99 | */
100 |
101 | public static void delayedShow(int id, long delayMillis) {
102 | delayedShow(stringIdToCharSequence(id), delayMillis);
103 | }
104 |
105 | public static void delayedShow(Object object, long delayMillis) {
106 | delayedShow(objectToCharSequence(object), delayMillis);
107 | }
108 |
109 | public static void delayedShow(CharSequence text, long delayMillis) {
110 | ToastParams params = new ToastParams();
111 | params.text = text;
112 | params.delayMillis = delayMillis;
113 | show(params);
114 | }
115 |
116 | /**
117 | * debug 模式下显示 Toast
118 | */
119 |
120 | public static void debugShow(int id) {
121 | debugShow(stringIdToCharSequence(id));
122 | }
123 |
124 | public static void debugShow(Object object) {
125 | debugShow(objectToCharSequence(object));
126 | }
127 |
128 | public static void debugShow(CharSequence text) {
129 | if (!isDebugMode()) {
130 | return;
131 | }
132 | ToastParams params = new ToastParams();
133 | params.text = text;
134 | show(params);
135 | }
136 |
137 | /**
138 | * 显示一个短 Toast
139 | */
140 |
141 | public static void showShort(int id) {
142 | showShort(stringIdToCharSequence(id));
143 | }
144 |
145 | public static void showShort(Object object) {
146 | showShort(objectToCharSequence(object));
147 | }
148 |
149 | public static void showShort(CharSequence text) {
150 | ToastParams params = new ToastParams();
151 | params.text = text;
152 | params.duration = Toast.LENGTH_SHORT;
153 | show(params);
154 | }
155 |
156 | /**
157 | * 显示一个长 Toast
158 | */
159 |
160 | public static void showLong(int id) {
161 | showLong(stringIdToCharSequence(id));
162 | }
163 |
164 | public static void showLong(Object object) {
165 | showLong(objectToCharSequence(object));
166 | }
167 |
168 | public static void showLong(CharSequence text) {
169 | ToastParams params = new ToastParams();
170 | params.text = text;
171 | params.duration = Toast.LENGTH_LONG;
172 | show(params);
173 | }
174 |
175 | /**
176 | * 显示 Toast
177 | */
178 |
179 | public static void show(int id) {
180 | show(stringIdToCharSequence(id));
181 | }
182 |
183 | public static void show(Object object) {
184 | show(objectToCharSequence(object));
185 | }
186 |
187 | public static void show(CharSequence text) {
188 | ToastParams params = new ToastParams();
189 | params.text = text;
190 | show(params);
191 | }
192 |
193 | public static void show(ToastParams params) {
194 | checkInitStatus();
195 |
196 | // 如果是空对象或者空文本就不显示
197 | if (params.text == null || params.text.length() == 0) {
198 | return;
199 | }
200 |
201 | if (params.strategy == null) {
202 | params.strategy = sToastStrategy;
203 | }
204 |
205 | if (params.interceptor == null) {
206 | if (sToastInterceptor == null) {
207 | sToastInterceptor = new ToastLogInterceptor();
208 | }
209 | params.interceptor = sToastInterceptor;
210 | }
211 |
212 | if (params.style == null) {
213 | params.style = sToastStyle;
214 | }
215 |
216 | if (params.interceptor.intercept(params)) {
217 | return;
218 | }
219 |
220 | if (params.duration == -1) {
221 | params.duration = params.strategy.computeShowDuration(params.text);
222 | }
223 |
224 | params.strategy.showToast(params);
225 | }
226 |
227 | /**
228 | * 取消吐司的显示
229 | */
230 | public static void cancel() {
231 | sToastStrategy.cancelToast();
232 | }
233 |
234 | /**
235 | * 设置吐司的位置
236 | *
237 | * @param gravity 重心
238 | */
239 | public static void setGravity(int gravity) {
240 | setGravity(gravity, 0, 0);
241 | }
242 |
243 | public static void setGravity(int gravity, int xOffset, int yOffset) {
244 | setGravity(gravity, xOffset, yOffset, 0, 0);
245 | }
246 |
247 | public static void setGravity(int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) {
248 | sToastStyle = new LocationToastStyle(sToastStyle, gravity, xOffset, yOffset, horizontalMargin, verticalMargin);
249 | }
250 |
251 | /**
252 | * 给当前 Toast 设置新的布局
253 | */
254 | public static void setView(int id) {
255 | if (id <= 0) {
256 | return;
257 | }
258 | if (sToastStyle == null) {
259 | return;
260 | }
261 | setStyle(new CustomToastStyle(id, sToastStyle.getGravity(),
262 | sToastStyle.getXOffset(), sToastStyle.getYOffset(),
263 | sToastStyle.getHorizontalMargin(), sToastStyle.getVerticalMargin()));
264 | }
265 |
266 | /**
267 | * 初始化全局的 Toast 样式
268 | *
269 | * @param style 样式实现类,框架已经实现两种不同的样式
270 | * 黑色样式:{@link BlackToastStyle}
271 | * 白色样式:{@link WhiteToastStyle}
272 | */
273 | public static void setStyle(IToastStyle> style) {
274 | if (style == null) {
275 | return;
276 | }
277 | sToastStyle = style;
278 | }
279 |
280 | public static IToastStyle> getStyle() {
281 | return sToastStyle;
282 | }
283 |
284 | /**
285 | * 设置 Toast 显示策略
286 | */
287 | public static void setStrategy(IToastStrategy strategy) {
288 | if (strategy == null) {
289 | return;
290 | }
291 | sToastStrategy = strategy;
292 | sToastStrategy.registerStrategy(sApplication);
293 | }
294 |
295 | public static IToastStrategy getStrategy() {
296 | return sToastStrategy;
297 | }
298 |
299 | /**
300 | * 设置 Toast 拦截器(可以根据显示的内容决定是否拦截这个Toast)
301 | * 场景:打印 Toast 内容日志、根据 Toast 内容是否包含敏感字来动态切换其他方式显示(这里可以使用我的另外一套框架 EasyWindow)
302 | */
303 | public static void setInterceptor(IToastInterceptor interceptor) {
304 | sToastInterceptor = interceptor;
305 | }
306 |
307 | public static IToastInterceptor getInterceptor() {
308 | return sToastInterceptor;
309 | }
310 |
311 | /**
312 | * 是否为调试模式
313 | */
314 | public static void setDebugMode(boolean debug) {
315 | sDebugMode = debug;
316 | }
317 |
318 | /**
319 | * 检查框架初始化状态,如果未初始化请先调用{@link Toaster#init(Application)}
320 | */
321 | private static void checkInitStatus() {
322 | // 框架当前还没有被初始化,必须要先调用 init 方法进行初始化
323 | if (sApplication == null) {
324 | throw new IllegalStateException("Toaster has not been initialized");
325 | }
326 | }
327 |
328 | static boolean isDebugMode() {
329 | if (sDebugMode == null) {
330 | checkInitStatus();
331 | sDebugMode = (sApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
332 | }
333 | return sDebugMode;
334 | }
335 |
336 | private static CharSequence stringIdToCharSequence(int id) {
337 | checkInitStatus();
338 | try {
339 | // 如果这是一个资源 id
340 | return sApplication.getResources().getText(id);
341 | } catch (Resources.NotFoundException ignored) {
342 | // 如果这是一个 int 整数
343 | return String.valueOf(id);
344 | }
345 | }
346 |
347 | private static CharSequence objectToCharSequence(Object object) {
348 | return object != null ? object.toString() : "null";
349 | }
350 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/WindowLifecycle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.Context;
6 | import android.os.Build;
7 | import android.os.Bundle;
8 | import android.view.WindowManager;
9 |
10 | /**
11 | * author : Android 轮子哥
12 | * github : https://github.com/getActivity/Toaster
13 | * time : 2018/11/06
14 | * desc : WindowManager 生命周期管控
15 | */
16 | final class WindowLifecycle implements Application.ActivityLifecycleCallbacks {
17 |
18 | /** 当前 Activity 对象 */
19 | private Activity mActivity;
20 |
21 | /** 当前 Application 对象 */
22 | private Application mApplication;
23 |
24 | /** 自定义 Toast 实现类 */
25 | private ToastImpl mToastImpl;
26 |
27 | WindowLifecycle(Activity activity) {
28 | mActivity = activity;
29 | }
30 |
31 | WindowLifecycle(Application application) {
32 | mApplication = application;
33 | }
34 |
35 | /**
36 | * 获取 WindowManager 对象
37 | */
38 | public WindowManager getWindowManager() {
39 | if (mActivity != null) {
40 |
41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mActivity.isDestroyed()) {
42 | return null;
43 | }
44 | return mActivity.getWindowManager();
45 |
46 | } else if (mApplication != null) {
47 |
48 | return (WindowManager) mApplication.getSystemService(Context.WINDOW_SERVICE);
49 | }
50 |
51 | return null;
52 | }
53 |
54 | /**
55 | * {@link Application.ActivityLifecycleCallbacks}
56 | */
57 |
58 | @Override
59 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) {}
60 |
61 | @Override
62 | public void onActivityStarted(Activity activity) {}
63 |
64 | @Override
65 | public void onActivityResumed(Activity activity) {}
66 |
67 | // A 跳转 B 页面的生命周期方法执行顺序:
68 | // onPause(A) ---> onCreate(B) ---> onStart(B) ---> onResume(B) ---> onStop(A) ---> onDestroyed(A)
69 |
70 | @Override
71 | public void onActivityPaused(Activity activity) {
72 | if (mActivity != activity) {
73 | return;
74 | }
75 |
76 | if (mToastImpl == null) {
77 | return;
78 | }
79 |
80 | // 不能放在 onStop 或者 onDestroyed 方法中,因为此时新的 Activity 已经创建完成,必须在这个新的 Activity 未创建之前关闭这个 WindowManager
81 | // 调用取消显示会直接导致新的 Activity 的 onCreate 调用显示吐司可能显示不出来的问题,又或者有时候会立马显示然后立马消失的那种效果
82 | mToastImpl.cancel();
83 | }
84 |
85 | @Override
86 | public void onActivityStopped(Activity activity) {}
87 |
88 | @Override
89 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
90 |
91 | @Override
92 | public void onActivityDestroyed(Activity activity) {
93 | if (mActivity != activity) {
94 | return;
95 | }
96 |
97 | if (mToastImpl != null) {
98 | mToastImpl.cancel();
99 | }
100 |
101 | unregister();
102 | mActivity = null;
103 | }
104 |
105 | /**
106 | * 注册
107 | */
108 | void register(ToastImpl impl) {
109 | mToastImpl = impl;
110 | if (mActivity == null) {
111 | return;
112 | }
113 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
114 | mActivity.registerActivityLifecycleCallbacks(this);
115 | } else {
116 | mActivity.getApplication().registerActivityLifecycleCallbacks(this);
117 | }
118 | }
119 |
120 | /**
121 | * 反注册
122 | */
123 | void unregister() {
124 | mToastImpl = null;
125 | if (mActivity == null) {
126 | return;
127 | }
128 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
129 | mActivity.unregisterActivityLifecycleCallbacks(this);
130 | } else {
131 | mActivity.getApplication().unregisterActivityLifecycleCallbacks(this);
132 | }
133 | }
134 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/config/IToast.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.config;
2 |
3 | import android.view.View;
4 | import android.widget.TextView;
5 |
6 | /**
7 | * author : Android 轮子哥
8 | * github : https://github.com/getActivity/Toaster
9 | * time : 2021/04/06
10 | * desc : Toast 接口
11 | */
12 | @SuppressWarnings("unused")
13 | public interface IToast {
14 |
15 | /**
16 | * 显示
17 | */
18 | void show();
19 |
20 | /**
21 | * 取消
22 | */
23 | void cancel();
24 |
25 | /**
26 | * 设置文本
27 | */
28 | void setText(int id);
29 |
30 | void setText(CharSequence text);
31 |
32 | /**
33 | * 设置布局
34 | */
35 | void setView(View view);
36 |
37 | /**
38 | * 获取布局
39 | */
40 | View getView();
41 |
42 | /**
43 | * 设置显示时长
44 | */
45 | void setDuration(int duration);
46 |
47 | /**
48 | * 获取显示时长
49 | */
50 | int getDuration();
51 |
52 | /**
53 | * 设置重心偏移
54 | */
55 | void setGravity(int gravity, int xOffset, int yOffset);
56 |
57 | /**
58 | * 获取显示重心
59 | */
60 | int getGravity();
61 |
62 | /**
63 | * 获取水平偏移
64 | */
65 | int getXOffset();
66 |
67 | /**
68 | * 获取垂直偏移
69 | */
70 | int getYOffset();
71 |
72 | /**
73 | * 设置屏幕间距
74 | */
75 | void setMargin(float horizontalMargin, float verticalMargin);
76 |
77 | /**
78 | * 设置水平间距
79 | */
80 | float getHorizontalMargin();
81 |
82 | /**
83 | * 设置垂直间距
84 | */
85 | float getVerticalMargin();
86 |
87 | /**
88 | * 智能获取用于显示消息的 TextView
89 | */
90 | default TextView findMessageView(View view) {
91 | if (view instanceof TextView) {
92 | if (view.getId() == View.NO_ID) {
93 | view.setId(android.R.id.message);
94 | } else if (view.getId() != android.R.id.message) {
95 | // 必须将 TextView 的 id 值设置成 android.R.id.message
96 | // 否则 Android 11 手机上在后台 toast.setText 的时候会出现报错
97 | // java.lang.RuntimeException: This Toast was not created with Toast.makeText()
98 | throw new IllegalArgumentException("You must set the ID value of TextView to android.R.id.message");
99 | }
100 | return (TextView) view;
101 | }
102 |
103 | View messageView = view.findViewById(android.R.id.message);
104 | if (messageView instanceof TextView) {
105 | return ((TextView) messageView);
106 | }
107 |
108 | // 如果设置的布局没有包含一个 TextView 则抛出异常,必须要包含一个 id 值为 message 的 TextView
109 | // xml 代码 android:id="@android:id/message"
110 | // java 代码 view.setId(android.R.id.message)
111 | throw new IllegalArgumentException("You must include a TextView with an ID value of message "
112 | + "(xml code: android:id=\"@android:id/message\", java code: view.setId(android.R.id.message))");
113 | }
114 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/config/IToastInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.config;
2 |
3 | import com.hjq.toast.ToastParams;
4 |
5 | /**
6 | * author : Android 轮子哥
7 | * github : https://github.com/getActivity/Toaster
8 | * time : 2019/05/19
9 | * desc : Toast 拦截器接口
10 | */
11 | public interface IToastInterceptor {
12 |
13 | /**
14 | * 根据显示的文本决定是否拦截该 Toast
15 | */
16 | boolean intercept(ToastParams params);
17 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/config/IToastStrategy.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.config;
2 |
3 | import android.app.Application;
4 | import com.hjq.toast.ToastParams;
5 |
6 | /**
7 | * author : Android 轮子哥
8 | * github : https://github.com/getActivity/Toaster
9 | * time : 2019/05/19
10 | * desc : Toast 处理策略
11 | */
12 | public interface IToastStrategy {
13 |
14 | /**
15 | * 注册策略
16 | */
17 | void registerStrategy(Application application);
18 |
19 | /**
20 | * 计算 Toast 显示时长
21 | */
22 | int computeShowDuration(CharSequence text);
23 |
24 | /**
25 | * 创建 Toast
26 | */
27 | IToast createToast(ToastParams params);
28 |
29 | /**
30 | * 显示 Toast
31 | */
32 | void showToast(ToastParams params);
33 |
34 | /**
35 | * 取消 Toast
36 | */
37 | void cancelToast();
38 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/config/IToastStyle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.config;
2 |
3 | import android.content.Context;
4 | import android.view.Gravity;
5 | import android.view.View;
6 |
7 | /**
8 | * author : Android 轮子哥
9 | * github : https://github.com/getActivity/Toaster
10 | * time : 2018/09/01
11 | * desc : 默认样式接口
12 | */
13 | public interface IToastStyle {
14 |
15 | /**
16 | * 创建 Toast 视图
17 | */
18 | V createView(Context context);
19 |
20 | /**
21 | * 获取 Toast 显示重心
22 | */
23 | default int getGravity() {
24 | return Gravity.CENTER;
25 | }
26 |
27 | /**
28 | * 获取 Toast 水平偏移
29 | */
30 | default int getXOffset() {
31 | return 0;
32 | }
33 |
34 | /**
35 | * 获取 Toast 垂直偏移
36 | */
37 | default int getYOffset() {
38 | return 0;
39 | }
40 |
41 | /**
42 | * 获取 Toast 水平间距
43 | */
44 | default float getHorizontalMargin() {
45 | return 0;
46 | }
47 |
48 | /**
49 | * 获取 Toast 垂直间距
50 | */
51 | default float getVerticalMargin() {
52 | return 0;
53 | }
54 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/style/BlackToastStyle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.style;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.graphics.drawable.GradientDrawable;
6 | import android.os.Build;
7 | import android.util.TypedValue;
8 | import android.view.Gravity;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.TextView;
12 | import com.hjq.toast.config.IToastStyle;
13 |
14 | /**
15 | * author : Android 轮子哥
16 | * github : https://github.com/getActivity/Toaster
17 | * time : 2018/09/01
18 | * desc : 默认黑色样式实现
19 | */
20 | @SuppressWarnings({"unused", "deprecation"})
21 | public class BlackToastStyle implements IToastStyle {
22 |
23 | @Override
24 | public View createView(Context context) {
25 | TextView textView = new TextView(context);
26 | textView.setId(android.R.id.message);
27 | textView.setGravity(getTextGravity(context));
28 | textView.setTextColor(getTextColor(context));
29 | textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getTextSize(context));
30 |
31 | int horizontalPadding = getHorizontalPadding(context);
32 | int verticalPadding = getVerticalPadding(context);
33 |
34 | // 适配布局反方向特性
35 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
36 | textView.setPaddingRelative(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
37 | } else {
38 | textView.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
39 | }
40 |
41 | textView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
42 |
43 | Drawable backgroundDrawable = getBackgroundDrawable(context);
44 | // 设置背景
45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
46 | textView.setBackground(backgroundDrawable);
47 | } else {
48 | textView.setBackgroundDrawable(backgroundDrawable);
49 | }
50 |
51 | // 设置 Z 轴阴影
52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
53 | textView.setZ(getTranslationZ(context));
54 | }
55 |
56 | return textView;
57 | }
58 |
59 | protected int getTextGravity(Context context) {
60 | return Gravity.CENTER;
61 | }
62 |
63 | protected int getTextColor(Context context) {
64 | return 0XEEFFFFFF;
65 | }
66 |
67 | protected float getTextSize(Context context) {
68 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
69 | 14, context.getResources().getDisplayMetrics());
70 | }
71 |
72 | protected int getHorizontalPadding(Context context) {
73 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
74 | 24, context.getResources().getDisplayMetrics());
75 | }
76 |
77 | protected int getVerticalPadding(Context context) {
78 | return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
79 | 16, context.getResources().getDisplayMetrics());
80 | }
81 |
82 | protected Drawable getBackgroundDrawable(Context context) {
83 | GradientDrawable drawable = new GradientDrawable();
84 | // 设置颜色
85 | drawable.setColor(0XB3000000);
86 | // 设置圆角
87 | drawable.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
88 | 10, context.getResources().getDisplayMetrics()));
89 | return drawable;
90 | }
91 |
92 | protected float getTranslationZ(Context context) {
93 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, context.getResources().getDisplayMetrics());
94 | }
95 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/style/CustomToastStyle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.style;
2 |
3 | import android.content.Context;
4 | import android.view.Gravity;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 |
8 | import com.hjq.toast.config.IToastStyle;
9 |
10 | /**
11 | * author : Android 轮子哥
12 | * github : https://github.com/getActivity/Toaster
13 | * time : 2021/03/09
14 | * desc : Toast 自定义 View 包装样式实现
15 | */
16 | public class CustomToastStyle implements IToastStyle {
17 |
18 | private final int mLayoutId;
19 | private final int mGravity;
20 | private final int mXOffset;
21 | private final int mYOffset;
22 | private final float mHorizontalMargin;
23 | private final float mVerticalMargin;
24 |
25 | public CustomToastStyle(int id) {
26 | this(id, Gravity.CENTER);
27 | }
28 |
29 | public CustomToastStyle(int id, int gravity) {
30 | this(id, gravity, 0, 0);
31 | }
32 |
33 | public CustomToastStyle(int id, int gravity, int xOffset, int yOffset) {
34 | this(id, gravity, xOffset, yOffset, 0f, 0f);
35 | }
36 |
37 | public CustomToastStyle(int id, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) {
38 | mLayoutId = id;
39 | mGravity = gravity;
40 | mXOffset = xOffset;
41 | mYOffset = yOffset;
42 | mHorizontalMargin = horizontalMargin;
43 | mVerticalMargin = verticalMargin;
44 | }
45 |
46 | @Override
47 | public View createView(Context context) {
48 | return LayoutInflater.from(context).inflate(mLayoutId, null);
49 | }
50 |
51 | @Override
52 | public int getGravity() {
53 | return mGravity;
54 | }
55 |
56 | @Override
57 | public int getXOffset() {
58 | return mXOffset;
59 | }
60 |
61 | @Override
62 | public int getYOffset() {
63 | return mYOffset;
64 | }
65 |
66 | @Override
67 | public float getHorizontalMargin() {
68 | return mHorizontalMargin;
69 | }
70 |
71 | @Override
72 | public float getVerticalMargin() {
73 | return mVerticalMargin;
74 | }
75 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/style/LocationToastStyle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.style;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | import com.hjq.toast.config.IToastStyle;
7 |
8 | /**
9 | * author : Android 轮子哥
10 | * github : https://github.com/getActivity/Toaster
11 | * time : 2021/03/09
12 | * desc : Toast 位置包装样式实现
13 | */
14 | @SuppressWarnings("unused")
15 | public class LocationToastStyle implements IToastStyle {
16 |
17 | private final IToastStyle> mStyle;
18 |
19 | private final int mGravity;
20 | private final int mXOffset;
21 | private final int mYOffset;
22 | private final float mHorizontalMargin;
23 | private final float mVerticalMargin;
24 |
25 | public LocationToastStyle(IToastStyle> style, int gravity) {
26 | this(style, gravity, 0, 0, 0, 0);
27 | }
28 |
29 | public LocationToastStyle(IToastStyle> style, int gravity, int xOffset, int yOffset, float horizontalMargin, float verticalMargin) {
30 | mStyle = style;
31 | mGravity = gravity;
32 | mXOffset = xOffset;
33 | mYOffset = yOffset;
34 | mHorizontalMargin = horizontalMargin;
35 | mVerticalMargin = verticalMargin;
36 | }
37 |
38 | @Override
39 | public View createView(Context context) {
40 | return mStyle.createView(context);
41 | }
42 |
43 | @Override
44 | public int getGravity() {
45 | return mGravity;
46 | }
47 |
48 | @Override
49 | public int getXOffset() {
50 | return mXOffset;
51 | }
52 |
53 | @Override
54 | public int getYOffset() {
55 | return mYOffset;
56 | }
57 |
58 | @Override
59 | public float getHorizontalMargin() {
60 | return mHorizontalMargin;
61 | }
62 |
63 | @Override
64 | public float getVerticalMargin() {
65 | return mVerticalMargin;
66 | }
67 | }
--------------------------------------------------------------------------------
/library/src/main/java/com/hjq/toast/style/WhiteToastStyle.java:
--------------------------------------------------------------------------------
1 | package com.hjq.toast.style;
2 |
3 | import android.content.Context;
4 | import android.graphics.drawable.Drawable;
5 | import android.graphics.drawable.GradientDrawable;
6 | import android.util.TypedValue;
7 |
8 | /**
9 | * author : Android 轮子哥
10 | * github : https://github.com/getActivity/Toaster
11 | * time : 2018/09/01
12 | * desc : 默认白色样式实现
13 | */
14 | public class WhiteToastStyle extends BlackToastStyle {
15 |
16 | @Override
17 | protected int getTextColor(Context context) {
18 | return 0XBB000000;
19 | }
20 |
21 | @Override
22 | protected Drawable getBackgroundDrawable(Context context) {
23 | GradientDrawable drawable = new GradientDrawable();
24 | // 设置颜色
25 | drawable.setColor(0XFFEAEAEA);
26 | // 设置圆角
27 | drawable.setCornerRadius(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
28 | 10, context.getResources().getDisplayMetrics()));
29 | return drawable;
30 | }
31 | }
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/logo.png
--------------------------------------------------------------------------------
/picture/en/demo_logcat_code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_logcat_code.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_activity.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_activity.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_layout_custom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_layout_custom.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_layout_error.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_layout_error.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_layout_info.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_layout_info.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_layout_success.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_layout_success.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_layout_warn.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_layout_warn.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_style_black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_style_black.jpg
--------------------------------------------------------------------------------
/picture/en/demo_toast_style_white.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/en/demo_toast_style_white.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_logcat_code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_logcat_code.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_activity.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_activity.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_layout_custom.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_layout_custom.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_layout_error.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_layout_error.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_layout_info.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_layout_info.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_layout_success.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_layout_success.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_layout_warn.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_layout_warn.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_style_black.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_style_black.jpg
--------------------------------------------------------------------------------
/picture/zh/demo_toast_style_white.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/demo_toast_style_white.jpg
--------------------------------------------------------------------------------
/picture/zh/download_demo_apk_qr_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/download_demo_apk_qr_code.png
--------------------------------------------------------------------------------
/picture/zh/help_doc_rename_vote.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/getActivity/Toaster/96044bfb45887238bc57d849e570d8326c78eb31/picture/zh/help_doc_rename_vote.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':library'
2 |
--------------------------------------------------------------------------------