Android 通用倒计时弹窗工具类 CountDownDialogHelper(全局复用、线程安全)

文章标题

Android 封装通用倒计时提示弹窗工具类,支持动态更新文案、自动倒计时关闭,工控 / 自助售卖机项目实战

文章标签

#Android #Dialog 封装 #倒计时弹窗 #工具类 #自助设备开发

正文内容

一、前言

在无人自助设备、工控 Android 项目中,经常需要弹出带自动倒计时关闭的提示弹窗 :操作成功提醒、设备故障提示、取餐超时预警等场景。 如果每个页面单独写 Dialog 会造成大量重复代码,本文封装一套全局可复用CountDownDialogHelper,具备以下特性:

  1. 线程安全:使用主线程 Handler,支持子线程动态更新弹窗文案
  2. 自动倒计时:传入总秒数,时间结束自动关闭(可自定义逻辑)
  3. 全局控制:提供显示 / 销毁 / 判断弹窗状态 / 更新内容方法
  4. 自定义布局:宽高自适应屏幕 85% 宽度,禁止外部点击关闭,适配工业自助机场景
  5. 低耦合:构造传入标题、内容、倒计时总时长,一行代码调用

二、完整工具类代码 CountDownDialogHelper

java 复制代码
package sales.machine.common;

import android.app.Dialog;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;

import sales.machine.androidproject.R;

public class CountDownDialogHelper {

    private final Dialog dialog;
    private final TextView tvCountDown;
    private final Handler mainHandler = new Handler(Looper.getMainLooper());
    private int remainSecond;
    public boolean isDialogShowing() {
        return dialog != null && dialog.isShowing();
    }
    private final Runnable countDownTask = new Runnable() {
        @Override
        public void run() {
            remainSecond--;
            if (remainSecond > 0) {
                tvCountDown.setText(remainSecond + "秒后自动关闭");
                mainHandler.postDelayed(this, 1000);
            } else {
                remainSecond = 100;
               // dismissDialog();
            }
        }
    };
    TextView tvContent = null;
    public CountDownDialogHelper(Context context, String title, String content, int totalSecond) {
        remainSecond = totalSecond;
        // 创建Dialog  Theme_Light_NoTitle_Dialog
        dialog = new Dialog(context, android.R.style.Theme_Light_NoTitleBar);
        View view = LayoutInflater.from(context).inflate(R.layout.dialog_countdown_tip, null);
        dialog.setContentView(view);

        // 控件绑定
        TextView tvTitle = view.findViewById(R.id.tv_dialog_title);
        tvContent = view.findViewById(R.id.tv_dialog_content);
        tvCountDown = view.findViewById(R.id.tv_dialog_count_down);
       // Button btnClose = view.findViewById(R.id.btn_c_close);

        tvTitle.setText(title);
        tvContent.setText(content);
        tvCountDown.setText(remainSecond + "秒后自动关闭");

        // 手动关闭
       // btnClose.setOnClickListener(v -> dismissDialog());

        // 设置弹窗大小(大弹窗)
        Window window = dialog.getWindow();
        if (window != null) {
            WindowManager.LayoutParams lp = window.getAttributes();
            lp.width = (int) (context.getResources().getDisplayMetrics().widthPixels * 0.85f); // 屏幕宽度85%
            lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
            window.setAttributes(lp);
        }

        // 点击外部不可取消
        dialog.setCancelable(false);
    }
    //更新content
    // 更新弹窗提示文案
    public void updateContent(String content) {
        mainHandler.post(() -> {
            if (tvContent != null) {
                tvContent.setText(content);
            }
        });
    }
    // 显示弹窗 + 启动倒计时
    public void show() {
        dialog.show();
        mainHandler.postDelayed(countDownTask, 1000);
    }

    // 关闭弹窗,移除倒计时
    public void dismissDialog() {
        mainHandler.removeCallbacks(countDownTask);
        if (dialog.isShowing()) {
            dialog.dismiss();
        }
    }
}

三、弹窗布局 XML dialog_countdown_tip.xml

放置于 res/layout/ 目录,适配工控大屏,文字放大适配远距离观看

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="600dp"
    android:orientation="vertical"
    android:background="#00FF00"
    android:padding="40dp">

    <!-- 标题 -->
    <TextView
        android:id="@+id/tv_dialog_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="温馨提示"
        android:textSize="22sp"
        android:textColor="#ffffff"
        android:gravity="center"/>

    <!-- 提示内容 -->
    <TextView
        android:id="@+id/tv_dialog_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="操作成功!"
        android:textColor="#FF0000"
        android:textSize="100sp"
        android:layout_marginTop="20dp"
        android:gravity="center"/>

    <!-- 倒计时文字 -->
    <TextView
        android:id="@+id/tv_dialog_count_down"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="5秒后自动关闭"
        android:textSize="14sp"
        android:textColor="#F53F3F"
        android:layout_marginTop="16dp"
        android:gravity="center"/>

    <!-- 手动关闭按钮 -->
<!--    <Button-->
<!--        android:id="@+id/btn_c_close"-->
<!--        android:layout_width="match_parent"-->
<!--        android:layout_height="wrap_content"-->
<!--        android:text="立即关闭"-->
<!--        android:layout_marginTop="24dp"/>-->

</LinearLayout>

四、工具类使用示例

1. 快速创建并显示弹窗(Activity/Fragment)

java

运行

复制代码
// 构造:上下文、标题、提示内容、倒计时总秒数5秒
CountDownDialogHelper countDownDialog = new CountDownDialogHelper(this, "操作提示", "取餐码已生成,请前往设备扫码取餐", 5);
// 显示弹窗,开始倒计时
countDownDialog.show();
2. 子线程动态更新弹窗文案

java

运行

复制代码
new Thread(() -> {
    // 模拟网络/设备异步任务
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // 子线程直接调用,内部自动切主线程更新UI
    countDownDialog.updateContent("饺子制作完成,请尽快取餐!超时将作废");
}).start();
3. 手动关闭弹窗、判断弹窗状态

java

运行

复制代码
// 判断弹窗是否正在显示
if (countDownDialog.isDialogShowing()) {
    // 关闭弹窗,清除倒计时回调,避免内存泄漏
    countDownDialog.dismissDialog();
}

五、关键细节与优化点说明

  1. 内存泄漏防护 关闭弹窗时调用mainHandler.removeCallbacks(countDownTask)移除延时任务,防止 Activity 销毁后 Handler 持有引用造成泄漏。
  2. 线程安全设计 所有 UI 更新操作通过mainHandler.post()切换主线程,子线程可直接调用updateContent()无需额外切换线程。
  3. 工控设备适配优化
    • 弹窗宽度固定为屏幕 85%,大屏不会过小;
    • 提示内容字号放大至 100sp,适配自助机远距离观看;
    • setCancelable(false)禁止误触空白处关闭弹窗,保证用户读取提示。
  4. 可扩展改造点
    • 放开注释的关闭按钮,在构造方法增加回调接口,实现手动关闭监听;
    • 修改倒计时结束逻辑,放开dismissDialog()实现倒计时自动关闭;
    • 增加弹窗背景、圆角、渐变样式适配 UI 规范;
    • 新增回调接口OnCountDownFinishListener,倒计时结束回调业务逻辑。