HarmonyOS 评论回复弹窗最佳实践

前言

在移动应用开发中,评论回复功能是一个常见且重要的交互场景。本文将详细介绍如何在 HarmonyOS 中实现一个功能完善的评论回复弹窗,包括弹窗选型、富文本编辑、软键盘适配等关键技术点。

功能概述

我们要实现的评论回复弹窗具备以下功能:

  • 支持文字输入
  • 支持表情选择
  • 支持@好友功能
  • 软键盘与表情面板无缝切换
  • 良好的用户体验

技术选型分析

弹窗组件选型

在开始开发之前,我们需要选择合适的弹窗实现方案。HarmonyOS 提供了多种弹窗实现方式,我们对比了三种主要方案:

通过对CustomDialog自定义弹窗、bindSheet半模态弹窗、Navigation Dialog三种弹窗方案进行尝试,发现自定义弹窗和半模态弹窗有一定规格限制,会产生一些无法避免的问题,最终选用Navigation Dialog方案实现评论模块弹窗。以下对三种方案优劣势进行一个详细的说明。

方案一:CustomDialog 自定义弹窗

CustomDialog 是 HarmonyOS 提供的标准弹窗组件。

优势:

  • ✅ 开箱即用,无需实现弹窗交互逻辑
  • ✅ 自动避让软键盘,使用简单
  • ✅ 系统级组件,稳定性好

劣势:

  • ❌ 软键盘避让行为无法自定义配置
  • ❌ 表情面板切换时会出现短暂的布局跳动
  • ❌ 无法获取软键盘动画信息,难以实现平滑过渡

问题演示: 当用户点击表情按钮时,软键盘收起过程中表情面板会短暂显示在错误位置,影响用户体验。

CustomDialog 问题演示

注意: PromptAction.openCustomDialog 与 CustomDialog 效果相同,存在同样的问题。

方案二:bindSheet 半模态弹窗

bindSheet 是 HarmonyOS 提供的半模态弹窗组件,常用于底部弹出的交互场景。

优势:

  • ✅ 开箱即用,无需实现弹窗交互逻辑
  • ✅ 可以解决 CustomDialog 中的软键盘顶起问题
  • ✅ 支持手势拖拽,交互体验较好

劣势:

  • ❌ 高度自适应时内部滚动行为难以控制
  • ❌ 即使禁用拖拽条,仍可拖动弹窗
  • ❌ 拖动过程中可能暴露表情面板,影响视觉效果

问题演示: 当禁用拖拽条后,用户仍可以拖动弹窗,这会在拖动过程中暴露底层的表情面板区域。

bindSheet 问题演示

方案三:Navigation Dialog(推荐方案)

Navigation Dialog 基于 Navigation 路由系统实现的弹窗方案。

优势:

  • ✅ 完美解决前两种方案的所有问题
  • ✅ 基于路由栈管理,弹窗与 UI 完全解耦
  • ✅ 可精确控制软键盘避让行为
  • ✅ 支持复杂的弹窗层级管理

劣势:

  • ❌ 需要手动实现遮罩层和点击关闭逻辑
  • ❌ 开发复杂度相对较高

重要提醒: Navigation Dialog 的 z 轴层级较低,如果项目中同时使用多种弹窗方案,建议统一使用 Navigation Dialog 以避免层级冲突。

最终选择

经过综合对比,我们选择 Navigation Dialog 作为最终方案,主要原因:

  1. 完美的软键盘控制:可以精确控制软键盘避让行为,实现平滑的切换动画
  2. 良好的架构设计:基于路由的设计更符合现代应用架构理念
  3. 可扩展性强:便于后续功能扩展和维护

虽然开发复杂度稍高,但带来的用户体验提升是值得的。

编辑区域组件选型

评论输入框需要支持多种内容类型:

  • 📝 文字输入:普通文本内容
  • 😊 表情符号:图片形式的表情
  • 👥 @好友功能:特殊样式的用户标签
RichEditor 组件介绍

对于这种图文混排的需求,HarmonyOS 提供的 RichEditor 组件是最佳选择。它支持:

  • 富文本编辑:文字、图片、自定义组件混合编辑
  • 灵活的内容管理:通过不同的 Span 类型管理内容
  • 丰富的交互事件:输入、删除、选择等事件监听
内容类型与实现方法

RichEditor 提供了三种主要的内容添加方法:

内容类型实现方法用途
文字addTextSpan普通文本内容
图片addImageSpan表情图片
自定义组件addBuilderSpan@好友标签

术语说明: 为方便理解,我们将通过这三种方法添加的内容分别称为 textSpanimageSpanbuilderSpan

@好友功能实现方案对比

对于 @好友功能,我们有两种实现方案可选:

方案一:使用 addTextSpan 实现

将 @好友 作为普通文本处理。

问题分析:

  • 文本合并问题:前后输入的文字会自动与 @好友 文本合并,破坏标签的独立性
  • 交互复杂:需要手动处理光标定位和整体删除逻辑
  • 数据关联困难:只能获取昵称文本,无法关联用户的完整信息(如 ID、头像等)
方案二:使用 addBuilderSpan 实现(推荐)

将 @好友 作为自定义组件处理。

优势分析:

  • 独立性好:不会与前后文字合并,保持标签完整性
  • 交互简单:系统自动处理光标和删除逻辑
  • 数据丰富:可以维护完整的用户信息,便于后续处理

注意事项:

  • 需要手动维护 builderSpan 的信息,但这也带来了更大的灵活性
最终选择

我们选择 addBuilderSpan 方案,主要考虑:

  1. 更好的用户体验:@好友 标签作为整体,交互更自然
  2. 更强的扩展性:可以轻松添加头像、样式等丰富元素
  3. 更可靠的数据管理:完整的用户信息便于业务处理

核心功能实现

1. 弹窗显示实现

功能流程

评论弹窗的显示流程如下:

  1. 用户在视频页面点击消息按钮
  2. 弹出评论列表页面
  3. 用户点击写评论按钮
  4. 弹出评论输入弹窗

弹窗显示流程

技术实现要点
1. Navigation 配置
// 主页面 Navigation 配置
Navigation() {// 页面内容
}
.mode(NavigationMode.Stack)  // 设置为栈模式
.hideTitleBar(true)         // 隐藏标题栏
2. 弹窗组件结构
// 弹窗页面组件
@Component
struct CommentDialog {build() {NavDestination() {Stack() {// 遮罩层Column().width('100%').height('100%').backgroundColor('rgba(0,0,0,0.5)').onClick(() => {// 点击遮罩关闭弹窗router.back()})// 弹窗内容Column() {// 评论输入组件}.backgroundColor(Color.White).borderRadius(12)}}.mode(NavDestinationMode.DIALOG)  // 设置为弹窗模式.expandSafeArea([SafeAreaType.KEYBOARD])  // 不避让软键盘}
}
3. 关键配置说明
配置项作用重要性
NavigationMode.Stack启用路由栈管理⭐⭐⭐
NavDestinationMode.DIALOG设置为弹窗类型⭐⭐⭐
expandSafeArea([SafeAreaType.KEYBOARD])不避让软键盘⭐⭐⭐
遮罩层点击事件提供关闭交互⭐⭐
弹窗管理策略
  • 弹出:通过 router.pushUrl() 进入路由栈
  • 关闭:通过 router.back() 退出路由栈
  • 层级:路由栈的顺序决定弹窗层级关系

2. 软键盘和表情面板切换适配

功能需求

在评论弹窗中,用户需要能够在软键盘和表情面板之间无缝切换,提供良好的输入体验。

软键盘表情切换

技术实现方案
1. 自定义键盘控制

本文选择自定义键盘来控制软键盘和表情面板的切换:

  • 显示表情面板:设置 RichEditor.customKeyboard 为表情面板组件的构建函数 EmojiKeyboard
  • 显示软键盘:设置 customKeyboard 属性为 undefined
  • 焦点管理:通过这种方式切换时无需手动处理 RichEditor 焦点
2. 高度适配策略

为保证切换过程中评论模块整体高度不变,需要实现以下逻辑:

软键盘高度监听:

// 监听软键盘高度变化
window.on('keyboardHeightChange', (height: number) => {if (height > 0) {this.keyboardHeight = height;}
});

高度计算规则:

  • 表情面板高度 = 常用表情列表高度 + 软键盘高度
  • 占位元素高度 = 当前显示组件的高度(软键盘或表情面板)
3. 布局适配实现

由于弹窗设置了不避让软键盘,需要通过占位元素来控制布局:

// 占位元素高度控制
@State placeholderHeight: number = 0;// 切换到软键盘时
this.placeholderHeight = this.keyboardHeight;// 切换到表情面板时  
this.placeholderHeight = this.emojiPanelHeight + this.keyboardHeight;
注意事项
  • ⚠️ 内存管理:组件销毁前必须取消键盘高度监听事件
  • ⚠️ 高度变化:软键盘高度可能被用户手动调整,需要实时监听
  • ⚠️ 时序控制:切换过程中要确保高度设置的时序正确

3. @好友功能实现

功能概述

@好友功能允许用户在评论中提及其他用户,被@的用户会收到通知,这是社交应用中的重要功能。

触发方式

用户可以通过两种方式触发@好友功能:

  1. 点击@按钮:直接点击编辑区域的@按钮
  2. 键盘输入:在软键盘上输入@符号

实现流程
1. 触发@功能

点击@按钮时:

// 添加@符号并显示好友列表
this.richEditorController.addTextSpan('@', {style: {fontColor: Color.Blue}
});
this.showFriendList = true;

监听键盘输入:

// 监听输入事件,统一处理@符号
.aboutToIMEInput((value: RichEditorInsertValue) => {if (value.insertValue === '@') {// 触发@好友逻辑this.showFriendList = true;return true; // 阻止默认输入}return false;
})

通过 RichEditorController.addTextSpan 添加@符号,并显示好友列表。同时监听 RichEditor.aboutToIMEInput 事件,统一处理点击@按钮和键盘输入@的逻辑。

在好友列表中点击好友头像时,通过RichEditorController.getSpans可以获取光标前一个span的内容,若光标前一个span是内容为@的textSpan,则先删除,然后通过RichEditorController.addBuilderSpan将“@[好友昵称]”以指定的样式作为一个整体添加到编辑区域中。

4. 内容删除功能

功能需求

在删除@好友内容时,需要实现智能删除:第一次点击删除键时选中整个@好友组件,第二次点击时整体删除,而不是逐字符删除。

删除功能演示

实现方案
// 监听删除事件
.aboutToDelete((value: RichEditorDeleteValue) => {// 获取要删除的span信息const spans = this.richEditorController.getSpans(value.offset, value.offset + value.length);if (spans.length > 0) {const span = spans[0];// 如果是builderSpan(@好友)且未被选中if (span.spanType === 'builderSpan' && !this.isSpanSelected(span)) {// 第一次删除:选中整个@好友组件this.richEditorController.setSelection(span.start, span.end);return false; // 阻止默认删除行为}}return true; // 允许默认删除行为
})
技术要点
功能API说明
删除监听aboutToDelete()监听删除操作,可阻止默认行为
内容选中setSelection()选中指定范围的内容
获取内容getSpans()获取指定位置的span信息

通过 RichEditor.aboutToDelete 事件监听删除操作,使用 RichEditorController.setSelection 实现@好友组件的整体选中和删除。

5. 内容获取与展示

功能概述

当用户完成评论编辑后,需要获取编辑区域的所有内容(文字、表情、@好友),并进行统一的数据处理和展示。

内容展示效果

数据类型映射

通过 RichEditorController.getSpans 获取编辑区域内容,返回值包含 RichEditorTextSpanResult 和 RichEditorImageSpanResult 两种类型。

不同内容类型与数据类型的对应关系:

textSpan可通过RichEditorTextSpanResult.value获取文字内容。imageSpan可通过RichEditorImageSpanResult.valueResourceStr获取图片资源。但是builderSpan在RichEditorImageSpanResult中获取不到任何相关的内容信息,所以在点击好友头像添加@好友内容时需要手动将这些builderSpan进行维护。

实际开发中编辑区域不同类型的内容往往需要一种统一的数据结构来表达,方便传输和存储。该数据结构需要不仅能对编辑区域内容进行记录,也需要有携带一些额外信息的能力,比如携带@好友相关的用户信息。本文定义为RichEditorSpan。(实际开发中需要的属性字段根据需求灵活调整)。

使用RichEditorSpan[]类型的数组builderSpans来维护@好友时的builderSpan,需要注意的是要保证每个builderSpan在数组中的顺序要与实际内容中出现的顺序一致。在添加builderSpan时,通过计算当前光标位置前面builderSpan的个数,来确定添加到builderSpans数组中的位置,并把需要携带的好友信息放入data属性中。

发送评论时,将获取到的内容用RichEditorSpan[]类型的数组richEditorSpans进行统一地表达。通过getSpans获取所有内容,如果是textSpan,通过value属性取出文字内容,设置RichEditorSpan.type为text,如果是imageSpan,通过valueResourceStr属性获取图片资源,设置RichEditorSpan.type为image。如果是builderSpan,按顺序从数组builderSpans中获取,并将他们按顺序添加到richEditorSpans中。

最终生成的richEditorSpans数据格式如下:

当需要展示评论内容时,只需要对richEditorSpans进行遍历,根据type属性,分别对文字、表情、@好友进行展示逻辑的处理。具体展示形式开发者根据实际需求确定。

  • 选择图片

    点击图片按钮拉起系统相册,选择本地图片进行上传。该功能使用场景相对独立,本文不详细介绍。开发者需要进一步了解详情,可参考以下sample。

    • 选择并查看文档和媒体文件
    • 文件管理
    • 发布图片评论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/84281.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/84281.shtml
英文地址,请注明出处:http://en.pswp.cn/web/84281.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git 回退操作详解:带示例的“小白”指南

前言 在日常开发中,我们难免会遇到: 改错代码:推送之前才发现某些行根本就不该动提交错误:commit 信息打错、提交到错误分支想回到之前版本:测试时发现之前版本是好的,需要回去查看 这就需要用到 Git 的…

redux以及react-redux

1.redux案例完整版 上一篇文章我们是没有action文件,直接在countre组件与store以及reducer直接进行状态的改变以及展示。 下面我们加上action文件,我们就不能直接通过dispatch传,而是通过调用action里面的函数讲我们传入的参数变成action这种…

idea中配置svn及提交提示“未检测到更改”提示

首先要安装TortoiseSVN,选command line client tools; 在idea中,文件->设置->Subversion->如下图 文件->设置->目录映射->如下图 初次导入到svn, 输入服务器上的svn地址,等待成功即可;…

vue 实现dot-dropdown

<template><div class"app-container"><div class"mt30"></div><el-row :gutter"20"><!-- title --><!-- <div class"modt-box">导航管理</div> --><el-col :span"2&q…

使用 mysql2/promise 模块返回以后,使用 await 返回数据总结

SELECT 返回结构 const [rows, fields] await db.query(SELECT * FROM folders);返回&#xff1a; rows: 是一个数组&#xff0c;包含所有查到的记录。fields: 是字段的结构定义&#xff08;列信息&#xff09;&#xff0c;一般不用。 rows 是一个数组&#xff0c;包含所有…

Manus Metagloves pro高精度+无漂移+低延迟 ,重构VR/XR手部交互方式

manus metagloves pro是一款专为动画制作、虚拟现实及游戏开发打造的高精度无线动作捕捉手套。采用先进的Quantum追踪技术&#xff0c;实现毫米级动作捕捉&#xff0c;精准还原手指细节&#xff0c;显著提升创作效率与交互真实感。 MANUS Metagloves Pro解锁动捕 / 机器人 / XR…

Uniapp插件改造指南:如何让vue-plugin支持HarmonyOS5原生能力?

一、分层架构设计 采用通用逻辑与平台实现分离的三层结构&#xff1a; uni-plugin-harmony ├── common # 跨平台通用层 │ ├── interfaces # 能力接口抽象&#xff08;如Scanner.ets&#xff09; │ └── utils # 工具类 ├── harmony …

P1040 [NOIP 2003 提高组] 加分二叉树 题解

题目描述 设一个 n n n 个节点的二叉树 tree \text{tree} tree 的中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,…,n)&#xff0c;每个节点都有一个分数&#xff08;均为正整数&#xff09;。任一棵子树 subtree \text{subtree} subtree&#xff08;包含 tr…

【Golang面试题】Data Race 问题怎么检测?

Go Race Detector 深度指南&#xff1a;原理、用法与实战技巧 一、什么是数据竞争&#xff1f; 在并发编程中&#xff0c;数据竞争发生在两个或多个 goroutine 同时访问同一内存位置&#xff0c;且至少有一个是写操作时。这种竞争会导致不可预测的行为和极其难以调试的问题。…

257. 二叉树的所有路径(js)

257. 二叉树的所有路径——DFS 回溯&#xff08;js&#xff09; 题目描述解题思路完整代码时间复杂度分析 题目描述 257. 二叉树的所有路径 解题思路 题意理解 给定一棵二叉树&#xff0c;要求返回所有从根节点到叶子节点的路径&#xff0c;路径以字符串形式表示&#xff0c…

自动化文档生成工具(亲测可运行)

本文介绍了一个用Java编写的自动化文档生成工具&#xff0c;通过读取开发清单文本自动生成格式规范的Word文档。该工具的主要特点包括&#xff1a; 采用Apache POI库处理Word文档&#xff0c;支持多级标题和段落自动生成实现中文数字转换功能&#xff0c;将编号转换为"一、…

湖北理元理律师事务所债务优化模型:法律与生活的平衡之道

在债务重组领域&#xff0c;专业机构需同时解决两个矛盾&#xff1a;法律合规性与债务人可持续生存能力。湖北理元理律师事务所通过“三维干预模型”&#xff0c;在武汉某餐饮连锁企业债务危机中验证了该方案的有效性。 一、法律底层设计&#xff1a;还款方案的合法性审查 以该…

Web3-代币ERC20/ERC721以及合约安全溢出和下溢的研究

Web3-代币ERC20/ERC721以及合约安全溢出和下溢的研究 以太坊上的代币 如果你对以太坊的世界有一些了解&#xff0c;你很可能听人们聊过代币— ERC20代币 一个 代币 在以太坊基本上就是一个遵循一些共同规则的智能合约——即它实现了所有其他代币合约共享的一组标准函数&…

论文笔记 <交通灯><多智能体>MetaLight:基于价值的元强化学习用于交通信号控制

今天看的论文是这篇MetaLight:基于价值的元强化学习用于交通信号控制 里面提到的创新点就是MetaLight框架&#xff1a;他目标是让交通信号控制智能体&#xff08;Agent&#xff09;在新路口&#xff08;即使结构或流量模式不同&#xff09;上能​​快速学习​​&#xff08;Few…

华为OD-2024年E卷-寻找符合要求的最长子串[200分] -- python

问题描述&#xff1a; 给定一个字符串s&#xff0c;找出这样一个子串: 1)该子串中的任意一个字符最多出现2次; 2)该子串不包含指定某个字符; 请你找出满足该条件的最长子串的长度。 输入描述 第一行为要求不包含的指定字符&#xff0c;为单个字符&#xff0c;取值范围[0-9a-zA…

CppCon 2016 学习:What C++ Programmers Need to Know about Header <random>

随机数生成的历史背景 Middle-Square 方法&#xff08;中位平方法&#xff09;&#xff1a; 已知最早的随机算法之一或由修道士 Brother Edvin 在 1245 年发明由 John von Neumann 在 1949 年重新发现缺点明显&#xff0c;但执行速度快 Monte Carlo 方法&#xff1a; 起初是…

Origin:误差棒点线图绘制

1.首先将你的数据复制到表格 2.选中B(y)列数据&#xff0c;依次点击图示选项 3.选中图中红框数据&#xff0c;点击绘制点线图即可 4.结果展示

Spring 源码学习 1:ApplicationContext

Spring 源码学习 1&#xff1a;ApplicationContext Bean 定义和 Bean 实例 AnnotationConfigApplicationContext 首先&#xff0c;创建一个最简单的 Spring Boot 应用。 在入口类中接收SpringApplication.run的返回值&#xff1a; SpringBootApplication public class Dem…

CppCon 2017 学习:Design Patterns for Low-Level Real-Time Rendering

这段内容讲的是离散显卡&#xff08;Discrete GPU&#xff09;中的内存管理模型&#xff0c;重点是CPU和GPU各自独立管理自己的物理内存&#xff0c;以及它们如何通过虚拟内存和DMA引擎实现高效通信。以下是详细的理解和梳理&#xff1a; 1. 基本概念 CPU 和 GPU 是两个独立的…

【单调队列】-----【原理+模版】

单调队列 一、什么是单调队列&#xff1f; 单调队列是一种在滑动窗口或区间查询中维护候选元素单调性的数据结构&#xff0c;通常用于解决“滑动窗口最大值/最小值”等问题。 核心思想是&#xff1a;利用双端队列&#xff08;deque&#xff09;维护当前窗口内或候选范围内元素…