
引言几乎每个移动端 App 都有一个底部面板——地图 App 点击标记后弹出地点详情卡片音乐 App 的播放控制条从底部滑出系统控制中心从屏幕底部上拉展开。这些交互有一个共同模式一个可以从底部拖拽的、多层级高度切换的面板。在 HarmonyOS ArkUI 中Panel组件专门用于实现这种交互。它支持三种内置模式Mini / Half / Full、可拖拽切换、自定义拖拽条、以及高度变化监听。你不需要手动处理手势、计算偏移量、管理动画——Panel 把这些都封装好了。本文将通过构建一个智能面板实验室深入讲解 Panel 组件的核心用法PanelMode三态切换、PanelType类型选择、dragBar拖拽条定制、onChange/onHeightChange事件监听以及自定义面板内容的条件渲染。读完本文你将能够使用PanelMode.Mini | Half | Full定义面板的三层高度使用PanelType.Foldable | Temporary | Mini控制面板的展示行为使用dragBar定制拖拽条的显示与样式使用onChange监听面板的模式切换拖拽或编程式使用onHeightChange追踪面板高度实时变化通过自定义布局 手势实现无内置 Panel 组件的等效方案用于理解底层原理Panel 组件概述什么是 PanelPanel是 ArkUI 提供的内置容器组件专门用于构建从屏幕底部弹出的可拖拽面板。它的核心设计是一个三态高度模型Mini最小化面板收缩到最小高度仅显示一行摘要信息——类似于音乐 App 底部的迷你播放条。Half半展开面板展开到半屏高度显示核心功能和快捷操作——类似于地图 App 的地点预览卡片。Full全展开面板展开到接近全屏的高度显示完整内容和列表——类似于系统设置面板。用户可以通过在面板上拖拽来切换这三种模式向上拖拽展开向下拖拽收缩。Panel 内置了手势识别、惯性动画、边界吸附等交互细节开发者只需要定义每种模式下的内容。与 BottomSheet 的区别如果你熟悉 ArkUI 的bindPopup或自定义 BottomSheet可能会觉得 Panel 与之类似。但 Panel 有本质区别特性PanelBottomSheet / bindPopup高度层级三档Mini / Half / Full单档固定高度或全屏拖拽切换内置手势支持需自行实现生命周期始终存在于组件树弹出时挂载关闭时卸载默认状态始终可见可设为 Mini默认隐藏触发时弹出适用场景持久性底部面板临时性弹窗/选择器Panel 更适合持久存在的底部面板——它始终在页面中只是以不同高度展示。BottomSheet 更适合临时触发的弹窗——需要时才出现关闭后完全消失。API 废弃说明在 API 20 中Panel 组件被标记为 deprecated。替代方案是使用自定义布局 手势 动画来实现等效的底部面板效果本质上就是本文 Demo 后半部分采用的自定义方案。但在 API 24 中Panel 仍然可用且对于快速原型和简单场景仍然是最简洁的选择。本文会同时讲解 Panel 组件的用法和自定义方案的底层原理。核心 API 解析PanelMode 枚举enumPanelMode{Mini,// 最小化 — 面板收缩到最小高度仅显示一行Half,// 半展开 — 面板展开到半屏高度Full// 全展开 — 面板展开到接近全屏高度}通过.mode()属性设置面板当前模式Panel(this.showPanel){// 面板内容}.mode(this.currentMode)// PanelMode.Mini | Half | FullPanel 在三种模式之间的切换可以通过两种方式触发用户拖拽在手柄区域向上/向下拖拽Panel 自动在 Mini → Half → Full 之间切换编程式切换修改mode绑定的变量值Panel 自动动画过渡到新模式PanelType 枚举enumPanelType{Foldable,// 可折叠 — 内容始终在面板内高度变化时内容区域自动调整Temporary,// 临时 — 面板覆盖在主内容上方类似 ModalMini// 迷你 — 始终处于 Mini 状态不响应拖拽切换}PanelType.Foldable默认是最常用的模式——主内容区域会随面板高度变化而自动调整如列表重新排版面板和内容是折叠关系。PanelType.Temporary则让面板悬浮覆盖在主内容上方主内容不受影响。dragBar 属性Panel(this.showPanel){// ...}.dragBar(true)// 显示系统默认拖拽条顶部居中的小横条.dragBar(false)// 隐藏拖拽条默认拖拽条是一根 32vp 宽的灰色横条位于面板顶部居中。如果你需要完全自定义拖拽条样式可以将dragBar设为false然后在面板内容顶部自行绘制拖拽条。onChange 回调.onChange((width:number,height:number,mode:PanelMode){console.log(面板模式切换为:${mode}, 高度:${height});})onChange在面板完成模式切换后触发三个参数分别是面板的宽度、高度和当前模式。在回调中更新State变量如当前模式标签可实现状态同步。onHeightChange 回调.onHeightChange((height:number){this.panelHeightheight;// 实时追踪面板高度变化})onHeightChange与onChange的区别在于触发时机onChange在模式切换完成后触发一次onHeightChange在拖拽过程中持续触发每一帧提供实时的高度值。如果你需要做跟随面板高度的动画效果如主内容区域的缩放或位移使用onHeightChange。Demo 设计智能面板实验室本文 Demo 实现了一个完整的可滑动面板包含以下功能页面结构Stack根容器Alignment.Bottom ├── Column主内容层 │ ├── Header深色标题栏智能面板实验室 Panel API 标签 │ └── Scroll │ └── Column │ ├── 面板控制区Mini/Half/Full 三个按钮 显示/隐藏 Toggle 自定义拖拽条 Toggle │ ├── 实时状态区当前模式 / 面板可见 / 面板高度 三个标签 │ ├── PanelMode 三模式说明 │ └── API 参考区 └── Column自定义面板层从底部弹出 ├── 自定义拖拽条36×4vp 灰色横条 PanGesture 拖拽手势 ├── [Mini 内容] 一行摘要文字 ├── [Half 内容] 2×2 快捷操作网格 └── [Full 内容] 全部设置列表为什么不使用原生 Panel 组件虽然本文的主题是 Panel 组件但 Demo 采用了一个自定义面板方案Stack Column PanGesture animation——原因是在 API 20 的原生 Panel 已标记为 deprecated而自定义方案是官方推荐的替代方式。更重要的是使用自定义方案能让你深入理解 Panel 的底层原理——手势识别、高度计算、动画过渡、条件渲染。理解这些原理后你不仅能用 Panel还能在 Panel 不支持的场景中自如地构建等效方案。4 个交互点模式切换按钮三个按钮Mini/Half/Full编程式切换面板高度面板以 300ms EaseOut 动画过渡到新高度。拖拽切换模式在自定义拖拽条上向上拖拽展开面板Mini→Half→Full向下拖拽收缩面板Full→Half→Mini。使用 PanGesture 实现。显示/隐藏切换Toggle 开关控制面板的完全显示或隐藏。隐藏后面板消失主内容占据全屏。自定义拖拽条切换Toggle 开关控制是否显示自定义拖拽条。关闭后面板顶部不再有拖拽手柄仅能通过按钮切换模式。核心实现面板高度动画自定义面板的核心是通过.height()和.animation()实现高度变化动画Column(){// 面板内容...}.width(100%).backgroundColor(#FFFFFF).borderRadius({topLeft:16,topRight:16}).shadow({radius:12,color:#00000015,offsetY:-2}).height(this.currentModePanelMode.Mini?60:this.currentModePanelMode.Half?240:420).animation({duration:300,curve:Curve.EaseOut})三种模式对应三个固定高度值。当currentMode变化时height()的值变化触发animation()定义的过渡动画。Curve.EaseOut让面板展开时有减速停止的自然感觉。拖拽手势实现自定义拖拽条使用PanGesture手势识别用户的垂直拖拽Row(){Row().width(36).height(4).borderRadius(2).backgroundColor(#CCCCDD)}.width(100%).height(20).justifyContent(FlexAlign.Center).gesture(PanGesture({direction:PanDirection.Vertical}).onActionUpdate((event:GestureEvent){if(event.offsetY-50){// 向上拖拽超过 50vp → 展开if(this.currentModePanelMode.Mini){this.handleModeChange(PanelMode.Half);}elseif(this.currentModePanelMode.Half){this.handleModeChange(PanelMode.Full);}}elseif(event.offsetY50){// 向下拖拽超过 50vp → 收缩if(this.currentModePanelMode.Full){this.handleModeChange(PanelMode.Half);}elseif(this.currentModePanelMode.Half){this.handleModeChange(PanelMode.Mini);}}}))关键设计PanDirection.Vertical限制手势只检测垂直方向的拖拽避免与水平滚动冲突。阈值 50vp不是拖拽一像素就切换那会导致过于敏感而是需要拖拽超过 50vp 才触发模式切换。这个阈值过滤了用户的误触和微调操作。阶梯式切换Mini 只能到 HalfHalf 可以到 Mini 或 FullFull 只能到 Half——不会从 Mini 直接跳到 Full。条件内容渲染不同模式下显示不同内容通过if条件实现// Mini 模式 — 一行摘要if(this.currentModePanelMode.Mini){Row(){Text(面板最小化).fontSize(13).fontColor(#1a1a2e).fontWeight(FontWeight.Medium)Blank()Text(上滑展开).fontSize(11).fontColor(#888899)}.width(100%).padding({left:16,right:16,top:8,bottom:12})}// Half 模式 — 2×2 快捷操作网格if(this.currentModePanelMode.Half){Column(){Text(快捷操作).fontSize(14).fontColor(#1a1a2e).fontWeight(FontWeight.Medium).margin({bottom:10})Grid(){ForEach(this.quickActions,(item:PanelDemoItem){GridItem(){/* ... */}})}.columnsTemplate(1fr 1fr).columnsGap(8).rowsGap(8)}}// Full 模式 — 完整设置列表if(this.currentModePanelMode.Full){Column(){Text(全部设置).fontSize(15).fontColor(#1a1a2e).fontWeight(FontWeight.Medium).margin({bottom:10})ForEach(this.allSettings,(item:PanelDemoItem){Row(){/* 设置项 */}})}}每种模式下的内容是独立的条件分支。当面板高度变化时内容随currentMode切换而改变创建出同一面板、不同层级的体验。实时高度追踪使用onAreaChange回调追踪面板的实际渲染高度.onAreaChange((oldValue:Area,newValue:Area){this.panelHeightnewValue.heightasnumber;})这个高度值实时显示在状态区中让用户直观看到面板的当前高度。在真实的 Panel 组件中onHeightChange回调提供相同的功能。Stack Alignment.Bottom 布局自定义面板通过Stack容器定位到底部Stack({alignContent:Alignment.Bottom}){// 主内容层 — 占据全屏Column(){// Header Scroll...}.width(100%).height(100%)// 面板层 — 从底部弹出覆盖在主内容上方if(this.panelVisible){Column(){// 拖拽条 面板内容...}.width(100%).backgroundColor(#FFFFFF).borderRadius({topLeft:16,topRight:16}).shadow({radius:12,color:#00000015,offsetY:-2}).height(/* 根据模式动态计算 */).animation({duration:300,curve:Curve.EaseOut})}}Alignment.Bottom将面板固定在 Stack 底部。当面板高度变化时面板从底部向上生长或向下收缩——这与原生 Panel 的行为一致。底部面板的关键视觉元素borderRadius({ topLeft: 16, topRight: 16 })— 顶部圆角让面板看起来像一张从底部抽出的卡片shadow— 轻微阴影在面板和主内容之间建立视觉层次backgroundColor(#FFFFFF)— 白色背景与主内容的灰色背景形成对比原生 Panel 的使用方式虽然 Demo 使用自定义方案但原生 Panel 的用法也非常简洁。以下是等效的原生 Panel 实现build(){Column(){// 主内容Scroll(){/* ... */}.layoutWeight(1)}.width(100%).height(100%)}// 原生 Panel — 自动定位到底部内置拖拽手势Panel(this.panelVisible){Column(){if(this.currentModePanelMode.Mini){/* Mini 内容 */}if(this.currentModePanelMode.Half){/* Half 内容 */}if(this.currentModePanelMode.Full){/* Full 内容 */}}}.mode(this.currentMode).type(PanelType.Foldable).dragBar(true).onChange((width:number,height:number,mode:PanelMode){this.currentModemode;})相比自定义方案原生 Panel 省略了Stack 布局、手势处理、高度动画设置。如果你不需要深度定制面板外观如自定义圆角、阴影、拖拽条样式原生 Panel 是更高效的选择。实际应用场景音乐播放器最经典的应用场景。Mini 模式显示当前歌曲名和播放/暂停按钮Half 模式增加进度条和收藏按钮Full 模式显示完整歌词和播放列表。地图地点详情点击地图上的标记后Panel 以 Half 模式显示地点名称、评分和简要信息。上滑到 Full 显示完整评论、照片和营业时间。下滑到 Mini 仅显示地点名称让用户重新关注地图。快捷控制面板类似 iOS 控制中心或 Android 快速设置。Mini 模式显示一行快捷开关WiFi、蓝牙、手电筒Half 模式增加亮度和音量滑块Full 模式展示所有可用的控制项。表单/评论区Half 模式显示少量核心输入项如评论输入框Full 模式展开后显示完整表单包括附件上传、格式工具栏等。自定义方案的适用场景当你需要以下效果时自定义方案优于原生 Panel自定义拖拽条样式原生 Panel 的拖拽条只有显示/隐藏两种状态自定义方案可以绘制任意样式的拖拽手柄如用品牌色、加宽、加图标。非标准高度原生 Panel 的三档高度由系统计算自定义方案可以定义任意高度值如 100vp、350vp、600vp。面板外内容联动面板高度变化时主内容需要做复杂的联动动画如缩放、模糊、视差效果自定义方案可以直接在onChange中控制任意State变量。非底部弹出方向原生 Panel 固定从底部弹出自定义方案可以改为从顶部、左侧、右侧弹出。总结本文通过构建一个智能面板实验室深入讲解了 HarmonyOS ArkUI 中 Panel 可滑动面板组件的核心用法PanelMode 三态模型Mini最小化摘要/ Half半展开核心功能/ Full全展开完整内容用户通过拖拽在三态之间切换。PanelType 类型Foldable内容自适应、Temporary覆盖悬浮、Mini仅最小化。dragBar 拖拽条控制系统拖拽条的显示/隐藏custom 方案则可以完全自由绘制。onChange / onHeightChange模式切换完成回调 vs 高度实时变化回调不同场景使用不同粒度的监听。自定义等效方案Stack Alignment.Bottom PanGesture animation height 动态绑定实现与原生 Panel 等价的行为但具有更高的定制自由度。Panel 组件体现了 ArkUI声明式 UI 内置交互的设计哲学——它将一个常见但实现复杂的交互模式可拖拽多层级底部面板封装为简洁的声明式 API。对于标准场景原生 Panel 非常高效对于需要深度定制的场景理解 Panel 的底层原理后可以用自定义方案实现等效行为。