HarmonyOS 評論回復彈窗最佳實踐
前言
在移動應用開發中,評論回復功能是一個常見且重要的交互場景。本文將詳細介紹如何在 HarmonyOS 中實現一個功能完善的評論回復彈窗,包括彈窗選型、富文本編輯、軟鍵盤適配等關鍵技術點。
功能概述
我們要實現的評論回復彈窗具備以下功能:
- 支持文字輸入
- 支持表情選擇
- 支持@好友功能
- 軟鍵盤與表情面板無縫切換
- 良好的用戶體驗
技術選型分析
彈窗組件選型
在開始開發之前,我們需要選擇合適的彈窗實現方案。HarmonyOS 提供了多種彈窗實現方式,我們對比了三種主要方案:
通過對CustomDialog自定義彈窗、bindSheet半模態彈窗、Navigation Dialog三種彈窗方案進行嘗試,發現自定義彈窗和半模態彈窗有一定規格限制,會產生一些無法避免的問題,最終選用Navigation Dialog方案實現評論模塊彈窗。以下對三種方案優劣勢進行一個詳細的說明。
方案一:CustomDialog 自定義彈窗
CustomDialog 是 HarmonyOS 提供的標準彈窗組件。
優勢:
- ? 開箱即用,無需實現彈窗交互邏輯
- ? 自動避讓軟鍵盤,使用簡單
- ? 系統級組件,穩定性好
劣勢:
- ? 軟鍵盤避讓行為無法自定義配置
- ? 表情面板切換時會出現短暫的布局跳動
- ? 無法獲取軟鍵盤動畫信息,難以實現平滑過渡
問題演示: 當用戶點擊表情按鈕時,軟鍵盤收起過程中表情面板會短暫顯示在錯誤位置,影響用戶體驗。
注意: PromptAction.openCustomDialog 與 CustomDialog 效果相同,存在同樣的問題。
方案二:bindSheet 半模態彈窗
bindSheet 是 HarmonyOS 提供的半模態彈窗組件,常用于底部彈出的交互場景。
優勢:
- ? 開箱即用,無需實現彈窗交互邏輯
- ? 可以解決 CustomDialog 中的軟鍵盤頂起問題
- ? 支持手勢拖拽,交互體驗較好
劣勢:
- ? 高度自適應時內部滾動行為難以控制
- ? 即使禁用拖拽條,仍可拖動彈窗
- ? 拖動過程中可能暴露表情面板,影響視覺效果
問題演示: 當禁用拖拽條后,用戶仍可以拖動彈窗,這會在拖動過程中暴露底層的表情面板區域。
方案三:Navigation Dialog(推薦方案)
Navigation Dialog 基于 Navigation 路由系統實現的彈窗方案。
優勢:
- ? 完美解決前兩種方案的所有問題
- ? 基于路由棧管理,彈窗與 UI 完全解耦
- ? 可精確控制軟鍵盤避讓行為
- ? 支持復雜的彈窗層級管理
劣勢:
- ? 需要手動實現遮罩層和點擊關閉邏輯
- ? 開發復雜度相對較高
重要提醒: Navigation Dialog 的 z 軸層級較低,如果項目中同時使用多種彈窗方案,建議統一使用 Navigation Dialog 以避免層級沖突。
最終選擇
經過綜合對比,我們選擇 Navigation Dialog 作為最終方案,主要原因:
- 完美的軟鍵盤控制:可以精確控制軟鍵盤避讓行為,實現平滑的切換動畫
- 良好的架構設計:基于路由的設計更符合現代應用架構理念
- 可擴展性強:便于后續功能擴展和維護
雖然開發復雜度稍高,但帶來的用戶體驗提升是值得的。
編輯區域組件選型
評論輸入框需要支持多種內容類型:
- 📝 文字輸入:普通文本內容
- 😊 表情符號:圖片形式的表情
- 👥 @好友功能:特殊樣式的用戶標簽
RichEditor 組件介紹
對于這種圖文混排的需求,HarmonyOS 提供的 RichEditor 組件是最佳選擇。它支持:
- 富文本編輯:文字、圖片、自定義組件混合編輯
- 靈活的內容管理:通過不同的 Span 類型管理內容
- 豐富的交互事件:輸入、刪除、選擇等事件監聽
內容類型與實現方法
RichEditor 提供了三種主要的內容添加方法:
內容類型 | 實現方法 | 用途 |
---|---|---|
文字 | addTextSpan | 普通文本內容 |
圖片 | addImageSpan | 表情圖片 |
自定義組件 | addBuilderSpan | @好友標簽 |
術語說明: 為方便理解,我們將通過這三種方法添加的內容分別稱為
textSpan
、imageSpan
、builderSpan
。
@好友功能實現方案對比
對于 @好友功能,我們有兩種實現方案可選:
方案一:使用 addTextSpan 實現
將 @好友 作為普通文本處理。
問題分析:
- ? 文本合并問題:前后輸入的文字會自動與 @好友 文本合并,破壞標簽的獨立性
- ? 交互復雜:需要手動處理光標定位和整體刪除邏輯
- ? 數據關聯困難:只能獲取昵稱文本,無法關聯用戶的完整信息(如 ID、頭像等)
方案二:使用 addBuilderSpan 實現(推薦)
將 @好友 作為自定義組件處理。
優勢分析:
- ? 獨立性好:不會與前后文字合并,保持標簽完整性
- ? 交互簡單:系統自動處理光標和刪除邏輯
- ? 數據豐富:可以維護完整的用戶信息,便于后續處理
注意事項:
- 需要手動維護 builderSpan 的信息,但這也帶來了更大的靈活性
最終選擇
我們選擇 addBuilderSpan 方案,主要考慮:
- 更好的用戶體驗:@好友 標簽作為整體,交互更自然
- 更強的擴展性:可以輕松添加頭像、樣式等豐富元素
- 更可靠的數據管理:完整的用戶信息便于業務處理
核心功能實現
1. 彈窗顯示實現
功能流程
評論彈窗的顯示流程如下:
- 用戶在視頻頁面點擊消息按鈕
- 彈出評論列表頁面
- 用戶點擊寫評論按鈕
- 彈出評論輸入彈窗
技術實現要點
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. 觸發@功能
點擊@按鈕時:
// 添加@符號并顯示好友列表
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。
- 選擇并查看文檔和媒體文件
- 文件管理
- 發布圖片評論