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>
相关推荐
robotx3 小时前
安卓线程相关
android
消失的旧时光-19433 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon4 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon4 小时前
VSYNC 信号完整流程2
android
dalancon4 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013845 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android6 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才6 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶7 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙7 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github