美文网首页
toast 工具 - 取消连续显示

toast 工具 - 取消连续显示

作者: 前行的乌龟 | 来源:发表于2018-09-05 06:59 被阅读518次

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 的实现

  1. toast 的成员成员变量
public class Toast {

    final Context mContext;
    final TN mTN;
    int mDuration;
    View mNextView;

能看出什么来,看到 TN 和 View 了吧,TN 是一个 ALDL 类,mNextView是个 view 类,这应该就是 toast 显示文本的核心了吧

  1. 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 身上做的文章

  1. 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发送异步任务,调用远程通知服务显示通知,当然是排队等待显示了。

参考


相关文章

网友评论

      本文标题:toast 工具 - 取消连续显示

      本文链接:https://www.haomeiwen.com/subject/pzsowftx.html