android 支持自定义布局、线程安全、避免内存泄漏的 Toast 工具类

支持自定义布局:可以灵活地显示自定义样式的 Toast。

线程安全:确保在主线程中显示 Toast,避免崩溃。

避免内存泄漏:使用 ApplicationContext 和取消机制,防止内存泄漏问题。

工具类:作为一个通用的工具类,方便在项目中复用。

ToastUtil

复制代码
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public class ToastUtil {

    private static Toast toast; // 全局Toast对象,避免重复创建
    private static final int DEFAULT_GRAVITY = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; // 默认显示位置
    private static final int DEFAULT_Y_OFFSET = 100; // 默认Y轴偏移量
    private static final Handler mainHandler = new Handler(Looper.getMainLooper()); // 主线程Handler

    /**
     * 显示短时间的Toast
     *
     * @param context 上下文
     * @param message 要显示的消息
     */
    public static void showShort(Context context, String message) {
        showToast(context, message, Toast.LENGTH_SHORT, DEFAULT_GRAVITY, 0, DEFAULT_Y_OFFSET);
    }

    /**
     * 显示长时间的Toast
     *
     * @param context 上下文
     * @param message 要显示的消息
     */
    public static void showLong(Context context, String message) {
        showToast(context, message, Toast.LENGTH_LONG, DEFAULT_GRAVITY, 0, DEFAULT_Y_OFFSET);
    }

    /**
     * 显示短时间的Toast(使用字符串资源ID)
     *
     * @param context 上下文
     * @param resId   字符串资源ID
     */
    public static void showShort(Context context, int resId) {
        showShort(context, context.getString(resId));
    }

    /**
     * 显示长时间的Toast(使用字符串资源ID)
     *
     * @param context 上下文
     * @param resId   字符串资源ID
     */
    public static void showLong(Context context, int resId) {
        showLong(context, context.getString(resId));
    }

    /**
     * 显示自定义位置的Toast
     *
     * @param context  上下文
     * @param message  要显示的消息
     * @param gravity  显示位置(例如 Gravity.TOP)
     * @param xOffset  X轴偏移量
     * @param yOffset  Y轴偏移量
     */
    public static void showAtPosition(Context context, String message, int gravity, int xOffset, int yOffset) {
        showToast(context, message, Toast.LENGTH_SHORT, gravity, xOffset, yOffset);
    }

    /**
     * 显示自定义布局的Toast
     *
     * @param context     上下文
     * @param layoutResId 自定义布局资源ID
     * @param message     要显示的消息
     */
    public static void showCustom(Context context, int layoutResId, String message) {
        runOnUiThread(() -> {
            if (toast != null) {
                toast.cancel(); // 取消之前的Toast
            }

            // 使用ApplicationContext,避免内存泄漏
            Context appContext = context.getApplicationContext();
            LayoutInflater inflater = (LayoutInflater) appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(layoutResId, null);

            // 查找布局中的TextView(假设id为text)
            TextView textView = layout.findViewById(R.id.text);
            if (textView != null) {
                textView.setText(message);
            }

            toast = new Toast(appContext);
            toast.setDuration(Toast.LENGTH_SHORT);
            toast.setView(layout);
            toast.show();
        });
    }

    /**
     * 显示自定义布局的Toast(支持自定义显示时长)
     *
     * @param context     上下文
     * @param layoutResId 自定义布局资源ID
     * @param message     要显示的消息
     * @param duration    显示时长(Toast.LENGTH_SHORT 或 Toast.LENGTH_LONG)
     */
    public static void showCustom(Context context, int layoutResId, String message, int duration) {
        runOnUiThread(() -> {
            if (toast != null) {
                toast.cancel(); // 取消之前的Toast
            }

            // 使用ApplicationContext,避免内存泄漏
            Context appContext = context.getApplicationContext();
            LayoutInflater inflater = (LayoutInflater) appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            View layout = inflater.inflate(layoutResId, null);

            // 查找布局中的TextView(假设id为text)
            TextView textView = layout.findViewById(R.id.text);
            if (textView != null) {
                textView.setText(message);
            }

            toast = new Toast(appContext);
            toast.setDuration(duration);
            toast.setView(layout);
            toast.show();
        });
    }

    /**
     * 核心方法:显示Toast
     *
     * @param context  上下文
     * @param message  要显示的消息
     * @param duration 显示时长(Toast.LENGTH_SHORT 或 Toast.LENGTH_LONG)
     * @param gravity  显示位置
     * @param xOffset  X轴偏移量
     * @param yOffset  Y轴偏移量
     */
    private static void showToast(Context context, String message, int duration, int gravity, int xOffset, int yOffset) {
        runOnUiThread(() -> {
            if (toast != null) {
                toast.cancel(); // 取消之前的Toast
            }

            // 使用ApplicationContext,避免内存泄漏
            Context appContext = context.getApplicationContext();
            toast = Toast.makeText(appContext, message, duration);
            toast.setGravity(gravity, xOffset, yOffset); // 设置显示位置
            toast.show();
        });
    }

    /**
     * 取消Toast
     */
    public static void cancelToast() {
        if (toast != null) {
            toast.cancel();
            toast = null; // 释放引用
        }
    }

    /**
     * 确保在主线程中运行
     *
     * @param runnable 需要执行的任务
     */
    private static void runOnUiThread(Runnable runnable) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            runnable.run(); // 当前是主线程,直接运行
        } else {
            mainHandler.post(runnable); // 当前是子线程,切换到主线程运行
        }
    }
}

使用示例

  1. 显示自定义布局的 Toast

    ToastUtil.showCustom(MainActivity.this, R.layout.custom_toast, "这是一个自定义Toast");

在子线程中调用:

复制代码
new Thread(() -> {
    // 在子线程中调用
    ToastUtil.showCustom(MainActivity.this, R.layout.custom_toast, "子线程中的自定义Toast");
}).start();

自定义布局示例:

假设 res/layout/custom_toast.xml 是一个自定义布局文件,例如:

复制代码
<!-- res/layout/custom_toast.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/toast_background"
    android:padding="16dp"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/icon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@drawable/ic_toast_icon"
        android:layout_marginEnd="8dp"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:textSize="16sp"/>
</LinearLayout>
相关推荐
移动开发者1号7 分钟前
Android ContentProvider多表关联查询
android·kotlin
移动开发者1号10 分钟前
显式与隐式Intent调用对比
android·kotlin
移动开发者1号13 分钟前
Android后台服务保活简介
android·kotlin
移动开发者1号15 分钟前
Android后台任务管理利器
android·kotlin
恋猫de小郭1 小时前
Flutter 官方多窗口体验 ,为什么 Flutter 推进那么慢,而 CMP 却支持那么快
android·前端·flutter
yan123687 小时前
Linux 驱动之设备树
android·linux·驱动开发·linux驱动
aningxiaoxixi9 小时前
android stdio 的布局属性
android
CYRUS STUDIO10 小时前
FART 自动化脱壳框架一些 bug 修复记录
android·bug·逆向·fart·脱壳
寻找优秀的自己11 小时前
Cocos 打包 APK 兼容环境表(Android API Level 10~15)
android·cocos2d