ArkUI框架下半模态弹窗的自定义设计指南:打造丝滑的用户体验 原创

在敲键盘的小鱼干很饥饿
发布于 2024-12-11 20:16
浏览
4收藏

前言

  • 模态转场是新的界面覆盖在旧的界面上,旧的界面不消失的一种转场方式。
  • 半模态转场是一种特殊的页面切换效果,用于在当前页面上显示一部分内容,而不会完全覆盖底下的页面。这种转场方式通常用于提供额外的信息或操作选项,而不需要用户离开当前页面。

介绍

通过bindSheet属性为组件绑定半模态页面,在组件插入时可通过设置自定义或默认的内置高度确定半模态大小,属性设置如下。

bindSheet(isShow: Optional<boolean>, builder: CustomBuilder, options?: SheetOptions)
参数名 类型 说明
isShow Optional<boolean> 是否显示半模态页面。从API version 10开始,该参数支持$$双向绑定变量。
builder CustomBuilder 配置半模态页面内容。
isShow SheetOptions 配置半模态页面的可选属性。

其中前两个参数是必填的。

说明:

  1. 在非双向绑定情况下,以拖拽方式关闭半模态页面不会改变isShow参数的值。
  2. 为了使isShow参数值与半模态界面的状态同步,建议使用$$双向绑定isShow参数。
  3. 在半模态单挡位向上拖拽或是多挡位上滑换挡情况下,内容在拖拽结束或换挡结束后更新显示区域。
  4. 半模态是一个严格和宿主节点绑定在一起的弹窗。若是想实现类似“页面显示的瞬间就弹出半模态”的效果,请确认宿主节点是否已挂载上树。若宿主节点还没上树就将isShow置为true,半模态将不生效。建议使用onAppear函数,确保在宿主节点挂载后再显示半模态。 尤其是 SheetMode = EMBEDDED 时,除宿主节点外,还需确保对应的页面节点成功挂载。
  5. 半模态页面的离场动效不支持打断,动效执行期间无法响应其他手势动作。目前离场动效使用弹簧曲线,该动画曲线存在视觉上并不明显的拖尾动画。因此,在半模态退出时,视觉上半模态页面已经消失,但此时动效可能还未结束,若想再次点击拉起半模态页面则不会响应。需要等动效完全结束后,才可以再次拉起。

配置半模态页面的可选属性

属性有很多我们介绍几个就行

  1. detents (半模态页面的切换高度档位。)
    介绍:
    底部弹窗竖屏生效,元组中第一个高度为初始高度。
    面板可跟手滑动切换档位,松手后是否滑动至目标档位有两个判断条件:速度和距离。速度超过阈值,则执行滑动至与手速方向一致的目标档位;速度小于阈值,则引入距离判断条件,当位移距离>当前位置与目标位置的1/2,滑动至与手速方向一致的目标档位,位移距离当前位置与目标位置的1/2,返回至当前档位。速度阈值:1000,距离阈值:50%。
detents: [300, 600, 900],
  1. preferType( 半模态页面的样式。)
    半模态在不同屏幕宽度所支持的显示类型:
  • 宽度 < 600vp:底部。
  • 600vp <= 宽度 < 840vp:底部、居中。默认居中样式。
  • 宽度 >= 840vp:底部、居中、跟手。默认跟手样式。
preferType: SheetType.CENTER,
  1. blurStyle (半模态面板的模糊背景。默认无模糊背景。)
  2. maskColor (半模态页面的背景蒙层颜色。)
  3. enableOutsideInteractive (半模态所在页面是否允许交互。)
    设置为true时允许交互,不显示蒙层;设置为false时不允许交互,显示蒙层;若不进行设置,默认底部弹窗与居中弹窗不允许交互,跟手弹窗允许交互。当设置为true时,maskColor设置无效。
  4. shouldDismiss,onWillDismiss,onWillSpringBackWhenDismiss等一些回调函数,可以去官方文档查阅。

另外还有一些通用属性,例如高度,宽度,圆角,边线, 控制条,显示层级等。可以查询openHarmony半模态弹窗官方文档

丝滑示例

代码

主页面

import {TimeComponent} from "./addModal"
@Entry
@Component
struct Index {
  @State isShowTime: boolean = false
  build() {
    Column() {
      Text('弹起半模态弹窗')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 20 , bottom: 20 })
        .textAlign(TextAlign.Center)
      Button('点击弹出弹窗',{buttonStyle: ButtonStyleMode.NORMAL})
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .width(150)
        .height(50)
        .borderRadius(25)
        .backgroundColor('#ff08c1c6')
        .border({ color: '#ff08c1c6', width: 1 })
        .margin({ right: 25 })
        .onClick(() => {
          this.isShowTime = true;
        })
        .bindSheet(this.isShowTime,this.addModal(),{
          width: "100%",
          maskColor: 'rgba(125, 125, 125, 0.5)',
          showClose: false,
          height: '43%',
          mode: SheetMode.EMBEDDED,
          enableOutsideInteractive: false,
          shouldDismiss: () => {
            this.isShowTime = false;
          }
        })
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
  @Builder
  addModal() {
    TimeComponent({
      isTrue: this.isShowTime,
    })
  }
}
  • 使用bindSheet方法将半模态弹窗与按钮绑定在一起
  • bindSheet方法的参数包括:
  1. this.isShowTime: 控制模态弹窗显示和隐藏的状态变量。
  2. this.addModal(): 用于构建模态弹窗内容的@Builder方法。
    自定义设置时间弹窗组件
  • width: 弹窗的宽度,设置为100%。
  • maskColor: 弹窗背景遮罩层的颜色,设置为半透明灰色。
  • showClose: 是否显示关闭按钮,这里设置为false,即不显示。
  • height: 弹窗的高度,设置为40%。
  • mode: 弹窗的模式,这里设置为SheetMode.EMBEDDED,表示嵌入模式。
  • enableOutsideInteractive: 是否允许点击弹窗外的区域来关闭弹窗,这里设置为false,即不允许。
  • shouldDismiss: 当弹窗关闭时执行的回调函数,这里将isShowTime状态变量设置为false

使用@Builder装饰器定义了一个名为addModal的方法,用于构建模态弹窗的内容。这里使用了之前导入的TimeComponent组件,并将当前isShowTime状态变量作为属性传递给它,用于控制TimeComponent的显示和隐藏。

import { emitter } from "@kit.BasicServicesKit";

interface DwellTime{
  hour: number;
  minute: number;
}
@Component
export struct TimeComponent {

  @State timeId: string = "设置驻留时间";
  confirm: ((dwellTime: DwellTime) => void) | undefined = undefined;
  @Link isTrue: boolean;
  //@State isTrue: boolean = false;
  @State hour: number = 3;
  @State minute: number = 23;

  aboutToAppear(): void {
    emitter.once(this.timeId, () => {
      console.log("===用户点击了确定" + this.timeId)
      if(this.confirm){
        this.confirm({hour: this.hour, minute: this.minute});
      }
    })
  }
  build() {
    RelativeContainer() {
      Column() {
        Text("添加时间")
          .fontSize(22)
          .padding(20)
          .width('100%')
          .margin({top: 5, bottom: 5})
        //小时选择器
        Row() {
          Text("小时")
            .fontSize(18)
            .margin({ left: 15, right: 15})

          Text(this.hour.toFixed(0).toString())
            .fontSize(16)
            .height(26)
            .width(60)
            .textAlign(TextAlign.Center)
            .backgroundColor('#EDEDED')
            .borderRadius(4)

          Slider({
            value: this.hour,
            min: 0,
            max: 23,
            step: 1,
            style: SliderStyle.OutSet
          })
            .blockColor(Color.Black)
            .blockStyle({ type: SliderBlockType.DEFAULT})
            .blockBorderWidth(6)
            .trackColor(Color.Black)
            .trackThickness(5)
            .selectedColor('#89BA20')
            .onChange((value: number, mode: SliderChangeMode) => {
              this.hour = value
            })
            .width(216)
        }
        .margin({ top: 12, bottom: 12})

        Row() {
          Text("分钟")
            .fontSize(18)
            .margin({ left: 15, right: 15})

          Text(this.minute.toFixed(0).toString())
            .fontSize(16)
            .height(26)
            .width(60)
            .textAlign(TextAlign.Center)
            .backgroundColor('#EDEDED')
            .borderRadius(4)

          Slider({
            value: this.minute,
            min: 0,
            max: 59,
            step: 1,
            style: SliderStyle.OutSet
          })
            .blockColor(Color.Black)
            .blockStyle({ type: SliderBlockType.DEFAULT})
            .blockBorderWidth(6)
            .trackColor(Color.Black)
            .trackThickness(5)
            .selectedColor('#89BA20')
            .onChange((value: number, mode: SliderChangeMode) => {
              this.minute = value
            })
            .width(216)
        }
        .margin({ top: 12, bottom: 42})

        Row() {
          Button("取消", { type: ButtonType.Normal })
            .height(40)
            .width("43%")
            .backgroundColor('#EDEDED')
            .fontColor("#000000")
            .fontSize(18)
            .borderRadius(5)
            .onClick(() => {
              this.isTrue = false;
            })

          Button("确定", { type: ButtonType.Normal })
            .height(40)
            .width("43%")
            .fontColor("#ff21ca5a")
            .linearGradient({
              direction: GradientDirection.Left,
              colors: [[("#ff52c614"), 0.0], [("#ff160b05"), 1.0]]
            })
            .fontSize(18)
            .borderRadius(5)
            .onClick(() => {
              this.isTrue = false;
            })
        }
        .justifyContent(FlexAlign.SpaceAround)
        .padding({left: 12, right: 12})
        .width("100%")
      }
      .width("100%")
      .height("100%")
      .backgroundColor("#F5F5F5")
      .borderRadius({ topLeft: 24, topRight: 24 })
    }
  }
}

演示效果

ArkUI框架下半模态弹窗的自定义设计指南:打造丝滑的用户体验-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-12-26 11:28:25修改
5
收藏 4
回复
举报
5条回复
按时间正序
/
按时间倒序
wx648063cb367de
wx648063cb367de

大佬,太牛了,能带带我吗?


1
回复
2024-12-12 14:23:06
mb6759406178e33
mb6759406178e33

6666666666

1
回复
2024-12-12 14:30:57
X叶域Q
X叶域Q

丝滑呀

回复
2024-12-18 11:22:22
bond_heloworld
bond_heloworld

1111111111111111111111111

回复
2024-12-18 15:19:10
Crips
Crips

厉害了

回复
2024-12-18 19:26:13
回复
    相关推荐