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>
相关推荐
Digitally18 小时前
如何在安卓手机/平板上找到下载文件?
android·智能手机·电脑
硬件学长森哥21 小时前
Android影像基础--cameraAPI2核心流程
android·计算机视觉
前行的小黑炭1 天前
Android 协程的使用:结合一个环境噪音检查功能的例子来玩玩
android·java·kotlin
阿华的代码王国1 天前
【Android】内外部存储的读写
android·内外存储的读写
inmK11 天前
蓝奏云官方版不好用?蓝云最后一版实测:轻量化 + 不限速(避更新坑) 蓝云、蓝奏云第三方安卓版、蓝云最后一版、蓝奏云无广告管理工具、安卓网盘轻量化 APP
android·工具·网盘工具
giaoho1 天前
Android 热点开发的相关api总结
android
咖啡の猫1 天前
Android开发-常用布局
android·gitee
程序员老刘1 天前
Google突然“变脸“,2026年要给全球开发者上“紧箍咒“?
android·flutter·客户端
Tans51 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
雨白1 天前
实现双向滑动的 ScalableImageView(下)
android