此文章是我在简书的文章,自行搬到博客园.简书地址:SweetTips: 快意灵动的Android提示库!
源码及所在DEMO已上传至GitHub:SweetTips,欢迎大家提Bug,喜欢的话记得Star或Fork下哈!
上面的问题也可以这样问:有哪些常见的需求,Android原生Toast及Design包中的Snackbar实现起来相对繁琐?Toast:
Snackbar:
很显然,可以解决上面列举的那些很常见的小问题;
截图:
自定义Toast:SweetToast + 自定义Snackbar:SweetSnackbar + SnackbarUtils:SweetSnackbar的工具类
SweetToast:
SweetSnackbar:
SweetTips.java
另外,为了这个提示库,也花了不少时间收集了一些常用的颜色,保存在Constant.java中,可作为一个通用的工具类适用于不同项目,喜欢的同学尽管拿走.
SweetToast是通过WindowManager向屏幕添加View来展示提示信息:
params.type = WindowManager.LayoutParams.TYPE_TOAST;
在Manifest.xml中已经声明过权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
在SDK>=23(Android 6)的系统中,用户需要手动允许当前App使用这个权限,才可以正常显示!
1 /**
2 * 自定义Toast
3 *
4 * 作者:幻海流心
5 * GitHub:https://github.com/HuanHaiLiuXin
6 * 邮箱:wall0920@163.com
7 * 2016/12/13
8 */
9
10 public final class SweetToast {
11 public static final int LENGTH_SHORT = 0;
12 public static final int LENGTH_LONG = 1;
13 public static final long SHORT_DELAY = 2000; // 2 seconds
14 public static final long LONG_DELAY = 3500; // 3.5 seconds
15 //SweetToast默认背景色
16 private static int mBackgroundColor = 0XE8484848;
17 //
18 private View mContentView = null; //内容区域View
19 private SweetToastConfiguration mConfiguration = null;
20 private WindowManager mWindowManager = null;
21 private boolean showing = false; //是否在展示中
22 private boolean showEnabled = true; //是否允许展示
23 private boolean hideEnabled = true; //是否允许移除
24 private boolean stateChangeEnabled = true; //是否允许改变展示状态
25
26 public static SweetToast makeText(Context context, CharSequence text){
27 return makeText(context, text, LENGTH_SHORT);
28 }
29 public static SweetToast makeText(View mContentView){
30 return makeText(mContentView, LENGTH_SHORT);
31 }
32 public static SweetToast makeText(Context context, CharSequence text, int duration) {
33 try {
34 LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
35 View v = inflate.inflate(R.layout.transient_notification, null);
36 TextView tv = (TextView)v.findViewById(R.id.message);
37 tv.setText(text);
38 SweetToast sweetToast = new SweetToast();
39 sweetToast.mContentView = v;
40 sweetToast.mContentView.setBackgroundDrawable(getBackgroundDrawable(sweetToast, mBackgroundColor));
41 initConfiguration(sweetToast,duration);
42 return sweetToast;
43 }catch (Exception e){
44 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":69");
45 }
46 return null;
47 }
48 public static SweetToast makeText(View mContentView, int duration){
49 SweetToast sweetToast = new SweetToast();
50 sweetToast.mContentView = mContentView;
51 initConfiguration(sweetToast,duration);
52 return sweetToast;
53 }
54 private static void initConfiguration(SweetToast sweetToast,int duration){
55 try {
56 if(duration < 0){
57 throw new RuntimeException("显示时长必须>=0!");
58 }
59 //1:初始化mWindowManager
60 sweetToast.mWindowManager = (WindowManager) sweetToast.getContentView().getContext().getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
61 //2:初始化mConfiguration
62 SweetToastConfiguration mConfiguration = new SweetToastConfiguration();
63 //2.1:设置显示时间
64 mConfiguration.setDuration(duration);
65 //2.2:设置WindowManager.LayoutParams属性
66 WindowManager.LayoutParams params = new WindowManager.LayoutParams();
67 final Configuration config = sweetToast.getContentView().getContext().getResources().getConfiguration();
68 final int gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
69 params.gravity = gravity;
70 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
71 params.horizontalWeight = 1.0f;
72 }
73 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
74 params.verticalWeight = 1.0f;
75 }
76 params.x = 0;
77 params.y = sweetToast.getContentView().getContext().getResources().getDimensionPixelSize(R.dimen.toast_y_offset);
78 params.verticalMargin = 0.0f;
79 params.horizontalMargin = 0.0f;
80 params.height = WindowManager.LayoutParams.WRAP_CONTENT;
81 params.width = WindowManager.LayoutParams.WRAP_CONTENT;
82 params.format = PixelFormat.TRANSLUCENT;
83 params.windowAnimations = R.style.Anim_SweetToast;
84 //在小米5S上实验,前两种type均会报错
85 params.type = WindowManager.LayoutParams.TYPE_TOAST;
86 // params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
87 // params.type = WindowManager.LayoutParams.TYPE_PHONE;
88 params.setTitle("Toast");
89 params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
90 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
91 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
92 mConfiguration.setParams(params);
93 sweetToast.setConfiguration(mConfiguration);
94 }catch (Exception e){
95 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":120");
96 }
97 }
98 /**
99 * 根据指定的背景色,获得mToastView的背景drawable实例
100 * @param backgroundColor
101 * @return
102 */
103 private static ShapeDrawable getBackgroundDrawable(SweetToast sweetToast, @ColorInt int backgroundColor){
104 try {
105 ShapeDrawable shapeDrawable = new ShapeDrawable();
106 DrawableCompat.setTint(shapeDrawable,backgroundColor);
107 //获取当前设备的屏幕尺寸
108 //实验发现不同的设备上面,Toast内容区域的padding值并不相同,根据屏幕的宽度分别进行处理,尽量接近设备原生Toast的体验
109 int widthPixels = sweetToast.getContentView().getResources().getDisplayMetrics().widthPixels;
110 int heightPixels = sweetToast.getContentView().getResources().getDisplayMetrics().heightPixels;
111 float density = sweetToast.getContentView().getResources().getDisplayMetrics().density;
112 if(widthPixels >= 1070){
113 //例如小米5S:1920 x 1080
114 shapeDrawable.setPadding((int)(density*13),(int)(density*12),(int)(density*13),(int)(density*12));
115 }else {
116 //例如红米2:1280x720
117 shapeDrawable.setPadding((int)(density*14),(int)(density*13),(int)(density*14),(int)(density*13));
118 }
119 float radius = density*8;
120 float[] outerRadii = new float[]{radius,radius,radius,radius,radius,radius,radius,radius};
121 int width = sweetToast.getContentView().getWidth();
122 int height = sweetToast.getContentView().getHeight();
123 RectF rectF = new RectF(1,1,width-1,height-1);
124 RoundRectShape roundRectShape = new RoundRectShape(outerRadii,rectF,null);
125 shapeDrawable.setShape(roundRectShape);
126 DrawableCompat.setTint(shapeDrawable,backgroundColor);
127 return shapeDrawable;
128 }catch (Exception e){
129 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":154");
130 }
131 return null;
132 }
133 /**
134 * 自定义SweetToast实例的入场出场动画
135 * @param windowAnimations
136 * @return
137 */
138 public SweetToast setWindowAnimations(@StyleRes int windowAnimations){
139 mConfiguration.getParams().windowAnimations = windowAnimations;
140 return this;
141 }
142 public SweetToast setGravity(int gravity, int xOffset, int yOffset) {
143 mConfiguration.getParams().gravity = gravity;
144 mConfiguration.getParams().x = xOffset;
145 mConfiguration.getParams().y = yOffset;
146 return this;
147 }
148 public SweetToast setMargin(float horizontalMargin, float verticalMargin) {
149 mConfiguration.getParams().horizontalMargin = horizontalMargin;
150 mConfiguration.getParams().verticalMargin = verticalMargin;
151 return this;
152 }
153 /**
154 * 向mContentView中添加View
155 *
156 * @param view
157 * @param index
158 * @return
159 */
160 public SweetToast addView(View view, int index) {
161 if(mContentView != null && mContentView instanceof ViewGroup){
162 ((ViewGroup)mContentView).addView(view,index);
163 }
164 return this;
165 }
166 /**
167 * 设置SweetToast实例中TextView的文字颜色
168 *
169 * @param messageColor
170 * @return
171 */
172 public SweetToast messageColor(@ColorInt int messageColor){
173 if(mContentView !=null && mContentView.findViewById(R.id.message) != null && mContentView.findViewById(R.id.message) instanceof TextView){
174 TextView textView = ((TextView) mContentView.findViewById(R.id.message));
175 textView.setTextColor(messageColor);
176 }
177 return this;
178 }
179 /**
180 * 设置SweetToast实例的背景颜色
181 *
182 * @param backgroundColor
183 * @return
184 */
185 public SweetToast backgroundColor(@ColorInt int backgroundColor){
186 if(mContentView!=null){
187 mContentView.setBackgroundDrawable(getBackgroundDrawable(this, backgroundColor));
188 }
189 return this;
190 }
191 /**
192 * 设置SweetToast实例的背景资源
193 *
194 * @param background
195 * @return
196 */
197 public SweetToast backgroundResource(@DrawableRes int background){
198 if(mContentView!=null){
199 mContentView.setBackgroundResource(background);
200 }
201 return this;
202 }
203 /**
204 * 设置SweetToast实例的文字颜色及背景颜色
205 *
206 * @param messageColor
207 * @param backgroundColor
208 * @return
209 */
210 public SweetToast colors(@ColorInt int messageColor, @ColorInt int backgroundColor) {
211 messageColor(messageColor);
212 backgroundColor(backgroundColor);
213 return this;
214 }
215 /**
216 * 设置SweetToast实例的文字颜色及背景资源
217 *
218 * @param messageColor
219 * @param background
220 * @return
221 */
222 public SweetToast textColorAndBackground(@ColorInt int messageColor, @DrawableRes int background) {
223 messageColor(messageColor);
224 backgroundResource(background);
225 return this;
226 }
227
228 /**
229 * 设置SweetToast实例的宽高
230 * 很有用的功能,参考了简书上的文章:http://www.jianshu.com/p/491b17281c0a
231 * @param width SweetToast实例的宽度,单位是pix
232 * @param height SweetToast实例的高度,单位是pix
233 * @return
234 */
235 public SweetToast size(int width, int height){
236 if(mContentView!=null && mContentView instanceof LinearLayout){
237 mContentView.setMinimumWidth(width);
238 mContentView.setMinimumHeight(height);
239 ((LinearLayout)mContentView).setGravity(Gravity.CENTER);
240 try {
241 TextView textView = ((TextView) mContentView.findViewById(R.id.message));
242 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) textView.getLayoutParams();
243 params.width = LinearLayout.LayoutParams.MATCH_PARENT;
244 params.height = LinearLayout.LayoutParams.MATCH_PARENT;
245 textView.setLayoutParams(params);
246 textView.setGravity(Gravity.CENTER);
247 }catch (Exception e){
248 Log.e("幻海流心","e:"+e.getLocalizedMessage());
249 }
250 }
251 return this;
252 }
253
254 /**
255 * 设置SweetToast实例的显示位置:左上
256 * @return
257 */
258 public SweetToast leftTop(){
259 return setGravity(Gravity.LEFT|Gravity.TOP,0,0);
260 }
261 /**
262 * 设置SweetToast实例的显示位置:右上
263 * @return
264 */
265 public SweetToast rightTop(){
266 return setGravity(Gravity.RIGHT|Gravity.TOP,0,0);
267 }
268 /**
269 * 设置SweetToast实例的显示位置:左下
270 * @return
271 */
272 public SweetToast leftBottom(){
273 return setGravity(Gravity.LEFT|Gravity.BOTTOM,0,0);
274 }
275 /**
276 * 设置SweetToast实例的显示位置:右下
277 * @return
278 */
279 public SweetToast rightBottom(){
280 return setGravity(Gravity.RIGHT|Gravity.BOTTOM,0,0);
281 }
282 /**
283 * 设置SweetToast实例的显示位置:上中
284 * @return
285 */
286 public SweetToast topCenter(){
287 return setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL,0,0);
288 }
289 /**
290 * 设置SweetToast实例的显示位置:下中
291 * @return
292 */
293 public SweetToast bottomCenter(){
294 return setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL,0,0);
295 }
296 /**
297 * 设置SweetToast实例的显示位置:左中
298 * @return
299 */
300 public SweetToast leftCenter(){
301 return setGravity(Gravity.LEFT|Gravity.CENTER_VERTICAL,0,0);
302 }
303 /**
304 * 设置SweetToast实例的显示位置:右中
305 * @return
306 */
307 public SweetToast rightCenter(){
308 return setGravity(Gravity.RIGHT|Gravity.CENTER_VERTICAL,0,0);
309 }
310 /**
311 * 设置SweetToast实例的显示位置:正中
312 * @return
313 */
314 public SweetToast center(){
315 return setGravity(Gravity.CENTER,0,0);
316 }
317 /**
318 * 将SweetToast实例显示在指定View的顶部
319 * @param targetView 指定View
320 * @param statusHeight 状态栏显示情况下,状态栏的高度
321 * @return
322 */
323 public SweetToast layoutAbove(View targetView, int statusHeight){
324 if(mContentView!=null){
325 int[] locations = new int[2];
326 targetView.getLocationOnScreen(locations);
327 //必须保证指定View的顶部可见
328 int screenHeight = ScreenUtil.getScreenHeight(mContentView.getContext());
329 if(locations[1] > statusHeight&&locations[1]<screenHeight){
330 setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL,0,screenHeight - locations[1]);
331 }
332 }
333 return this;
334 }
335 /**
336 * 将SweetToast实例显示在指定View的底部
337 * @param targetView
338 * @param statusHeight
339 * @return
340 */
341 public SweetToast layoutBellow(View targetView, int statusHeight){
342 if(mContentView!=null){
343 int[] locations = new int[2];
344 targetView.getLocationOnScreen(locations);
345 //必须保证指定View的底部可见
346 int screenHeight = ScreenUtil.getScreenHeight(mContentView.getContext());
347 if(locations[1]+targetView.getHeight() > statusHeight&&locations[1]+targetView.getHeight()<screenHeight){
348 setGravity(Gravity.TOP|Gravity.CENTER_HORIZONTAL,0,locations[1]+targetView.getHeight()-statusHeight);
349 }
350 }
351 return this;
352 }
353
354
355 /********************************************** SweetToast显示及移除 **********************************************/
356 Handler mHandler = new Handler();
357 Runnable mHide = new Runnable() {
358 @Override
359 public void run() {
360 handleHide();
361 }
362 };
363 protected void handleHide() {
364 if(this != null && mContentView != null){
365 if(stateChangeEnabled){
366 if(hideEnabled){
367 if(showing){
368 mWindowManager.removeView(mContentView);
369 }
370 showing = false;
371 mContentView = null;
372 }else{
373 }
374 }
375 }
376 }
377 protected void handleShow() {
378 if(mContentView != null){
379 if(stateChangeEnabled){
380 if(showEnabled){
381 try {
382 mWindowManager.addView(mContentView,mConfiguration.getParams());
383 long delay = (mConfiguration.getDuration() == LENGTH_LONG || mConfiguration.getDuration() == Toast.LENGTH_LONG) ? LONG_DELAY : ((mConfiguration.getDuration() == LENGTH_SHORT || mConfiguration.getDuration() == Toast.LENGTH_SHORT)? SHORT_DELAY : mConfiguration.getDuration());
384 mHandler.postDelayed(mHide,delay);
385 showing = true;
386 }catch (Exception e){
387 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":213");
388 }
389 }
390 }
391 }
392 }
393
394 /**
395 * 保持当前实例的显示状态:不允许向Window中添加或者移除View
396 */
397 protected void removeCallbacks(){
398 stateChangeEnabled = false;
399 }
400
401 /**
402 * 设置是否允许展示当前实例
403 * @param showEnabled
404 */
405 public void setShowEnabled(boolean showEnabled) {
406 this.showEnabled = showEnabled;
407 }
408
409 /**
410 * 设置是否允许移除当前实例中的View
411 * @param hideEnabled
412 */
413 public void setHideEnabled(boolean hideEnabled) {
414 this.hideEnabled = hideEnabled;
415 }
416
417 /**
418 * 设置是否允许改变当前实例的展示状态
419 * @param stateChangeEnabled
420 */
421 public void setStateChangeEnabled(boolean stateChangeEnabled) {
422 this.stateChangeEnabled = stateChangeEnabled;
423 }
424
425 /**
426 * 将当前实例添加到队列{@link SweetToastManager#queue}中,若队列为空,则加入队列后直接进行展示
427 */
428 public void show(){
429 try {
430 if (Build.VERSION.SDK_INT >= 23) {
431 //Android6.0以上,需要动态声明权限
432 if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
433 //用户还未允许该权限
434 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
435 mContentView.getContext().startActivity(intent);
436 return;
437 } else if(mContentView!=null) {
438 //用户已经允许该权限
439 SweetToastManager.show(this);
440 }
441 } else {
442 //Android6.0以下,不用动态声明权限
443 if (mContentView!=null) {
444 SweetToastManager.show(this);
445 }
446 }
447 // SweetToastManager.show(this);
448 }catch (Exception e){
449 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":232");
450 }
451 }
452 /**
453 * 利用队列{@link SweetToastManager#queue}中正在展示的SweetToast实例,继续展示当前实例的内容
454 */
455 public void showByPrevious(){
456 try {
457 if (Build.VERSION.SDK_INT >= 23) {
458 //Android6.0以上,需要动态声明权限
459 if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
460 //用户还未允许该权限
461 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
462 mContentView.getContext().startActivity(intent);
463 return;
464 } else if(mContentView!=null) {
465 //用户已经允许该权限
466 SweetToastManager.showByPrevious(this);
467 }
468 } else {
469 //Android6.0以下,不用动态声明权限
470 if (mContentView!=null) {
471 SweetToastManager.showByPrevious(this);
472 }
473 }
474 // SweetToastManager.showByPrevious(this);
475 }catch (Exception e){
476 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":290");
477 }
478 }
479 /**
480 * 清空队列{@link SweetToastManager#queue}中已经存在的SweetToast实例,直接展示当前实例的内容
481 */
482 public void showImmediate(){
483 try {
484 if (Build.VERSION.SDK_INT >= 23) {
485 //Android6.0以上,需要动态声明权限
486 if(mContentView!=null && !Settings.canDrawOverlays(mContentView.getContext().getApplicationContext())) {
487 //用户还未允许该权限
488 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
489 mContentView.getContext().startActivity(intent);
490 return;
491 } else if(mContentView!=null) {
492 //用户已经允许该权限
493 SweetToastManager.showImmediate(this);
494 }
495 } else {
496 //Android6.0以下,不用动态声明权限
497 if (mContentView!=null) {
498 SweetToastManager.showImmediate(this);
499 }
500 }
501 // SweetToastManager.showImmediate(this);
502 }catch (Exception e){
503 Log.e("幻海流心","e:"+e.getLocalizedMessage()+":252");
504 }
505 }
506 /**
507 * 移除当前SweetToast并将mContentView置空
508 */
509 public void hide() {
510 mHandler.post(mHide);
511 }
512 /********************************************** SweetToast显示及移除 **********************************************/
513
514 //Setter&Getter
515 public View getContentView() {
516 return mContentView;
517 }
518 public void setContentView(View mContentView) {
519 this.mContentView = mContentView;
520 }
521 public SweetToastConfiguration getConfiguration() {
522 return mConfiguration;
523 }
524 public void setConfiguration(SweetToastConfiguration mConfiguration) {
525 this.mConfiguration = mConfiguration;
526 }
527 public WindowManager getWindowManager() {
528 return mWindowManager;
529 }
530 public void setWindowManager(WindowManager mWindowManager) {
531 this.mWindowManager = mWindowManager;
532 }
533 public boolean isShowing() {
534 return showing;
535 }
536 public void setShowing(boolean showing) {
537 this.showing = showing;
538 }
539 }
源码及所在DEMO已上传至GitHub:SweetTips,欢迎大家提Bug,喜欢的话记得Star或Fork下哈!
That‘s all !
原文:http://www.cnblogs.com/HuanHaiLiuXin/p/6278287.html