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 | 配置半模态页面的可选属性。 |
其中前两个参数是必填的。
说明:
- 在非双向绑定情况下,以拖拽方式关闭半模态页面不会改变isShow参数的值。
- 为了使isShow参数值与半模态界面的状态同步,建议使用$$双向绑定isShow参数。
- 在半模态单挡位向上拖拽或是多挡位上滑换挡情况下,内容在拖拽结束或换挡结束后更新显示区域。
- 半模态是一个严格和宿主节点绑定在一起的弹窗。若是想实现类似“页面显示的瞬间就弹出半模态”的效果,请确认宿主节点是否已挂载上树。若宿主节点还没上树就将isShow置为true,半模态将不生效。建议使用onAppear函数,确保在宿主节点挂载后再显示半模态。 尤其是 SheetMode = EMBEDDED 时,除宿主节点外,还需确保对应的页面节点成功挂载。
- 半模态页面的离场动效不支持打断,动效执行期间无法响应其他手势动作。目前离场动效使用弹簧曲线,该动画曲线存在视觉上并不明显的拖尾动画。因此,在半模态退出时,视觉上半模态页面已经消失,但此时动效可能还未结束,若想再次点击拉起半模态页面则不会响应。需要等动效完全结束后,才可以再次拉起。
配置半模态页面的可选属性
属性有很多我们介绍几个就行
- detents (半模态页面的切换高度档位。)
介绍:
底部弹窗竖屏生效,元组中第一个高度为初始高度。
面板可跟手滑动切换档位,松手后是否滑动至目标档位有两个判断条件:速度和距离。速度超过阈值,则执行滑动至与手速方向一致的目标档位;速度小于阈值,则引入距离判断条件,当位移距离>当前位置与目标位置的1/2,滑动至与手速方向一致的目标档位,位移距离当前位置与目标位置的1/2,返回至当前档位。速度阈值:1000,距离阈值:50%。
detents: [300, 600, 900],
- preferType( 半模态页面的样式。)
半模态在不同屏幕宽度所支持的显示类型:
- 宽度 < 600vp:底部。
- 600vp <= 宽度 < 840vp:底部、居中。默认居中样式。
- 宽度 >= 840vp:底部、居中、跟手。默认跟手样式。
preferType: SheetType.CENTER,
- blurStyle (半模态面板的模糊背景。默认无模糊背景。)
- maskColor (半模态页面的背景蒙层颜色。)
- enableOutsideInteractive (半模态所在页面是否允许交互。)
设置为true时允许交互,不显示蒙层;设置为false时不允许交互,显示蒙层;若不进行设置,默认底部弹窗与居中弹窗不允许交互,跟手弹窗允许交互。当设置为true时,maskColor设置无效。 - 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方法的参数包括:
- this.isShowTime: 控制模态弹窗显示和隐藏的状态变量。
- 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 })
}
}
}
演示效果
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-12-26 11:28:25修改
赞
5
收藏 4
回复
相关推荐
大佬,太牛了,能带带我吗?
6666666666
丝滑呀
1111111111111111111111111
厉害了