自定义弹窗动态高度与滚动实现
==============

#### 问题现象

当弹窗内容高度不确定时，需实现以下效果：

*   动态高度：弹窗高度根据内容自动调整。
*   最大高度限制：超过设定高度（如100vp）时，内容区域支持滚动。
*   滚动体验：内容超出后显示滚动条，确保用户可完整查看内容。

#### 效果预览

![](https://contentcenter-vali-drcn.dbankcdn.cn/pvt_2/DeveloperAlliance_scene_100_1/31/v3/XeWWOF6eSw6iwiVkW3YGKA/zh-cn_image_0000002628401674.png?HW-CC-KV=V1&HW-CC-Date=20260709T134058Z&HW-CC-Expire=86400&HW-CC-Sign=0745CF3BDC49881E499E9A590B553E1437D072135F6E0F605E87D9C7908694FD "点击放大")

#### 背景知识

*   [自定义弹窗（CustomDialog）](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-custom-dialog-box)：通过[CustomDialogController](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-methods-custom-dialog-box#customdialogcontroller)或[PromptAction](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction)控制弹窗，支持完全自定义UI布局。
*   滚动容器（[Scroll](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-scroll)）：Scroll组件提供垂直/水平滚动能力，结合[constraintSize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-size#constraintsize)可限制最大尺寸。
*   约束布局：[constraintSize](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-universal-attributes-size#constraintsize)方法动态控制组件尺寸，确保布局自适应。

#### 解决方案

1.  弹窗外层包裹滚动容器。
    *   使用Scroll作为根容器，设置滚动方向为垂直（ScrollDirection.Vertical）。
    *   通过.scrollBar(BarState.On)启用滚动条，提升交互可视性。
2.  动态高度与最大高度限制。
    *   不固定高度：弹窗内部组件（如Column/Row）不设置固定高度。
    *   约束最大高度：用constraintSize限制Scroll组件的最大高度。
3.  滚动行为优化。
    
    禁用边缘效果：设置[edgeEffect](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-scroll#edgeeffect)属性的值为None避免滚动到边缘时有滑动效果。
    
4.  弹窗控制逻辑。
    *   通过PromptAction打开/关闭弹窗，绑定自定义组件ID实现精准控制。
    *   处理物理返回键/点击外部关闭：重写[BaseDialogOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction#basedialogoptions11)中的onWillDismiss方法拦截DismissReason.PRESS\_BACK和DismissReason.TOUCH\_OUTSIDE。

完整代码：

import { PromptAction } from '@kit.ArkUI';

@Entry
@Component
struct customDialogScroll {
  promptAction: PromptAction = this.getUIContext().getPromptAction();
  private customDialogComponentId: number = 0; _// 弹窗ID，用于关闭时定位_
  scroller: Scroller = new Scroller(); _// 滚动控制器_

 _ // 自定义弹窗UI_
  @Builder
  customDialogComponent() {
    Column() {
      Text('弹窗标题')
        .fontSize(30)
        .padding({ top: 10, bottom: 10 });
      Scroll(this.scroller) {
        Column() {
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
          Text('这里是弹窗内容区域，可根据内容变化高度这里是弹窗内容区域，可根据内容变化高度');
        }
        .padding({left:15,right:15})
        .backgroundColor('rgba(246, 244, 244, 1.00)')
        .width('100%'); _// 模拟动态内容_
      }
      .scrollable(ScrollDirection.Vertical) _// 启用垂直滚动_
      .scrollBar(BarState.On)
      .constraintSize({ maxHeight: 100 }) _// ✅核心：限制最大高度_
      .edgeEffect(EdgeEffect.None); _// 禁用边缘效果_
      Flex({direction:FlexDirection.Row,justifyContent:FlexAlign.SpaceEvenly,alignItems:ItemAlign.Center}){
        Button('取消')
          .fontColor('#0a59f7')
          .backgroundColor(Color.White)
          .backgroundColor('rgba(246, 244, 244, 1.00)')
          .onClick(() => this.closeDialog());
        Row()
          .width(1)
          .height(15)
          .backgroundColor(Color.Gray)
          .opacity(0.5)
        Button('确认')
          .fontColor('#0a59f7')
          .backgroundColor('rgba(246, 244, 244, 1.00)')
          .onClick(() => this.closeDialog());
      }

      .margin({ top: 8, bottom: 8 });
    }.backgroundColor('rgba(246, 244, 244, 1.00)');

  }

  _// 关闭弹窗方法_
  private closeDialog() {
    this.promptAction.closeCustomDialog(this.customDialogComponentId);
  }

  build() {
    Row() {
      Column() {
        Button('打开弹窗')
          .onClick(() => {
            this.promptAction.openCustomDialog({
              builder: () => this.customDialogComponent(), _// 绑定UI构建器_
              onWillDismiss: (action: DismissDialogAction) => {
              _  // 处理物理返回键/点击外部关闭_
                if (action.reason === DismissReason.PRESS\_BACK ||
                  action.reason === DismissReason.TOUCH\_OUTSIDE) {
                  action.dismiss(); _// 执行关闭_
                }
              }
            }).then((dialogId: number) => {
              this.customDialogComponentId = dialogId; _// 保存弹窗ID_
            });
          });
      }
      .width('100%')
      .height('100%')
      .alignItems(HorizontalAlign.Center)
      .justifyContent(FlexAlign.Center);
    };
  }
}