在软件开发过程中,我们经常会遇到各种各样的需求。比如,题目中提到的在应用中显示通知,这类需求实际上是项目需要与微信保持一致的特殊规定。这样的要求背后,蕴含着众多技术挑战和考量,同时也对项目的整体使用感受有着重要影响。
系统通知与应用内通知的差异
我们平时都习惯了系统通知的出现。但在这个项目中,我们需要在应用内部显示通知。系统自带的提醒在应用界面不会直接显示。不过,由于特定需求,我们不得不打破这一常规。如果用户感受到这种变化处理不当,可能会影响他们的使用体验。比如,某些新闻应用在用户阅读文章时突然弹出通知,可能会干扰阅读。同样,在办公应用中,如果在处理数据时突然出现通知,也可能打乱工作流程。对于开发者来说,要满足这一需求,必须完全放弃系统通知的常规做法。
我们需要注意各个平台的具体情况,例如iOS和安卓系统。在某些功能上,安卓系统可以轻松实现,但在iOS系统上可能遇到障碍,这是因为iOS的开发受到较多限制。
自定义组件解决方式的缺点
项目初期,我们采用自定组件来处理应用内的通知问题。但这种方法存在两个显著不足。首先,可能存在兼容性问题,某些手机型号或低版本系统可能无法正常使用这些组件。比如,一些旧款手机可能就无法正常显示自定义组件发出的通知。其次,性能也可能是问题之一,例如,可能会消耗过多内存或降低应用整体运行速度。如果手机内存本身就有限,再运行其他大型应用后,使用自定义组件通知可能会导致手机运行缓慢。
原生插件方式的尝试
针对自定义组件的缺陷,我们尝试采用原生插件技术,这种做法具有一定的创新点。我们借鉴了一些成功的做法,比如运用特定的Toast类,并结合独特的技术手段来模拟系统通知。这样做无需通知权限,可以完成一些基础功能。但这种方法也有其限制,并不适合完全取代系统通知,仅适用于应用内部的通知场景。此外,它的功能也相对不全面,若要实现更复杂的通知效果,可能无法满足需求。例如,涉及多种交互的通知,如点击后弹出菜单等,可能就无法实现。
/*
* @Author: yuanyxh
* @Date: 2023-12-27 12:04:11
* @Last Modified by: yuanyxh
* @Last Modified time: 2023-12-27 12:04:11
*/
import Vue from "vue";
const ZERO = 0;
/** 最小左侧触摸开始位置 */
const MINIMUM_LEFT_DISTANCE = 100;
/** 最小移动距离 */
const MINIMUM_MOVE_DISTANCE = 50;
/** 最小间隔事件 */
const MINIMUM_TIME_INTERVAL = 100;
/**
*
* @callback OnSideSlipListener
* @param {number} sideTime 上次侧滑事件
*/
/**
*
* @description 侦听 ios 侧滑事件, 通过此工具配合 beforeRouteLeave 导航守卫判断是否侧滑返回
*/
const sideSlip = (function sideSlipListener(global) {
let startX = 0,
endX = 0;
let sideTime = 0;
const vm = Vue.prototype;
/** @type {OnSideSlipListener[]} */
const callbackList = [];
function start(e) {
const point = e.touches?.length ? e.touches[0] : e;
startX = point.pageX;
}
function end(e) {
const point = e.changedTouched?.length ? e.changedTouched[0] : e;
endX = point.pageX;
if (
startX >= ZERO &&
startX <= MINIMUM_LEFT_DISTANCE &&
endX < ZERO &&
vm.windowWidth - Math.abs(endX) > MINIMUM_MOVE_DISTANCE
) {
sideTime = Date.now();
callbackList.forEach((callback) => callback(sideTime));
}
}
function isWithinValidityPeriod(time) {
const interval = Date.now() - time;
return interval >= ZERO && interval < MINIMUM_TIME_INTERVAL;
}
if (vm.isIos) {
global.addEventListener("touchstart", start);
global.addEventListener("touchend", end);
global.addEventListener("touchcancel", end);
}
return {
/**
* @description 是否左滑
* @readonly
*/
get isSideSlip() {
if (vm.isIos) {
return isWithinValidityPeriod(sideTime);
}
return false;
},
/**
* @description 判断侧滑时间是否在有效期内
* @param {number} time 侧滑时间
* @returns {boolean} 是否有效
*/
isWithinValidityPeriod: isWithinValidityPeriod,
/**
*
* @description 侦听侧滑
* @param {OnSideSlipListener} callback
*/
on(callback) {
if (typeof callback === 'function') {
callbackList.push(callback);
}
},
/**
* @description 取消事件侦听
* @param {OnSideSlipListener} callback
*/
off(callback) {
const i = callbackList.indexOf(callback);
if (i === -1) return;
callbackList.splice(i, 1);
},
};
})(
(function() {
let global = {};
if (typeof window !== "undefined") {
return (global = window);
}
global = {
addEventListener() {},
removeEventListener() {},
};
return global;
})()
);
export default sideSlip;
iOS端的解决思路推测
iOS开发方面,由于经验不足,我们尚未找到确切的方法。不过,一个大致的方向是增加一个视图。从理论上讲,这个方法是有可能实现的。然而,iOS对应用视图的管理非常严格,新增的视图必须遵循苹果的设计标准。例如,社交类应用若不符合这些标准,可能无法通过审核。此外,还需确保新增视图不会干扰到其他视图的正常运作,比如在iPad的多任务模式下,新视图不能影响到其他任务的显示效果。
定时器相关的使用情况
项目中常用定时器来处理动画或视图变动。程序后台运行时,会主动停止定时任务,而回到前台则恢复基础定时任务。确保多端任务执行逻辑一致是关键。比如,H5和App端采用不同定时器方法。在游戏或交互应用开发中,这种方法至关重要。若处理不当,动画效果可能不稳定,影响用户体验。以一款休闲游戏为例,后台切换多次后回到前台,动画流畅度会因定时器处理方式不同而受影响。
软键盘高度获取的差异
在App上获取软键盘的高度相对容易,但在H5上则较为复杂。不同浏览器对软键盘弹出时的页面模式没有统一标准。幸运的是,主流浏览器都提供了相应的接口支持。然而,在实际开发过程中,一些侧重商务功能的软件可能受到较大影响。比如,那些需要频繁输入长文本的商务邮件应用,若无法准确判断软键盘高度,可能会影响用户的输入体验。对于前端开发者而言,为了兼容各种浏览器,往往需要编写更多的代码来应对各种不同的情况。
import Vue from "vue";
const DELAY_TIME = 300;
let callbackList = [];
let unimplementedChangeList = [];
function emit(target, payload) {
if (target.length) {
for (let i = 0; i < target.length; i++) {
target[i](payload);
}
}
}
// 页面原始高度
let windowHeight = 0;
function onKeyboardHeightChangeWithH5() {
let extraHeight = 0;
let hasFocus = false;
let originScrollY = 0;
let cancheHeight = 0;
let keyboardChangeTimer = null;
function exec(height) {
const keyboardHeight = Math.max(0, windowHeight - height);
if (cancheHeight === keyboardHeight) {
return;
}
cancheHeight = keyboardHeight;
const { isIos } = Vue.prototype;
emit(callbackList, {
extra: isIos ? extraHeight : 0,
height: keyboardHeight,
});
}
window.addEventListener(
"focus",
(e) => {
if (
e instanceof FocusEvent &&
(e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target.contenteditable)
) {
hasFocus = true;
originScrollY = window.scrollY;
setTimeout(() => {
hasFocus = false;
}, 600);
}
},
{ capture: true }
);
window.addEventListener(
"blur",
(e) => {
if (
e instanceof FocusEvent &&
(e.target instanceof HTMLInputElement ||
e.target instanceof HTMLTextAreaElement ||
e.target.contenteditable)
) {
hasFocus = true;
setTimeout(() => {
hasFocus = false;
}, DELAY_TIME);
}
},
{ capture: true }
);
window.addEventListener(
"scroll",
() => {
if (hasFocus) {
extraHeight = window.scrollY - originScrollY;
}
},
{ capture: true }
);
if (typeof window.visualViewport !== "undefined") {
return window.visualViewport.addEventListener("resize", (e) => {
if (hasFocus === false) {
return emit(unimplementedChangeList);
}
if (keyboardChangeTimer) {
clearTimeout(keyboardChangeTimer);
}
keyboardChangeTimer = setTimeout(() => {
exec(e.target.height);
}, DELAY_TIME);
});
}
window.addEventListener("resize", () => {
if (hasFocus === false) {
return emit(unimplementedChangeList);
}
if (keyboardChangeTimer) {
clearTimeout(keyboardChangeTimer);
}
keyboardChangeTimer = setTimeout(() => {
exec(window.innerHeight);
}, DELAY_TIME);
});
}
let isInited = false;
function initKeyboardHeightChangeListener() {
// #ifdef APP-PLUS
uni.onKeyboardHeightChange((res) => {
emit(callbackList, { extra: 0, height: res.height });
});
// #endif
// #ifdef H5
windowHeight = Vue.prototype.windowHeight;
onKeyboardHeightChangeWithH5();
// #endif
}
/**
* @callback OnKeyboardHeightChangeCallback
* @param {{ extra: number; height: number }} 键盘高度(ios 中还有底部块的高度)
*/
/**
* @typedef Options
* @type {Object}
* @property {boolean} reset app 端对 uni.onKeyboardHeightChange 重置侦听
*/
/**
*
* @description 侦听键盘高度变化事件, 对多端做兼容处理
* @param {OnKeyboardHeightChangeCallback} callback 键盘高度变化事件回调
* @param {Options} options 额外参数
*/
function onKeyboardHeightChange(callback, options = {}) {
const { isPc } = Vue.prototype;
if (isPc) {
return console.error(
"PC devices do not need to listen for soft keyboard height"
);
}
// #ifdef APP-PLUS
if (options.reset) {
isInited = false;
callbackList = [];
}
// #endif
if (typeof callback === "function") {
callbackList.push(callback);
}
if (isInited === false) {
isInited = true;
initKeyboardHeightChangeListener();
}
}
/**
*
* @description 移除事件侦听函数
* @param {OnKeyboardHeightChangeCallback} callback 需要移除的已注册函数
*/
function offKeyboardHeightChange(callback) {
if (typeof callback === "function") {
const index = callbackList.indexOf(callback);
if (index >= 0) callbackList.splice(index, 1);
}
}
/**
*
* @description H5 页面高度变化但未执行键盘高度变化事件时触发
* @param {OnKeyboardHeightChangeCallback} callback 需要添加的注册函数
*/
function onUnimplementedChange(callback) {
const { isPc } = Vue.prototype;
if (isPc) {
return console.error(
"PC devices do not need to listen for soft keyboard height"
);
}
if (typeof callback === "function") {
unimplementedChangeList.push(callback);
}
if (isInited === false) {
isInited = true;
initKeyboardHeightChangeListener();
}
}
/**
*
* @description H5 页面高度变化但未执行键盘高度变化事件时触发
* @param {OnKeyboardHeightChangeCallback} callback 需要添加的注册函数
*/
function offUnimplementedChange(callback) {
if (typeof callback === "function") {
const index = unimplementedChangeList.indexOf(callback);
if (index >= 0) unimplementedChangeList.splice(index, 1);
}
}
export default {
onKeyboardHeightChange,
offKeyboardHeightChange,
onUnimplementedChange,
offUnimplementedChange,
};
在进行项目开发过程中,大家是否遇到过不同平台间差异显著的难题?期待大家分享个人经历,同时也欢迎对这篇文章给予点赞和转发。