Android Toast进阶——自定义Toast

时间:2021-09-17 20:43:05

目录结构


前言

CSDN竟然支持Markdown语法了,对我这种懒得自己搭建博客的人确实是好消息,再复习Markdown语法的同时,我准备把之前写的一篇Android Toast进阶用Markdown语法重写一遍。


进阶目标

上一篇博客我们学习了Toast的源码,了解了Toast从显示到消失的全过程,学习链接:Android Toast源码分析。俗话说的好,学以致用。我们学习Toast源码不是用来炫技的,而是用来了解Toast原理,从而真正解决我们问题的。下面我就提两个业务中可能遇到的跟Toast相关的真实问题,看看学习了Toast源码之后,该如何解决这些问题。两个问题是:
1. 如何自定义Toast的显示时间。
2. 如何修改Toast的出现动画。
接下来,我们分别讲解阅读了Toast源码之后,如何解决这两个业务中真实遇到的问题。


控制Toast显示时间

通过对Toast源码的学习,我们知道Toast的显示和消失是NotificationManagerService调用TN类的show和hide方法实现的,而Toast的显示时间的长短则跟Handler发送消息的延迟时间相关。具体源码如下:

private void scheduleTimeoutLocked(ToastRecord r)
{
    mHandler.removeCallbacksAndMessages(r);
    Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
    long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
    mHandler.sendMessageDelayed(m, delay);
}

之前Toast源码里也讲过,为什么Toast的桌面显示时间只能是2s和3.5s,关键就是在于long delay变量的延迟时间只能是2s和3.5s。因此,如果你是Android操作系统的开发人员,你可以直接修改Android Framework层的NotificationManagerService类代码,将LONG_DELAY和SHORT_DELAY改成你想要的时间间隔。但是,这种做法的弊端很明显。首先,你可能只是一个小小的应用层开发工程师,只能改动应用层代码。其次,就算修改NotificationManagerService,也只能改动LONG_DELAY和SHORT_DELAY两个变量,无法做到随意修改显示时间。
弊端这么多,那我们应用层开发工程师该怎么办呢?答案也很简单,仿照Toast源码,我们自己造个*,自定义一个Toast,这样我们肯定就可以控制Toast的显示时间了。通过源码我们知道,Toast是基于WindowManager来显示的,那我们完全可以撸一个自定义Toast出来,源码如下:

import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

public class ToastCustom {
    private static final int MESSAGE_TIMEOUT = 2;
    private WindowManager wdm;
    private double time;
    private View mView;
    private WindowManager.LayoutParams params;
    private WorkerHandler mHandler;

    private ToastCustom(Context context, String text, double time) {
        wdm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mHandler = new WorkerHandler();

        Toast toast = Toast.makeText(context, text, Toast.LENGTH_LONG);
        mView = toast.getView();

        params = new WindowManager.LayoutParams();
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.format = PixelFormat.TRANSLUCENT;
        params.windowAnimations = toast.getView().getAnimation().INFINITE;
        params.type = WindowManager.LayoutParams.TYPE_TOAST;
        params.setTitle("Toast");
        params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
        params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;

        this.time = time;
    }

    public static ToastCustom makeText(Context context, String text, double time) {
        ToastCustom toastCustom = new ToastCustom(context, text, time);
        return toastCustom;
    }

    public void show() {
        wdm.addView(mView, params);
        mHandler.sendEmptyMessageDelayed(MESSAGE_TIMEOUT, (long) (time * 1000));
    }

    public void cancel() {
        wdm.removeView(mView);
    }

    private class WorkerHandler extends Handler {
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_TIMEOUT:
                    cancel();
                    break;
            }
        }
    }
}

原理很简单,利用WindowManager来显示Toast,然后利用Handler机制发送延迟消息控制WindowManager再将Toast删除。需要注意一点:这里自定义Toast的Handler用的也是主线程的Looper,子线程调用该自定义Toast需要增加Looper.prepare()和Looper.loop()代码。


修改Toast动画效果

Android原生的Toast类并没有提供给我们设置动画效果的接口,每个Android原生的Toast的动画效果都是在TN类中定义好的com.android.internal.R.style.Animation_Toast,因此,如果你想要修改Android Toast的动画效果,还是需要自己撸一个Toast,修改一下params.windowAnimations变量的内容即可。接下来,让我们先自定义一个动画效果。在style.xml文件中定义一个新的style,xml内容如下:

<style name="custom_toast_anim_view"> <item name="@android:windowEnterAnimation">@anim/enter_anim</item> <item name="@android:windowExitAnimation">@anim/exit_anim</item> </style>

然后在anim文件夹下面增加两个动画效果文件,分别为enter_anim.xml和exit_anim.xml。

  • enter_anim.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate  android:duration="1" android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="0" android:toYDelta="80" />
    <translate  android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="0" android:toYDelta="-100" android:duration="300" android:fillAfter="true" android:interpolator="@android:anim/decelerate_interpolator"/>
    <alpha  android:duration="100" android:fromAlpha="0" android:toAlpha="1" />
    <translate  android:duration="80" android:fillAfter="true" android:fromXDelta="0" android:fromYDelta="0" android:startOffset="300" android:toXDelta="0" android:toYDelta="20" />
    </set>
  • exit_anim.xml

    <?xml version="1.0" encoding="utf-8"?>
    <set xmlns:android="http://schemas.android.com/apk/res/android" >
    <alpha  android:duration="800" android:fromAlpha="1" android:toAlpha="0" />
    </set>

    然后,修改一下自定义Toast代码中的params.windowAnimations变量即可:

    params.windowAnimations = com.example.photocrop.R.style.custom_toast_anim_view;