HarmonyOS ArkUI 实现弹窗(模拟半模态弹窗效果)

一、鸿蒙的半模态弹窗没有找到从上往下出现的方式,所以手写一个类似效果的弹窗

二、效果:

点击弹窗"X"关闭弹窗、弹窗出现时点击非弹窗内容区域关闭弹窗

三、实现思路:

1、弹窗出现消失:获取设备窗口高度、用定位position控制,移出可视范围

2、弹窗出现消失行为的速度(动画)效果,用关键帧动画

3、点击非弹窗区域关闭页面-用一个大盒子作为底部遮罩,覆盖整个页面,页面内写一个小盒子放在大盒子的上面部位,布局用stack层叠布局,点击底部遮罩控制弹窗消失

四、代码实现:

import { HMRouter, HMRouterMgr } from '@hadss/hmrouter';
import { display, UIContext, window } from '@kit.ArkUI';

@Entry
@Component
export struct SheetBox {
  uiContext: UIContext | undefined = undefined;
  @State zhaoH: number = 0;  //遮罩y轴位置
  @State windowH: number = 0;  //设备窗口高度
  @State showOverlay: boolean = false;

  // 获取窗口高度
  getWindowHeight() {
    try {
      this.windowH =  display.getDefaultDisplaySync().height
      console.log(this.zhaoH+'test')
    } catch (exception) {
      console.error('Failed to obtain the default display object. Code: ' + JSON.stringify(exception))
    }
  }

  @Builder
  sheet() {
    Stack({ alignContent: Alignment.Top }) {
      // 使用Column作为遮罩层
      Row(){}
        .width('100%')
        .height('120%')
        // .backgroundColor(Color.Pink) // 遮罩颜色
        .onClick(() => this.closeSheet())


      Column() {
        Row() {
          Text('快捷入口')
          Text('×').fontSize(25).fontColor('#8c8c8c')
            .onClick(() => this.closeSheet())
        }.justifyContent(FlexAlign.SpaceBetween).width('100%')
        Row() {
        // 内容
          GridRow({breakpoints:{value:['200vp','300vp','400vp']}}) {	//不写这一行就用默认栅格尺寸,这个自定义的意思是200以下xs,200-300之间sm,300-400之间md,还可以往后写就是lg  xl xxl,这里400是最大的。
            GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
              Column(){
                Row(){
                  Image($r('app.media.sheet_home')).width(30).height(30)
                }.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
                Text('首页').fontSize(12).margin({top:10})
              }
            }.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})
            GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
              Column(){
                Row(){
                  Image($r('app.media.sheet_search')).width(30).height(30)
                }.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
                Text('搜素').fontSize(12).margin({top:10})
              }
            }.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})
            GridCol({span: {xs: 12, sm: 6, md: 4, lg: 2,}}){
              Column(){
                Row(){
                  Image($r('app.media.sheet_car')).width(30).height(30)
                }.backgroundColor('#fff').borderRadius('50%').width(60).height(60).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)
                Text('购物车').fontSize(12).margin({top:10})
              }
            }.onClick(() => {HMRouterMgr.push({pageUrl:'Home/Home'})})

          }.padding({top:10})
        // 内容
        }
      }
      .width('100%')
      // 模糊效果-毛玻璃  BlurStyle.BACKGROUND_THIN
      .backgroundBlurStyle(BlurStyle.BACKGROUND_THIN, { colorMode: ThemeColorMode.LIGHT, adaptiveColor: AdaptiveColor.DEFAULT, scale: 1.0 })
      .shadow(ShadowStyle.OUTER_DEFAULT_XS)
      .height(250)
      .padding({
        top: 50,
        bottom: 15,
        left: 15,
        right: 15
      })
      .borderRadius({ bottomLeft: 16, bottomRight: 16 })
    }
    // .backgroundColor(Color.Gray)
    .zIndex(98).position({top:this.zhaoH,right:-10})
  }

  closeSheet() {
    if (!this.uiContext) {
      return;
    }
    this.showOverlay = false;
    this.zhaoH = -60;
    this.uiContext.keyframeAnimateTo({
      iterations: 1,
    }, [
      {
        duration: 600,
        event: () => {
          this.zhaoH = -this.windowH;
        }
      }
    ]);
  }

  openSheet() {
    if (!this.uiContext) {
      return;
    }
    this.showOverlay = true;
    this.zhaoH = -this.windowH;
    this.uiContext.keyframeAnimateTo({
      iterations: 1,
    }, [
      {
        duration: 600,
        event: () => {
          this.zhaoH = -60;
        }
      }
    ]);
  }

  aboutToAppear() {
    this.uiContext = this.getUIContext?.();
    this.getWindowHeight()
    this.zhaoH = -this.windowH

  }


  build() {
    Column() {
      this.sheet()
      // 使用
      Image($rawfile('orders_iconmore.png')).width(20)
        .onClick(() => {
          this.openSheet()
        })
    }
  }
}

五、使用局限:

用定位实现不能拿过来直接用,需要和自己原来的代码进行调试,否则布局可能会乱

注意:因为用定位实现的,所以要把这个代码作为最后一个组件调用,否则弹窗会被覆盖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值