toast 工具 - 取消连续显示
Toast 是弹出消息的,我们最他讨厌 toast 就是 toast 的消息只能一个个弹出来,每一个 toast 显示的最短时间都是 4秒,不停的一个接一个的 toast 弹出显示是及其糟糕的体验,尤其是我深恶痛绝的,这个问题我们继续解决。
胆码上很简单,我们只要始终使用 toast 对象来弹出消息就行,至于我们如何使用这唯一的 toast 对象,静态 toast 单例,或是统一工具入口都可以,下面我用 kotlin 写的,没几行代码
- toast 组件
class ToastComponent {
lateinit var toast: Toast
companion object {
var TAG: String = ToastComponent::class.qualifiedName.toString()
var DEFAULT_MESSAGE: String = "ToastHelper_default_message"
var TIME_SHORT: Int = Toast.LENGTH_SHORT
var TIME_LONG: Int = Toast.LENGTH_LONG
var instance: ToastComponent = ToastComponent()
fun init(application: Application) {
if (application == null) {
Log.d(TAG, "初始化失败,application = null")
return
}
instance.toast = Toast.makeText(application, DEFAULT_MESSAGE, TIME_SHORT)
}
}
fun show(info: String?, time: Int = TIME_SHORT) {
toast?.setText(info)
toast?.duration = time
toast?.show()
}
}
- toast 组件使用
1. 先在 Application 对象中初始化 toast 组件,目的是把 toast 和 applition 全局上下文关联
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化 toast 组件
ToastComponent.init(this)
}
}
2. 然后我们通过 toast 组件提供的入口显示消息
ToastComponent.instance.show(toastMessage)
项目地址:
toast 源码
有时间的话我们来简单趴趴 toast 的实现
- toast 的成员成员变量
public class Toast {
final Context mContext;
final TN mTN;
int mDuration;
View mNextView;
能看出什么来,看到 TN 和 View 了吧,TN 是一个 ALDL 类,mNextView是个 view 类,这应该就是 toast 显示文本的核心了吧
- mNextView 是什么类型的
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
Toast result = new Toast(context, looper);
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text);
result.mNextView = v;
result.mDuration = duration;
return result;
}
我们使用 makeText() 方法后会得到一个 toast 对象,那么这个方法就很重要了,我们可以看到 toast 的 view 类型的成员变量其实是个 TextView ,怪不得我们值需要传个字符串进来,那些自定义 toast 样子的都是在这个 mNextView 身上做的文章
- TN 是什么
private static class TN extends ITransientNotification.Stub {
private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
final Handler mHandler;
View mView;
View mNextView;
int mDuration;
WindowManager mWM;
String mPackageName;
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
TN(String packageName, @Nullable Looper looper) {
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mPackageName = packageName;
if (looper == null) {
// Use Looper.myLooper() if looper is not specified.
looper = Looper.myLooper();
if (looper == null) {
throw new RuntimeException(
"Can't toast on a thread that has not called Looper.prepare()");
}
}
mHandler = new Handler(looper, null) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW: {
IBinder token = (IBinder) msg.obj;
handleShow(token);
break;
}
case HIDE: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
break;
}
case CANCEL: {
handleHide();
// Don't do this in handleHide() because it is also invoked by
// handleShow()
mNextView = null;
try {
getService().cancelToast(mPackageName, TN.this);
} catch (RemoteException e) {
}
break;
}
}
}
};
}
@Override
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
}
TN 从继承的类型上能看出是一个 ALDL 远程通讯类,然后 TN 里面涉及到了 WindowManager ,这明显是把某个 view 交给 WindowManager 去显示,以实现全局弹窗的效果。
然后在 show() 方法里我们看到 toast 显示的核心使用 Handler 发送消息,使用 android 系统的消息队列来实现异步管理,这就是为啥我们必须要用同一个 toast 对象来做显示,在 toast 已经在显示的时候,我们再显示消息只不过是 更新 toast 里面 textview 的内容罢了
记住这句话:
单例模式,每次创建toast都调用这个类的show方法,Toast的生命周期从show开始,到自己消失或者cancel为止。如果正在显示,则修改显示的内容,你持有这个对象的引用,当然可以修改显示的内容了。若每次你都makeText或者new一个toast对象,即每次通过handler发送异步任务,调用远程通知服务显示通知,当然是排队等待显示了。