使用Snackbar替换Toast

背景

Toast是Android平台较常用的基础提示控件,使用简单易用;但是,Toast是系统层面提供的,不依赖于前台页面,存在滥用的风险。为了规避这些风险,Google在Android系统版本的迭代过程中,不断进行了优化和限制。这些限制不可避免的影响到了正常的业务逻辑,在迭代过程中,我们遇到过以下几个问题:

  1. 设置中关闭某个App的【显示通知】开关,Toast不再弹出,极大的影响了用户体验。
  2. Toast在Android 7.1.2(API25)以下会发生BadTokenException异常,导致App崩溃。
  3. 自定义TYPE_TOAST类型的Window,在Android 7.1.1、7.1.2发生token null is not valid异常,导致App崩溃。

当然了,上面这些问题,多少有一些替代方案,比如以下这些方式:

image

经过对比和参考美团实践方案,最终采用Snackbar对Toast进行替换。

使用Snackbar存在的一些问题

  1. Snackbar弹出的时候,被Dialog,PopupWindow等控件遮住。
  2. Snackbar无法进行跨页面展示,这是Snackbar实现原理决定的。
  3. Snackbar无法自定义布局、动画等

解决方案

问题一:

针对Snackbar弹出的时候,被Dialog,PopupWindow等控件遮住的问题,原因在于Snackbar依赖于View,当把Activity布局的View传给Snackbar做为Snackbar展示依赖的父View时,后面再弹Dialog,PopupWindow等控件,Snackbar就会被控件遮挡。正确的做法是直接把PopupWindow和Dialog所依赖的View传给Snackbar。那么我们定制化的Snackbar不仅支持传递这个View,也支持直接传递PopupWindow和Dialog的实例

问题二:

跨页面存在两种情况:

  1. Snackbar#show → startActivity
  2. Snackbar#show → finish

这两种情况都是在弹出Snackbar之后所依赖的Activity不可见或者关闭导致无法正常显示。所以将消息缓存起来,后置到下一个可见Activity进行处理,通过 application.registerActivityLifecycleCallbacks 进行页面onStart监听实现

问题三:

系统的Snackbar不支持自定义扩展,所以参考Snackbar的源码,进行了按需定制。

如何使用

#正常单页面使用
SnackbarUtils#showToast

#针对自定义View容器(会相对于View容器大小居中对齐,最好使用FrameLayout)
SnackbarUtils#showToastForView

#针对跨页使用
SnackbarUtils#showToastForJump

#针对Dialog使用
SnackbarUtils#showToastForDialog

简而言之:请在合适的时候、合适的场景、使用合适的API

当前版本兼容

  • toast()#showToast ==>底层替换成Snackbar的显示
  • 对于传入context是Application的调用,还是使用Toast进行展示
  • ToastUtils#showToast的使用全部替换成SnackbarUtils#showToast
  • 对于跨页面的Toast替换成SnackbarUtils#showToastForJump

注意:如继续使用ToastUtils中相关API,还是会老的Toast进行展示

不支持的情况(请使用Toast)

  • 子线程异步执行→跳页面→子线程执行完毕弹Toast (有此种情况也是写的有问题)
  • 拿不到Activity的Context
  • PopupWindow不支持使用(暂未发现使用场景)
    • 获取的父容器是getContentView(),在低版本中如不是FrameLayout,会无法找到合适添加Snackbar的容器,从而导致NPE
    • 如contentView是FrameLayout,Snackbar的显示极度受contentView的大小限制

你可能感兴趣的:(使用Snackbar替换Toast)