当前位置: 首页 > news >正文

鸿蒙应用开发实战【17】— bindPopup 气泡弹出与筛选面板实现

鸿蒙应用开发实战【17】— bindPopup 气泡弹出与筛选面板实现

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

号码助手首页有一个「全部/已选N ⌄」筛选按钮,点击后会弹出一个气泡面板,展示分类、卡号、状态三维筛选条件。这个功能使用 ArkUI 的bindPopup实现——它能将一个自定义的@Builder内容以气泡形式浮层显示在触发元素附近,不会遮挡整个屏幕。

本篇涵盖:bindPopup 基本用法、PopupOptions 参数详解、Placement 弹出位置、自定义 @Builder builder 内容、onStateChange 状态回调、Flex + FlexWrap 实现 chip 布局、三维筛选系统完整实现。


一、bindPopup 组件预览

图1:bindPopup 锚点定位原理 + Placement 九宫格方位枚举


二、bindPopup 概述

2.1 bindPopup vs bindSheet 的选择

特性bindPopupbindSheet
显示位置触发元素附近(气泡)屏幕底部
遮挡内容部分遮挡(小区域)遮挡大部分屏幕
适合内容少量选项(筛选、快捷操作)大量内容(列表选择)
关闭方式点击外部自动关闭下滑或点击完成关闭
号码助手使用首页筛选面板AddAppPage 卡号选择

2.2 基本语法

// 绑定在触发元素上Button('筛选').bindPopup(this.showPopup,{builder:this.PopupContent,// @Builder 方法placement:Placement.Bottom,// 弹出位置popupColor:'#FFFFFF',// 气泡背景色onStateChange:(e)=>{if(!e.isVisible){this.showPopup=false}}}).onClick(()=>{this.showPopup=!this.showPopup})

三、bindPopup 参数详解

3.1 完整参数

interfacePopupOptions{builder?:CustomBuilder// 自定义内容(@Builder 方法)placement?:Placement// 弹出方向popupColor?:ResourceColor// 气泡背景色autoCancel?:boolean// 点击外部是否自动关闭(默认 true)offset?:{x:number,y:number}// 弹出偏移onStateChange?:(e:{isVisible:boolean})=>void// 状态变化回调arrowOffset?:number// 箭头偏移showInSubWindow?:boolean// 是否在子窗口显示maskColor?:ResourceColor// 遮罩颜色enableArrow?:boolean// 是否显示箭头(默认 true)}

3.2 Placement 弹出位置

// 可用的 Placement 枚举值Placement.Top// 触发元素上方Placement.Bottom// 触发元素下方(号码助手使用)✅Placement.Left// 触发元素左侧Placement.Right// 触发元素右侧Placement.TopLeft// 上方偏左Placement.TopRight// 上方偏右Placement.BottomLeft// 下方偏左Placement.BottomRight// 下方偏右// ❌ 注意:Placement.BottomEnd 不存在!// 正确使用 Placement.Bottom + 通过 offset 微调位置

四、首页筛选面板完整实现

4.1 触发按钮

// HomePage.ets — 筛选按钮Row({space:3}){Text(this.activeFilterCount()>0?`已选${this.activeFilterCount()}`:'全部').fontSize(12).fontWeight(AppFonts.WEIGHT_SEMIBOLD).fontColor(this.activeFilterCount()>0?'#3E68E0':AppColors.TEXT_2)Text('⌄').fontSize(10).fontColor(this.activeFilterCount()>0?'#3E68E0':AppColors.TEXT_3)}.padding({left:11,right:11,top:6,bottom:6}).backgroundColor(this.activeFilterCount()>0?AppColors.PRIMARY_BG:AppColors.CARD_B).border({width:1,color:this.activeFilterCount()>0?'#4D4F7CFF':AppColors.LINE,radius:14})// ← bindPopup 绑定.bindPopup(this.showFilter,{builder:this.FilterPopoverBuilder,placement:Placement.Bottom,popupColor:'#FFFFFF',onStateChange:(e)=>{if(!e.isVisible){this.showFilter=false}// 点外部关闭时同步状态}}).onClick(()=>{this.showFilter=!this.showFilter})

4.2 @Builder 筛选面板内容

@BuilderprivateFilterPopoverBuilder(){Column(){// ── 分类筛选 ──Column(){Text('分类').fontSize(11).fontColor(AppColors.TEXT_3).fontWeight(AppFonts.WEIGHT_SEMIBOLD).alignSelf(ItemAlign.Start).margin({bottom:8})Flex({wrap:FlexWrap.Wrap}){ForEach(CATEGORIES,(cat:string,idx:number)=>{Text(cat).fontSize(12).fontWeight(AppFonts.WEIGHT_MEDIUM).fontColor(this.filterCatIdx===idx?'#FFFFFF':AppColors.TEXT_2).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.filterCatIdx===idx?AppColors.PRIMARY:AppColors.CARD_B).border({width:1,color:this.filterCatIdx===idx?'#00000000':AppColors.LINE,radius:14}).margin({right:6,bottom:6}).onClick(()=>{this.filterCatIdx=idx})},(cat:string)=>cat)}}.margin({bottom:14}).alignItems(HorizontalAlign.Start)// ── 卡号筛选 ──Column(){Text('卡号').fontSize(11).fontColor(AppColors.TEXT_3).fontWeight(AppFonts.WEIGHT_SEMIBOLD).alignSelf(ItemAlign.Start).margin({bottom:8})Flex({wrap:FlexWrap.Wrap}){// 「全部」选项Text('全部').fontSize(12).fontWeight(AppFonts.WEIGHT_MEDIUM).fontColor(this.filterCardIdx===0?'#FFFFFF':AppColors.TEXT_2).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.filterCardIdx===0?AppColors.PRIMARY:AppColors.CARD_B).border({width:1,color:this.filterCardIdx===0?'#00000000':AppColors.LINE,radius:14}).margin({right:6,bottom:6}).onClick(()=>{this.filterCardIdx=0})// 各卡号选项ForEach(this.cards,(card:CardEntity,idx:number)=>{Text(card.label).fontSize(12).fontWeight(AppFonts.WEIGHT_MEDIUM).fontColor(this.filterCardIdx===idx+1?'#FFFFFF':AppColors.TEXT_2).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.filterCardIdx===idx+1?AppColors.PRIMARY:AppColors.CARD_B).border({width:1,color:this.filterCardIdx===idx+1?'#00000000':AppColors.LINE,radius:14}).margin({right:6,bottom:6}).onClick(()=>{this.filterCardIdx=idx+1})},(card:CardEntity)=>`fc_${card.id??0}`)}}.margin({bottom:14}).alignItems(HorizontalAlign.Start)// ── 状态筛选 ──Column(){Text('状态').fontSize(11).fontColor(AppColors.TEXT_3).fontWeight(AppFonts.WEIGHT_SEMIBOLD).alignSelf(ItemAlign.Start).margin({bottom:8})Flex({wrap:FlexWrap.Wrap}){ForEach(STATUS_FILTERS,(s:string,idx:number)=>{Text(s).fontSize(12).fontWeight(AppFonts.WEIGHT_MEDIUM).fontColor(this.filterStatIdx===idx?'#FFFFFF':AppColors.TEXT_2).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(this.filterStatIdx===idx?AppColors.PRIMARY:AppColors.CARD_B).border({width:1,color:this.filterStatIdx===idx?'#00000000':AppColors.LINE,radius:14}).margin({right:6,bottom:6}).onClick(()=>{this.filterStatIdx=idx})},(s:string)=>s)}}.margin({bottom:12}).alignItems(HorizontalAlign.Start)// ── 底部按钮 ──Row({space:8}){Button('重置',{type:ButtonType.Normal}).layoutWeight(1).height(36).fontSize(12).fontColor(AppColors.TEXT).backgroundColor(AppColors.CARD_B).border({width:1,color:AppColors.LINE,radius:10}).onClick(()=>this.resetFilters())Button('完成',{type:ButtonType.Normal}).layoutWeight(1).height(36).fontSize(12).fontColor('#FFFFFF').borderRadius(10).linearGradient({angle:135,colors:[[AppColors.PRIMARY,0],[AppColors.PRIMARY_2,1]]}).backgroundColor(Color.Transparent).onClick(()=>{this.showFilter=false})}}.width(220).padding(14)}

五、Flex + FlexWrap 实现 Chip 布局

5.1 FlexWrap 自动换行

// Chip 标签自动换行布局(筛选选项数量不固定时很有用)Flex({wrap:FlexWrap.Wrap}){Text('分类1').margin({right:6,bottom:6})Text('分类2').margin({right:6,bottom:6})Text('分类3').margin({right:6,bottom:6})// 超过一行时自动换行}// 关键参数:// FlexWrap.Wrap:允许换行(默认 NoWrap 不换行)// margin.right:chip 之间的水平间距// margin.bottom:行与行之间的垂直间距

5.2 与 Row 的区别

// Row:固定单行,不自动换行(内容超出时裁剪)Row({space:6}){Text('分类1')Text('分类2')Text('分类3')}// Flex + wrap:多行自动换行(适合数量不固定的标签)Flex({wrap:FlexWrap.Wrap}){Text('分类1').margin(...)Text('分类2').margin(...)Text('分类3').margin(...)}

六、筛选状态管理

6.1 筛选条件计数

privateactiveFilterCount():number{letn=0if(this.filterCatIdx!==0)n++// 非「全部」时 +1if(this.filterCardIdx!==0)n++if(this.filterStatIdx!==0)n++returnn}

6.2 筛选重置

privateresetFilters():void{this.filterCatIdx=0this.filterCardIdx=0this.filterStatIdx=0this.showFilter=false// 重置后关闭面板}

6.3 三维过滤逻辑

privategetFilteredRows():AppRow[]{returnthis.rows.filter((r:AppRow)=>{constcatOk=this.filterCatIdx===0||r.binding.category===CATEGORIES[this.filterCatIdx]constcardOk=this.filterCardIdx===0||r.cardLabel===this.cards[this.filterCardIdx-1]?.labelconststatOk=this.filterStatIdx===0||r.binding.status===STATUS_FILTERS[this.filterStatIdx]returncatOk&&cardOk&&statOk})}

七、本篇小结

知识点核心要点
bindPopup气泡弹出,适合小型筛选面板
Placement.Bottom在触发元素下方弹出
onStateChange点击外部关闭时同步 @State
Flex + FlexWrap.Wrap标签自动换行(chip 布局)
筛选三维度分类/卡号/状态独立过滤,AND 关系

参考资料

  • 鸿蒙应用开发实战【05】— 首页开发与状态管理
  • 鸿蒙应用开发实战【16】— bindSheet 底部弹出面板
  • bindPopup API 参考
  • Placement 枚举
  • Flex 容器
  • FlexWrap 说明
  • @Builder 装饰器
  • Button 组件

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
  • bindPopup 文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-popup
  • Flex 容器文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-flex
http://www.jsqmd.com/news/1194780/

相关文章:

  • 实战教程:从零搭建自主查资料的AI智能体
  • MusicFree插件化音乐播放器:如何快速打造你的个性化免费音乐库
  • 现代C:标准库:字符、字符串处理与数学计算
  • 告别VBA!用ChatGPT新功能一键生成精美PPT(附实战案例)
  • 重测序基因组:Pi核酸多样性在群体遗传选择分析中的应用
  • 3分钟解锁Wand游戏修改器:开源增强工具的完整使用指南
  • 2026年低延迟Agent数据库哪家专业:实时查询引擎、数据新鲜度与AI场景适配深度解析 - 科技焦点
  • EmojiOne Color 终极免费彩色表情字体完整使用指南
  • “不得”“应当”“鼓励”三类模态词自动归类准确率提升至99.7%——基于ChatGPT-4o微调的政策语义解析引擎(仅限首批200家政务云平台内测)
  • Deep-Live-Cam终极指南:3分钟掌握实时AI换脸核心技术
  • 树莓派5+Hailo AI加速帽部署自定义YOLO模型实战
  • ZEQP WMS:如何快速搭建企业级开源仓库管理系统
  • 南京首饰怎么卖高价?添价收专业鉴定保留品牌溢价 - 分享测评官
  • ENScan_GO:5分钟快速掌握企业信息收集的终极工具
  • Windows笔记本续航焦虑终结者:EnergyStarX智能电池优化实战指南
  • Obsidian插件推荐:T0与T1级别必装清单及外置工具扩展
  • # 2026年天津劳动争议律师推荐 高延庆律师全程亲办案例扎实 - 本地品牌推荐
  • 终极指南:如何用Layerdivider 5分钟实现专业PSD智能分层
  • 告别静态沙盘值守|视频孪生活态感知,重塑边境岸线智能主动防御新体系
  • 长沙雨花区房屋修缮上门|维小达|专业墙面维修、窗户维修、吊顶维修、壁纸壁布、瓷砖维修、瓷砖美缝、石材修复等一站式房屋修缮服务 - 一点传媒
  • 美妆oem是什么意思?14年工厂老板告诉你:不懂这3个品控细节,代工就是交学费
  • BlindWatermark完整指南:Java图像盲水印的终极解决方案
  • FreeRTOS系列|内存管理五:heap_5实战与多区域堆配置
  • 2026 建材行业:一线瓷砖品牌盘点(十大品牌)
  • 3大核心功能深度评测:鸣潮工具箱如何重塑你的游戏体验
  • 数字化会务新范式:会助力智能会务系统一站式会议管理系统解决方案
  • 办房产证委托书公证 2026 年如何办理?线上办理方法分享 - 跑政通
  • 别让预算卡住GEO第一步:2026中小企业如何用低成本工具实现品牌AI可见性监测
  • C++与TwinCAT 3通过ADS通讯:原理、实战与性能优化指南
  • 免费AI音频分离:在Audacity中一键提取人声和伴奏的完整方案