












本文档基于HarmonyOS NEXT API 12+官方标准,详细阐述Toast弹窗的最新开发规范、API演进、最佳实践与常见问题解决方案,为企业级应用提供统一的Toast开发标准,避免因上下文不明确导致的弹窗失效问题。
弃用提醒 全局调用 promptAction.showToast() 方式已不推荐使用
原有全局调用方式在多窗口、多Ability等复杂场景下可能因上下文不明确导致弹窗显示失败或异常,API 10及以上版本推荐通过UIContext获取PromptAction实例的方式调用。
@ohos.promptAction模块的功能依赖UI执行上下文(UIContext),全局调用方式无法明确绑定到当前页面的UI上下文,在以下场景可能出现不稳定:
• 多窗口应用中,无法确定弹窗显示在哪个窗口
• 页面跳转或Ability切换时,上下文失效导致弹窗失败
• 后台线程或非UI上下文环境中调用,直接抛出异常
• 复杂页面嵌套场景下,弹窗位置计算错误
|
API版本 |
支持方式 |
说明 |
|
API 9 |
仅支持全局调用 |
promptAction.showToast() 全局调用方式首次发布 |
|
API 10 |
两种方式并存 |
新增通过UIContext获取PromptAction实例的方式,推荐使用新方式 |
|
API 12+ |
推荐新方式 |
全局调用方式标记为不推荐,复杂场景下可能出现异常 |
|
API 18+ |
新增API |
新增 openToast() 和 closeToast() 方法,支持更灵活的控制 |
推荐 在组件中通过 getUIContext() 获取UI上下文,再获取PromptAction实例调用:
import { UIContext, PromptAction } from '@kit.ArkUI';
@Entry
@Component
struct ToastDemoPage {
// 获取当前组件的UI上下文
private uiContext: UIContext = this.getUIContext();
// 获取PromptAction实例
private promptAction: PromptAction = this.uiContext.getPromptAction();
build() {
Column({ space: 20 }) {
Button('显示普通Toast')
.width(200)
.height(50)
.onClick(() => {
this.showNormalToast();
})
Button('显示长时长Toast')
.width(200)
.height(50)
.onClick(() => {
this.showLongToast();
})
Button('显示自定义位置Toast')
.width(200)
.height(50)
.onClick(() => {
this.showCustomPositionToast();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.backgroundColor('#f5f5f5')
}
/**
* 显示普通Toast
*/
private showNormalToast(): void {
try {
this.promptAction.showToast({
message: '操作成功',
duration: 2000
});
} catch (error) {
console.error(`显示Toast失败: ${JSON.stringify(error)}`);
}
}
/**
* 显示长时长Toast
*/
private showLongToast(): void {
try {
this.promptAction.showToast({
message: '文件下载中,请稍候...',
duration: 5000 // 最长支持10000ms
});
} catch (error) {
console.error(`显示Toast失败: ${JSON.stringify(error)}`);
}
}
/**
* 显示自定义位置Toast
*/
private showCustomPositionToast(): void {
try {
this.promptAction.showToast({
message: '距离顶部200vp位置显示',
duration: 2000,
bottom: '200vp' // 距离底部的距离,单位vp
});
} catch (error) {
console.error(`显示Toast失败: ${JSON.stringify(error)}`);
}
}
}
API 18及以上版本推荐使用 openToast() 方法,支持手动关闭Toast:
import { UIContext, PromptAction, ToastShowOptions } from '@kit.ArkUI';
@Entry
@Component
struct ToastAPIDemo {
private uiContext: UIContext = this.getUIContext();
private promptAction: PromptAction = this.uiContext.getPromptAction();
@State toastId: number = -1;
build() {
Column({ space: 20 }) {
Button('显示可关闭Toast')
.width(200)
.height(50)
.onClick(() => {
this.showClosableToast();
})
Button('手动关闭Toast')
.width(200)
.height(50)
.enabled(this.toastId !== -1)
.onClick(() => {
this.closeToast();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
/**
* 显示可手动关闭的Toast
*/
private showClosableToast(): void {
try {
const options: ToastShowOptions = {
message: '下载进度 80%',
duration: 10000
};
// openToast返回ToastID,可用于后续关闭
this.toastId = this.promptAction.openToast(options);
console.log(`Toast已显示,ID: ${this.toastId}`);
} catch (error) {
console.error(`显示Toast失败: ${JSON.stringify(error)}`);
}
}
/**
* 手动关闭Toast
*/
private closeToast(): void {
if (this.toastId === -1) return;
try {
this.promptAction.closeToast(this.toastId);
this.toastId = -1;
console.log('Toast已手动关闭');
} catch (error) {
console.error(`关闭Toast失败: ${JSON.stringify(error)}`);
}
}
}
|
参数 |
类型 |
必填 |
说明 |
|
message |
string | Resource |
是 |
Toast显示的文本内容,支持字符串或资源引用 |
|
duration |
number |
否 |
显示时长,单位毫秒,默认1500ms,有效值范围1500-10000ms |
|
bottom |
string | number |
否 |
Toast距离屏幕底部的距离,单位vp,默认值根据系统版本不同而变化 |
为简化调用,建议封装统一的Toast工具类,支持全局调用和上下文自动绑定:
// ToastUtil.ets
import { UIContext, PromptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
/**
* Toast工具类
* 统一管理Toast显示,支持不同场景调用
*/
export class ToastUtil {
private static promptAction: PromptAction | null = null;
private static currentToastId: number = -1;
/**
* 初始化,在入口Ability的onWindowStageCreate中调用
* @param uiContext UI上下文
*/
static init(uiContext: UIContext): void {
this.promptAction = uiContext.getPromptAction();
}
/**
* 显示成功Toast
* @param message 提示内容
* @param duration 显示时长,默认2000ms
*/
static showSuccess(message: string, duration: number = 2000): void {
this.showToast(`✅ ${message}`, duration);
}
/**
* 显示错误Toast
* @param message 提示内容
* @param duration 显示时长,默认3000ms
*/
static showError(message: string, duration: number = 3000): void {
this.showToast(`❌ ${message}`, duration);
}
/**
* 显示警告Toast
* @param message 提示内容
* @param duration 显示时长,默认2500ms
*/
static showWarning(message: string, duration: number = 2500): void {
this.showToast(`⚠️ ${message}`, duration);
}
/**
* 显示信息Toast
* @param message 提示内容
* @param duration 显示时长,默认2000ms
*/
static showInfo(message: string, duration: number = 2000): void {
this.showToast(`ℹ️ ${message}`, duration);
}
/**
* 显示加载Toast
* @param message 提示内容,默认"加载中..."
* @returns ToastID,可用于手动关闭
*/
static showLoading(message: string = '加载中...'): number {
if (!this.promptAction) {
console.error('ToastUtil未初始化,请先在入口Ability中调用init()');
return -1;
}
try {
// API 18+使用openToast支持手动关闭
if (typeof this.promptAction.openToast === 'function') {
this.closeCurrentToast();
this.currentToastId = this.promptAction.openToast({
message: `⏳ ${message}`,
duration: 10000 // 最长显示10秒
});
return this.currentToastId;
} else {
// 低版本兼容
this.promptAction.showToast({
message: `⏳ ${message}`,
duration: 3000
});
return -1;
}
} catch (error) {
console.error(`显示加载Toast失败: ${JSON.stringify(error)}`);
return -1;
}
}
/**
* 关闭当前显示的Toast
*/
static closeCurrentToast(): void {
if (!this.promptAction || this.currentToastId === -1) return;
try {
if (typeof this.promptAction.closeToast === 'function') {
this.promptAction.closeToast(this.currentToastId);
this.currentToastId = -1;
}
} catch (error) {
console.error(`关闭Toast失败: ${JSON.stringify(error)}`);
}
}
/**
* 基础Toast显示方法
* @param message 提示内容
* @param duration 显示时长
*/
private static showToast(message: string, duration: number): void {
if (!this.promptAction) {
console.error('ToastUtil未初始化,请先在入口Ability中调用init()');
return;
}
// 校验时长范围
const validDuration = Math.max(1500, Math.min(duration, 10000));
try {
this.promptAction.showToast({
message: message,
duration: validDuration
});
} catch (error) {
console.error(`显示Toast失败: ${JSON.stringify(error)}`);
}
}
}
在入口Ability的onWindowStageCreate方法中初始化Toast工具类:
// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import { ToastUtil } from '../common/utils/ToastUtil';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
console.error('加载页面失败', JSON.stringify(err));
return;
}
// 获取主窗口UI上下文,初始化Toast工具类
windowStage.getMainWindow().then((window) => {
const uiContext = window.getUIContext();
ToastUtil.init(uiContext);
console.log('ToastUtil初始化完成');
}).catch((err) => {
console.error('获取窗口失败', JSON.stringify(err));
});
});
}
}
// 在任意页面或组件中使用
import { ToastUtil } from '../common/utils/ToastUtil';
@Entry
@Component
struct UserLoginPage {
@State username: string = '';
@State password: string = '';
build() {
Column({ space: 20 }) {
TextInput({ placeholder: '请输入用户名' })
.width('80%')
.height(50)
.onChange((value) => {
this.username = value;
})
TextInput({ placeholder: '请输入密码', type: InputType.Password })
.width('80%')
.height(50)
.onChange((value) => {
this.password = value;
})
Button('登录')
.width('80%')
.height(50)
.onClick(() => {
this.handleLogin();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
async handleLogin() {
if (!this.username || !this.password) {
ToastUtil.showWarning('请输入用户名和密码');
return;
}
// 显示加载Toast
const toastId = ToastUtil.showLoading('登录中...');
try {
// 模拟登录请求
await new Promise(resolve => setTimeout(resolve, 2000));
// 登录成功
ToastUtil.showSuccess('登录成功');
// 跳转到首页
} catch (e) {
// 登录失败
ToastUtil.showError('登录失败,请检查用户名和密码');
} finally {
// 关闭加载Toast
ToastUtil.closeCurrentToast();
}
}
}
|
适用场景 |
不适用场景 |
|
操作成功/失败的轻量提示 |
需要用户确认的重要操作提示(使用Dialog) |
|
网络请求状态提示 |
长时间显示的状态提示(使用自定义悬浮组件) |
|
表单校验错误提示 |
包含复杂交互的提示 |
|
非阻塞性的信息通知 |
后台运行时的用户通知(使用Notification) |
|
简短的操作反馈 |
需要用户阅读完全部内容才能继续的提示 |
• 内容简洁:Toast文本内容建议控制在20字以内,避免过长内容换行
• 时长合理:普通提示使用1500-2000ms,较长内容使用2500-3000ms,最长不超过5000ms
• 避免重复:相同内容的Toast不要连续重复显示,可做防抖处理
• 层次分明:不同类型的Toast使用不同的前缀图标,提升辨识度
• 不阻塞操作:Toast显示期间不影响用户其他操作
• 单例模式:全局维护一个PromptAction实例,避免重复创建
• 防抖处理:短时间内多次调用Toast显示时,合并或忽略重复请求
• 异常捕获:所有Toast调用都要添加try-catch,避免异常导致应用崩溃
• 资源释放:页面销毁时关闭正在显示的Toast,避免内存泄漏
当系统Toast无法满足样式或交互需求时,可以通过自定义弹窗实现类似效果:
// CustomToast.ets
@Component
export struct CustomToast {
@Param message: string = '';
@Param duration: number = 2000;
@State isShow: boolean = false;
private timerId: number = -1;
// 显示Toast
show(): void {
this.isShow = true;
// 自动关闭
if (this.timerId !== -1) {
clearTimeout(this.timerId);
}
this.timerId = setTimeout(() => {
this.hide();
}, this.duration);
}
// 隐藏Toast
hide(): void {
this.isShow = false;
if (this.timerId !== -1) {
clearTimeout(this.timerId);
this.timerId = -1;
}
}
build() {
if (this.isShow) {
Stack() {
// 半透明背景
Column() {
Text(this.message)
.fontSize(14)
.fontColor(Color.White)
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
}
.backgroundColor('rgba(0, 0, 0, 0.7)')
.borderRadius(24)
.maxWidth('80%')
}
.width('100%')
.height('100%')
.alignContent(Alignment.Bottom)
.padding({ bottom: 80 })
.pointerEvents(PointerEvent.None) // 不拦截点击事件
}
}
}
// 使用示例
@Entry
@Component
struct CustomToastDemo {
@State customToast: CustomToast | null = null;
build() {
Stack() {
Column() {
Button('显示自定义Toast')
.onClick(() => {
this.customToast?.show();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
// Toast组件放在根节点
CustomToast({
message: '这是自定义样式的Toast',
duration: 2500
})
.onAppear((instance) => {
this.customToast = instance as CustomToast;
})
}
.width('100%')
.height('100%')
}
}
|
问题现象 |
原因分析 |
解决方案 |
|
Toast不显示,控制台报错"context is null" |
在非UI上下文环境中调用,或上下文已失效 |
确保在UI组件中调用,或通过窗口获取有效的UIContext |
|
Toast显示在错误的窗口 |
使用了全局调用方式,无法识别当前窗口 |
改用UIContext方式,为每个窗口创建独立的PromptAction实例 |
|
页面跳转后Toast仍然显示 |
Toast绑定的是全局上下文,不受页面生命周期影响 |
页面onPageHide时手动关闭Toast,或使用自定义Toast绑定页面生命周期 |
|
duration参数设置无效 |
duration值超出1500-10000ms范围,被系统自动修正 |
将duration设置在有效范围内,超长提示建议使用其他方式 |
|
Toast位置异常 |
bottom参数设置不当,或系统布局变化 |
调整bottom参数值,或使用自定义Toast精确控制位置 |
|
后台调用Toast显示失败 |
应用退后台后UI上下文被系统回收 |
后台通知使用Notification Kit,不要使用Toast |
|
快速点击时多个Toast重叠显示 |
没有做防抖处理,多次调用导致多个Toast同时显示 |
封装工具类添加防抖逻辑,相同内容短时间内只显示一次 |
1. ✅ 所有Toast调用都通过UIContext获取PromptAction实例,不使用全局调用
2. ✅ Toast内容简洁,符合场景需求,不超过20字
3. ✅ duration参数设置在1500-10000ms有效范围内
4. ✅ 所有Toast调用都添加了try-catch异常捕获
5. ✅ 后台场景使用Notification,不使用Toast
6. ✅ 相同内容的Toast添加了防抖处理,避免重复显示
7. ✅ 重要提示使用Dialog,不依赖Toast作为唯一通知方式
8. ✅ 多窗口应用为每个窗口独立管理PromptAction实例
9. ✅ API 18+项目优先使用openToast/closeToast方法
10. ✅ 加载类Toast提供手动关闭机制,避免长时间显示
参考资料
@ohos.promptAction (弹窗) - 官方文档 (API 13)
Using Toasts (Toast) - 官方指南
文档版本:V1.0 | 适配版本:HarmonyOS NEXT API 12+ | 更新日期:2024年4月
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。