自定义选择弹窗

X叶域Q的主页-鸿蒙开发者社区-51CTO.COM

自定义选择弹窗

自定义弹窗(CustomDialog)是一种十分实用的交互组件,它能够让开发者根据具体的业务场景,灵活地为用户呈现各种选择、提示等交互界面。

整体分为一个界面,三个弹窗,一个按钮组件

mi1

一、自定义弹窗 (CustomDialog)

1. 定义要选择的数据类型和弹窗控制

首先,我们需要定义相关的数据类型以及用于控制弹窗的变量。在代码中,通过 @State 装饰器定义了两个重要的变量:

1
2
3
4
5
//DefSelect.ets
// 这个变量通常可以用于存储用户在弹窗中做出的选择内容,或者作为弹窗初始显示的提示信息等
@State goodsSelect: string = "弹窗选择"
// 这个变量充当了控制自定义弹窗的关键角色,后续我们会利用它来创建、打开以及管理弹窗的各种行为,比如打开弹窗展示内容、关闭弹窗等操作都需要通过这个控制器来实现。
@State dialogController: CustomDialogController | null = null;

2. 绑定事件,点击打开弹窗

为了让用户能够触发弹窗的显示,需要将打开弹窗的操作与某个用户交互行为进行绑定,常见的就是点击事件。在代码示例中,使用了一个 Text 组件,并对其添加了点击事件处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
//DefSelect.ets
Text(this.goodsSelect)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.dialogController = new CustomDialogController({
builder: PickerDialog({ goodsSelect: this.goodsSelect }) // 自定义组件,见下方
})
// 如果不为空,打开弹窗
if (this.dialogController != null) {
this.dialogController.open()
}
})

3. 自定义弹窗组件

tc1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//DefSelect.ets
// 自定义弹窗需要用CustomDialog修饰
@CustomDialog
struct PickerDialog {
// @Link双向数据绑定,达到修改主界面数据的目的
@Link goodsSelect: string;
controller: CustomDialogController;
build() {
// 自定义UI组件(见下方),自定义有参回调函数用于选择
MyTextPickerDialog({
confirm: (selectData: string) => {
this.goodsSelect = selectData
this.controller.close();
},
cancel: () => {
this.controller.close();
}
})
.height(300)
}
}

具体弹窗样式具体实现,可以设计不任意的UI界面实现

下面代码中使用了@ohos.events.emitter (Emitter)实现组件间通信,aboutToAppear自定义组件的生命周期中开始监听
下面主要是自定义了个统一的按钮组件,为了点击按钮组件(上图三个界面的按钮都用的同一块代码)的按钮触发不同界面的回调函数,具体实现见下方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//MyTextPickerDialog.ets
import { MyButton } from './MyButton';
import { emitter } from '@kit.BasicServicesKit';

@Component
export struct MyTextPickerDialog {
@State textPikerDialogId: string = "textPikerDialog";
private fruits: string[] = ['apple1', 'orange2', 'peach3', 'grape4']
private select: number = 0;
// 用户传进来的有参用于确定是执行的回调函数
confirm: ((selectData: string) => void) | undefined = undefined
cancel: (() => void) | undefined = undefined
// aboutToAppear生命周期函数,函数在创建自定义组件的新实例后,在执行其build()函数之前执行
aboutToAppear(): void {
// 单次订阅指定的事件,并在接收到该事件并执行完相应的回调函数后,自动取消订阅。
emitter.once(`${this.textPikerDialogId}确定`, () => {
if(this.confirm && this.select != -1){
this.confirm(this.fruits[this.select])
}
})
emitter.once(`${this.textPikerDialogId}取消`, () => {
if(this.cancel){
this.cancel()
}
})
}
build() {
Column() {
Text("自定义文本选择组件")

TextPicker({ range: this.fruits, selected: this.select })
.onChange((value: string | string[], index: number | number[]) => {
this.select = index as number
})
.disappearTextStyle({color: Color.Red, font: {size: 15, weight: FontWeight.Lighter}})
.textStyle({color: Color.Black, font: {size: 20, weight: FontWeight.Normal}})
.selectedTextStyle({color: Color.Blue, font: {size: 30, weight: FontWeight.Bolder}})
MyButton({buttonId: this.textPikerDialogId})
}
.height('100%')
.width('100%')
}
}

通用按钮组件,自定义通用的按钮用于不同的界面,用emtter区分是哪个界面点击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//MyButton.ets
import emitter from '@ohos.events.emitter';
@Component
export struct MyButton {
// 那个弹窗中的点击事件
buttonId: string = "";
build() {
Row() {
Button("取消", { type: ButtonType.Normal })
.height(40)
.width("43%")
.backgroundColor(Color.Brown)
.fontColor(Color.White)
.fontSize(18)
.borderRadius(5)
.onClick(() => {
let eventData: emitter.EventData = {
data: {"id": "自定义传输数据"}
};
emitter.emit(`${this.buttonId}取消`, eventData);
})

Button("确定", { type: ButtonType.Normal })
.height(40)
.width("43%")
.fontColor(Color.Blue)
.linearGradient({
direction: GradientDirection.Right,
colors: [["#02edff", 0.0],["#1281ff", 1.0]]
})
.fontSize(18)
.borderRadius(5)
.onClick(() => {
let eventData: emitter.EventData = {
data: {"id": "自定义传输数据"}
};
emitter.emit(`${this.buttonId}确定`, eventData);
})

}
.justifyContent(FlexAlign.SpaceAround)
.padding({left: 12, right: 12})
.width("100%")
}
}

二、基于半模态转场实现选择

能看出上面的弹窗是基于页面做固定定位的,位置无法变换,下面使用半模态转场实现半模态弹窗,更灵活的满足UI设计

1. 数据定义

用变量控制半模态弹窗的弹出

1
2
3
4
5
6
//DefSelect.ets
@State timeSelect: Date = new Date();
@State timeShow: boolean = false;

@State placeSelect: string = "地点选择";
@State placeShow: boolean = false;

2. 弹窗绑定

给组件绑定半模态页面(一个组件不能绑定多个,会乱弹)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//DefSelect.ets
Text(`${this.timeSelect.getHours()}小时${this.timeSelect.getMinutes()}分钟`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
// 给组件绑定半模态页面,点击后显示模态页面。this.timeSelectBuilder()自定义组件(见下方)
.bindSheet(this.timeShow, this.timeSelectBuilder(),{
width: "100%",
maskColor: 'rgba(125, 125, 125, 0.5)', // 蒙层颜色
showClose: false,
height: '40%',
mode: SheetMode.EMBEDDED,
// 半模态弹窗生命周期函数
shouldDismiss: () => {
this.timeShow = false;
}
})
.onClick(() => {
this.timeShow = true;
})
Text(this.placeSelect)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.bindSheet(this.placeShow, this.placeSelectBuilder(),{
width: "100%",
maskColor: 'rgba(125, 125, 125, 0.5)',
showClose: false,
height: '30%',
mode: SheetMode.EMBEDDED,
// 半模态弹窗生命周期函数
shouldDismiss: () => {
this.placeShow = false;
}
})
.onClick(() => {
this.placeShow = true;
})

3. 自定义@Builder

两种不同的方式实现将选择的数据同步到主界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//DefSelect.ets
@Builder
timeSelectBuilder(){
TimeBinSheet({
isShow: this.timeShow,
timeSelect: this.timeSelect // TimeBinSheet通过@Link数据双向绑定实现
})
}
@Builder
placeSelectBuilder(){
PlaceBinSheet({
isShow: this.placeShow,
confirm: (city: string) => { // PlaceBinSheet通过传入回调函数实现
this.placeSelect = city;
}
})
}

下面代码可以灵活修改实现自定义弹窗,重点是确定后的数据修改

image-20241222192242841

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//TimeBinSheet.ets
import { MyButton } from './MyButton';
import { emitter } from '@kit.BasicServicesKit';

@Component
export struct TimeBinSheet {
@Link isShow: boolean;
@Link timeSelect: Date;
@State time: Date = new Date()
@State TimeBinSheetId: string = "TimeBinSheet"

aboutToAppear(): void {
// 接受按钮的点击事件
emitter.once(`${this.TimeBinSheetId}确定`, () => {
this.timeSelect = this.time; // 将事件修改为选择时间
this.isShow = false; // 关闭弹窗
})
emitter.once(`${this.TimeBinSheetId}取消`, () => {
this.isShow = false;
})
}

build() {
Column() {
Text("自定义时间选择组件")
TimePicker({
selected: this.timeSelect,
format: TimePickerFormat.HOUR_MINUTE,
})
.useMilitaryTime(true)
.onChange((value: TimePickerResult) => {
this.time.setHours(value.hour, value.minute)
console.info('select current date is: ' + JSON.stringify(value))
})
.disappearTextStyle({color: "#F6F6F6", font: {size: 18, weight: FontWeight.Lighter}})
.textStyle({color: "#858585", font: {size: 18, weight: FontWeight.Normal}})
.selectedTextStyle({color: "#000000", font: {size: 18}})
.width("76%")
.height("74%")
MyButton({buttonId: this.TimeBinSheetId})
}
.height('100%')
.width('100%')
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//PlaceBinSheet.ets
import { MyButton } from './MyButton';
import { emitter } from '@kit.BasicServicesKit';

@Component
export struct PlaceBinSheet {
PlaceBinSheetId: string = "PlaceBinSheet"
@Link isShow: boolean;
cityList: Array<string> = ["北京", "南京", "深圳", "厦门"];

@State selectCity: number = -1;
confirm: ((city: string) => void) | undefined = undefined;
// 上同
aboutToAppear(): void {
emitter.once(`${this.PlaceBinSheetId}确定`, () => {
// 如果传入了确定的回调函数那么就执行
if(this.confirm && this.selectCity != -1){
this.confirm(this.cityList[this.selectCity]);
}
this.isShow = false;
})
emitter.once(`${this.PlaceBinSheetId}取消`, () => {
this.isShow = false;
})
}
build() {
Column() {
Text("自定义地点选择组件")
Row(){
ForEach(this.cityList,(item: string, index)=> {
Button({ type: ButtonType.Normal, stateEffect: true }) {
Text(item)
.fontSize(14)
.fontColor(this.selectCity === index ? Color.Black : Color.White)
}
.height(40)
.width('23%')
.borderRadius(4)
.backgroundColor(this.selectCity === index ? Color.Red : Color.Blue)
.onClick(() => {
this.selectCity = index
})
})
}
.width("80%")
.justifyContent(FlexAlign.SpaceAround)
.margin(20)

MyButton({buttonId: this.PlaceBinSheetId})
}
.height('100%')
.width('100%')
}
}

自定义选择弹窗
https://leaf-domain.gitee.io/2025/03/22/openharmony/自定义选择弹窗/
作者
叶域
发布于
2025年3月22日
许可协议