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>
相关推荐
ljx140005255031 分钟前
Android AudioFlinger(一)——初识AndroidAudio Flinger
android
ljx140005255033 分钟前
Android AudioFlinger(四)—— 揭开PlaybackThread面纱
android
Codingwiz_Joy34 分钟前
Day04 模拟原生开发app过程 Androidstudio+逍遥模拟器
android·安全·web安全·安全性测试
叶羽西36 分钟前
Android15 Camera框架中的StatusTracker
android·camera框架
梦中千秋40 分钟前
安卓设备root检测与隐藏手段
android
buleideli1 小时前
CameraX学习2-关于录像、慢动作录像
android·camerax
stevenzqzq3 小时前
android paging使用教程
android
无敌发光大蟒蛇3 小时前
MySQL第一次作业
android·数据库·mysql
m0_748238924 小时前
MySQL Workbench菜单汉化为中文
android·数据库·mysql
技术蔡蔡4 小时前
Android多线程开发之线程安全
android·面试