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>
相关推荐
奔跑吧 android14 分钟前
【android bluetooth 协议分析 07】【SDP详解 2】【SDP 初始化】
android·bluetooth·aosp15·bt·gd·sdp_init
梦否3 小时前
Android 代码热度统计(概述)
android
xchenhao6 小时前
基于 Flutter 的开源文本 TTS 朗读器(支持 Windows/macOS/Android)
android·windows·flutter·macos·openai·tts·朗读器
coder_pig7 小时前
跟🤡杰哥一起学Flutter (三十五、玩转Flutter滑动机制📱)
android·flutter·harmonyos
消失的旧时光-19438 小时前
OkHttp SSE 完整总结(最终版)
android·okhttp·okhttp sse
ansondroider9 小时前
OpenCV 4.10.0 移植 - Android
android·人工智能·opencv
hsx66611 小时前
Kotlin return@label到底怎么用
android
itgather12 小时前
安卓设备信息查看器 - 源码编译
android
whysqwhw12 小时前
OkHttp之buildSrc模块分析
android
hsx66612 小时前
从源码角度理解Android事件的传递流程
android