平板弹窗被遮挡
=======

#### 问题现象

在横屏模式下打开应用，点击“已有账号登录”后，登录弹窗从底部弹出，未居中显示，且无法点击弹窗中的内容。切换为竖屏模式后，同样点击“已有账号登录”，弹窗底部内容被截断，信息显示不完整。

#### 背景知识

关于[弹窗](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-dialog-overview)，根据用户交互操作场景，可分为模态弹窗和非模态弹窗两种类型，其区别在于用户是否必须对其做出响应。

*   模态弹窗为强交互形式，会中断用户当前的操作流程，要求用户必须做出响应才能继续其他操作，通常用于需要向用户传达重要信息的场景；
*   非模态弹窗，为弱交互形式，不会影响用户当前操作行为，用户可以不对其进行回应，通常都有时间限制，出现一段时间后会自动消失。一般用于告诉用户信息内容外还需要用户进行功能操作的场景。

#### 问题定位

*   排查自定义弹窗中[@Builder](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder)自定义构建函数修饰的自定义组件中是否设置的height属性，强制固定了弹窗的高度，导致自定义弹窗的内容被截断，显示不全。
*   排查自定义弹窗中[BaseDialogOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction#basedialogoptions11)属性中是否传入并设置了offset属性强行给自定义弹窗设置了向下的偏移量，导致弹窗向下偏移被遮挡。

#### 分析结论

*   自定义弹窗的自定义构建函数[@Builder](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-builder)修饰的自定义组件中设置了height属性，并且值被写死，导致自定义弹窗内容超出部分被截断。
*   自定义弹窗的选项属性[BaseDialogOptions](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction#basedialogoptions11)中传入了offset属性值，在Y轴方向上设置了偏移量，导致弹窗贴底被遮挡。

#### 修改建议

由于[CustomDialog](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-common-components-custom-dialog)在使用上存在较多限制，不支持动态创建和动态刷新，在相对较复杂的应用场景中推荐使用UIContext中获取到的[PromptAction](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction)对象提供的[openCustomDialog](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-promptaction#opencustomdialog12)接口来实现自定义弹出框。以下是基于[PromptAction](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-promptaction)封装的自定义弹窗实现横竖屏居中显示不遮挡的示例，具体实现步骤如下：

*   创建ComponentContent。ComponentContent用于定义自定义弹出框的内容。其中，wrapBuilder(buildText)封装自定义组件，new Params(this.message)是自定义组件的入参，可以缺省，也可以传入基础数据类型。
    
    private contentNode: ComponentContent<Object> =
      new ComponentContent(this.ctx, wrapBuilder(buildText));
    
*   打开自定义弹出框。通过调用openCustomDialog接口打开的弹出框默认customStyle为true，即弹出框的内容样式完全按照contentNode自定义样式显示。
    
    PromptActionClass.ctx.getPromptAction()
      .openCustomDialog(PromptActionClass.contentNode, PromptActionClass.options)
      .then(() => {
        console.info('OpenCustomDialog complete.');
      })
      .catch((error: BusinessError) => {
        let message = (error as BusinessError).message;
        let code = (error as BusinessError).code;
        console.error(\`OpenCustomDialog args error code is ${code}, message is ${message}\`);
      });
    
*   关闭自定义弹出框。由于closeCustomDialog接口需要传入待关闭弹出框对应的ComponentContent。因此，如果需要在弹出框中设置关闭方法，则可参考完整示例封装静态方法来实现。关闭弹出框之后若需要释放对应的ComponentContent，则需要调用ComponentContent的dispose方法。
    
    PromptActionClass.ctx.getPromptAction()
      .closeCustomDialog(PromptActionClass.contentNode)
      .then(() => {
        console.info('CloseCustomDialog complete.');
      })
      .catch((error: BusinessError) => {
        let message = (error as BusinessError).message;
        let code = (error as BusinessError).code;
        console.error(\`CloseCustomDialog args error code is ${code}, message is ${message}\`);
      });
    
*   PromptAction的BaseDialogOptions选项中offset属性值在Y轴方向上设置偏移量为0。
    
    aboutToAppear(): void {
      PromptActionClass.setContext(this.ctx);
      PromptActionClass.setContentNode(this.contentNode);
      PromptActionClass.setOptions({ alignment: DialogAlignment.Center });
    }
    
*   自定义组件的height属性不设置或设置为auto，高度由内容撑开。
    
    @Builder
    function buildText() {
      Column() {
        Flex({ justifyContent: FlexAlign.SpaceBetween }) {
          Text('账号登录').fontSize(24).fontWeight(FontWeight.Bold);
          Image($r('app.media.close'))
            .width(32)
            .height(32)
            .onClick(() => {
              PromptActionClass.closeDialog();
            });
        }.padding({ bottom: 24 });
    
        Column() {
          TextInput({ placeholder: '邮箱' })
            .width('100%')
            .showUnderline(true);
          TextInput({ placeholder: '验证码' })
            .width('100%')
            .showUnderline(true);
    
          Row() {
            Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
              .select(false)
              .selectedColor(0x39a2db)
              .shape(CheckBoxShape.ROUNDED\_SQUARE)
              .onChange((value: boolean) => {
                console.info('Checkbox2 change is' + value);
              });
            Text('我已阅读并同意《隐私政策》');
          }.padding({ top: 12, bottom: 12 });
        };
    
        Button('登录')
          .width('100%')
          .onClick(() => {
            PromptActionClass.closeDialog();
          });
    
        Column() {
          Text('其他账号登录').padding({ bottom: 12 });
          Row() {
            Image($r('app.media.huawei')).width(32).height(32).margin({ right: 12 });
          };
        }
        .padding({ top: 24 });
      }.backgroundColor('#FFF0F0F0')
      .padding(24)
      .width(480)
      .border({ radius: '10%' });
    }
    

完整示例如下：

import { BusinessError } from '@kit.BasicServicesKit';
import { ComponentContent, promptAction, UIContext } from '@kit.ArkUI';


export class PromptActionClass {
  static ctx: UIContext;
  static contentNode: ComponentContent<Object>;
  static options: promptAction.BaseDialogOptions;

  static setContext(context: UIContext) {
    PromptActionClass.ctx = context;
  }


  static setContentNode(node: ComponentContent<Object>) {
    PromptActionClass.contentNode = node;
  }


  static setOptions(options: promptAction.BaseDialogOptions) {
    PromptActionClass.options = options;
  }


  static openDialog() {
    if (PromptActionClass.contentNode !== null) {
      PromptActionClass.ctx.getPromptAction()
        .openCustomDialog(PromptActionClass.contentNode, PromptActionClass.options)
        .then(() => {
          console.info('OpenCustomDialog complete.');
        })
        .catch((error: BusinessError) => {
          let message = (error as BusinessError).message;
          let code = (error as BusinessError).code;
          console.error(\`OpenCustomDialog args error code is ${code}, message is ${message}\`);
        });
    }
  }


  static closeDialog() {
    if (PromptActionClass.contentNode !== null) {
      PromptActionClass.ctx.getPromptAction()
        .closeCustomDialog(PromptActionClass.contentNode)
        .then(() => {
          console.info('CloseCustomDialog complete.');
        })
        .catch((error: BusinessError) => {
          let message = (error as BusinessError).message;
          let code = (error as BusinessError).code;
          console.error(\`CloseCustomDialog args error code is ${code}, message is ${message}\`);
        });
    }
  }


  static updateDialog(options: promptAction.BaseDialogOptions) {
    if (PromptActionClass.contentNode !== null) {
      PromptActionClass.ctx.getPromptAction()
        .updateCustomDialog(PromptActionClass.contentNode, options)
        .then(() => {
          console.info('UpdateCustomDialog complete.');
        })
        .catch((error: BusinessError) => {
          let message = (error as BusinessError).message;
          let code = (error as BusinessError).code;
          console.error(\`UpdateCustomDialog args error code is ${code}, message is ${message}\`);
        });
    }
  }
}

_// Index.ets_
import { ComponentContent } from '@kit.ArkUI';
import { PromptActionClass } from './PromptActionView';

@Builder
function buildText() {
  Column() {
    Flex({ justifyContent: FlexAlign.SpaceBetween }) {
      Text('账号登录').fontSize(24).fontWeight(FontWeight.Bold);
      Image($r('app.media.close'))
        .width(32)
        .height(32)
        .onClick(() => {
          PromptActionClass.closeDialog();
        });
    }.padding({ bottom: 24 });

    Column() {
      TextInput({ placeholder: '邮箱' })
        .width('100%')
        .showUnderline(true);
      TextInput({ placeholder: '验证码' })
        .width('100%')
        .showUnderline(true);

      Row() {
        Checkbox({ name: 'checkbox2', group: 'checkboxGroup' })
          .select(false)
          .selectedColor(0x39a2db)
          .shape(CheckBoxShape.ROUNDED\_SQUARE)
          .onChange((value: boolean) => {
            console.info('Checkbox2 change is' + value);
          });
        Text('我已阅读并同意《隐私政策》');
      }.padding({ top: 12, bottom: 12 });
    };

    Button('登录')
      .width('100%')
      .onClick(() => {
        PromptActionClass.closeDialog();
      });

    Column() {
      Text('其他账号登录').padding({ bottom: 12 });
      Row() {
        Image($r('app.media.huawei')).width(32).height(32).margin({ right: 12 });
      };
    }
    .padding({ top: 24 });
  }.backgroundColor('#FFF0F0F0')
  .padding(24)
  .width(480)
  .border({ radius: '10%' });
}


@Entry
@Component
struct Index {
  private ctx: UIContext = this.getUIContext();
  private contentNode: ComponentContent<Object> =
    new ComponentContent(this.ctx, wrapBuilder(buildText));


  aboutToAppear(): void {
    PromptActionClass.setContext(this.ctx);
    PromptActionClass.setContentNode(this.contentNode);
    PromptActionClass.setOptions({ alignment: DialogAlignment.Center });
  }


  build() {
    Column() {
      Button('已有账号登录')
        .margin({ bottom: 24 })
        .onClick(() => {
          PromptActionClass.openDialog();
        });
    }
    .justifyContent(FlexAlign.End)
    .width('100%')
    .height('100%');
  }
}