uni-app 全局消息通知弹窗(App端)

uni-app 全局消息通知弹窗(App端)

实现效果

实现一个顶部的全局消息通知,并且可以常驻,除非手动关闭。

效果图如下

在这里插入图片描述

在这里插入图片描述

收到告警通知 弹窗从顶部向下弹出,可点击跳转到对应页面,可上滑关闭弹窗,弹窗出现期间不能阻塞其他操作(切换页面、点击其他按钮…)…

  1. 全局通知
  2. 可以常驻,不影响其他操作(页面跳转、点击事件)
  3. 点击消失(触发回调)、向上滑动消失(触发回调)
  4. 带点动画

实现

思索与翻找文档良久,未得容易的实现方式,转而向 HTML5+ 寻求解答,得一解决方案,遂记之,以留痕迹便与他人。

plus.nativeObj.View 原生控件对象

文档传送门

文档说明:

原生控件对象可用于在屏幕上绘制图片或文本内容,后显示的覆盖先显示的; 调用Webview窗口对象的append方法添加到Webview中,显示在父窗口所有子Webview的上面;不添加到Webview窗口对象,显示在所有Webview的上面。

注意事项

  1. 这东西无法写阴影( box-shadow

不过可以用图片替代

  1. 自带的 startAnimation 没啥效果(也许是我用错了)

可以通过定时器自己去实现动画效果

代码展示

内含详细注释

const { statusBarHeight } = uni.getSystemInfoSync();
class NativeMsg {
	// 整个区域的宽高
	viewStyle = {
		backgroundColor: "rgba(255,255,255,0)",
		top: "0px",
		left: "0px",
		width: "100%",
		// 取图片的高度(带阴影的尺寸)
		height: `${uni.upx2px(239)}px`
	};
	constructor(item, cb) {
		// 记录内容信息,以供回调使用
		this.item = item;
		// 弹出、消失动画要用
		this.offsetTop = -statusBarHeight - uni.upx2px(159);
		// 上边界
		this.startTop = -statusBarHeight - uni.upx2px(159);
		// 下边界
		this.endTop = statusBarHeight;
		// 上滑关闭要用
		this.clientY = 0;
		// nativeObj.View 实例
		this.view = null;
		// 背景图片
		this.bgBitmap = null;
		// 回调函数
		this.cb = cb || null;
		// 隐藏过程flag,防止重复执行
		this.hiding = false;
		// 标记当前弹窗状态
		this.status = "active";
		this.create();
	}
	// 创建区域以及背景
	create() {
		this.loadBg().then(() => {
			let _view = null;
			// 创建 View区域
			_view = new plus.nativeObj.View(`alarmMsg-${this.item.alarmId || "ins"}`, this.viewStyle);
			// 画背景
			_view.drawBitmap(
				this.bitmap,
				{},
				{ width: this.viewStyle.width, height: this.viewStyle.height, left: 0, top: 0 },
				"alarm-bg"
			);
			// 拦截触摸事件: 开启后 区域内的触摸事件不会透传到下面
			_view.interceptTouchEvent(true);
			// 增加点击事件监听
			_view.addEventListener("click", () => {
				if (this.hiding) return;
				this.hiding = true;
				this.cb && this.cb({ type: "click", result: this.item });
				this.animationHide();
			});
			// 触摸事件监听
			_view.addEventListener("touchstart", res => {
				this.clientY = res.clientY;
			});
			// 触摸事件监听
			_view.addEventListener("touchmove", res => {
				const { clientY } = res;
				let offsetY = this.clientY - clientY;
				if (offsetY > 25 && !this.hiding) {
					this.hiding = true;
					this.cb && this.cb({ type: "move", result: this.item });
					this.animationHide();
				}
			});
			// 保存
			this.view = _view;
			// 画内容
			this.drawInfo();
			// 显示
			this.animationShow();
		});
	}
	// 加载背景图片
	loadBg() {
		// 创建Bitmap图片
		this.bitmap = new plus.nativeObj.Bitmap("nativeMsg-bg");
		// 以Promise方式封装 图片加载过程
		return new Promise((resolve, reject) => {
			// 加载图片, 路径需要注意
			this.bitmap.load(
				"_www/static/tpt/alarm-bg.png",
				() => {
					resolve();
				},
				error => {
					console.log(" ====> error", error);
					reject();
				}
			);
		});
	}
	// 画内容
	drawInfo() {
		const { warningTypeStr, projectName, description } = this.item;
		this.view.draw([
			{
				tag: "font",
				id: "mainFont",
				text: warningTypeStr,
				textStyles: { size: `${uni.upx2px(36)}px`, color: "#262626", weight: "bold", align: "left" },
				position: { top: `${uni.upx2px(60)}px`, left: `${uni.upx2px(80)}px`, height: "wrap_content" }
			},
			{
				tag: "font",
				id: "projectFont",
				text: projectName,
				textStyles: { size: `${uni.upx2px(24)}px`, color: "#7B7B7B", align: "right", overflow: "ellipsis" },
				position: {
					top: `${uni.upx2px(60)}px`,
					left: `50%`,
					width: `${uni.upx2px(750 / 2 - 40 - 20)}px`,
					height: "wrap_content"
				}
			},
			{
				tag: "font",
				id: "infoFont",
				text: description,
				textStyles: { size: `${uni.upx2px(28)}px`, color: "#7B7B7B", align: "left", overflow: "ellipsis" },
				position: {
					top: `${uni.upx2px(117)}px`,
					left: `${uni.upx2px(80)}px`,
					width: `${uni.upx2px(670 - 40 - 10)}px`,
					height: "wrap_content"
				}
			}
		]);
	}
	// 简易向下出现动画
	animationShow() {
		this.view.show();
		this.view.setStyle({
			...this.viewStyle,
			top: `${this.offsetTop++}px`
		});
		if (this.offsetTop >= this.endTop) {
			this.status = "active";
			return;
		}
		setTimeout(() => {
			this.animationShow();
		}, 0);
	}
	// 简易向上消失动画
	animationHide() {
		this.view.setStyle({
			...this.viewStyle,
			top: `${this.offsetTop--}px`
		});
		if (this.offsetTop <= this.startTop) {
			this.view.close();
			this.hiding = false;
			this.status = "close";
			return;
		}
		setTimeout(() => {
			this.animationHide();
		}, 0);
	}
	// 获取当前状态
	getStatus() {
		return this.status;
	}
	// 不用动画,直接消失
	hide() {
		this.view.hide();
		this.view.close();
	}
}

// 对外暴露一个创建实例的方法
export function createAlarm(item, cb) {
	return new NativeMsg(item, cb);
}

简单描述过程

createAlarm 方法创建并返回一个 NativeMsg 的实例。

NativeMsg 类创建弹窗过程:

  1. 生成Bitmap背景图片对象 ,并加载。 方法: loadBg (异步)
  2. 在加载完成背景图片的回调里面创建弹窗区域以及把背景图画上去方法: create
  3. 区域和背景画好之后根据传递来的 item 画内容,以及绑定点击 click 事件和滑动 touch 事件。
    1. 点击事件:
      1. 点击设置 this.hiding 状态为真,防止重复触发
      2. 执行回调 this.cb
      3. 关闭弹窗 animationHide 向上消失效果
    2. 滑动事件
      1. touchstart 记录当前 this.clientY = res.clientY;
      2. touchmove 对比当前 clientYthis.clientY 之间距离,达到一定距离触发收起事件,和点击事件差不多,区别在于回调传递的 type 不同。
  4. 根据UI设计把内容话上去 drawInfo
  5. 以动画的方式显示弹窗 animationShow

使用

import { createAlarm } from "@/utils/nativeMsg";
// ...
// 接收到消息
function onReceived(data) {
		// #ifdef APP-PLUS
		// 调用
			showAlarmMsg(message);
		// #endif
}

// 定义一个全局变量保存 NativeMsg 实例
let alarmMsgInstance = null;
function showAlarmMsg(msg) {
	console.log(" ====> msg", msg);
	// #ifdef APP-PLUS
  //	业务需求只保留一个弹窗, 当存在的时候要干掉
	if (alarmMsgInstance && alarmMsgInstance.getStatus() === "active") {
		let _oldIns = alarmMsgInstance;
		alarmMsgInstance = null;
  //延迟300 等新的弹窗出来后再消失,视觉上比较好看
		setTimeout(() => {
			_oldIns.hide();
		}, 300);
	}
	// 创建一个新的弹窗(新的会覆盖在旧的上面)
	alarmMsgInstance = createAlarm(item, res => {
		// 这里是点击或者滑动的回调 点击type是‘click’,滑动是‘move’
    // result 是传递过去的msg,原封不动的返回过来了。
		const { type, result } = res;
		if (type === "move") return;
		uni.navigateTo({
			url: `/pages/xxxxxx?id=${result.id}`
		});
	});
	// #endif
	// #ifndef APP-PLUS
	console.log(" ====> 收到消息", msg);
	// #endif
}

为什么不做成单例模式?

一开始是打算弄成单例模式,之后发现第二条消息来的时候动画效果不太满意,所以改成现在这种可以创建多个,等第二个出来之后在把底下那个干掉。

SubNvues原生子窗体?

需要挂载在某个页面下,场景不太适合

真机效果

Android 真机实测无啥问题。

Ios 受限于设备问题,暂无法测试


2023年5月17日
特意做了个demo gitHub传送门

  • 32
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 33
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值