回复
自定义选择弹窗,半模态弹窗 原创
X叶域Q
发布于 2024-12-22 19:37
浏览
0收藏
自定义弹窗(CustomDialog)是一种十分实用的交互组件,它能够让开发者根据具体的业务场景,灵活地为用户呈现各种选择、提示等交互界面。
整体分为一个界面,三个弹窗,一个按钮组件。视频展示见我主页视频
一、自定义弹窗 (CustomDialog)
1. 定义要选择的数据类型和弹窗控制
首先,我们需要定义相关的数据类型以及用于控制弹窗的变量。在代码中,通过 @State
装饰器定义了两个重要的变量:
//DefSelect.ets
// 这个变量通常可以用于存储用户在弹窗中做出的选择内容,或者作为弹窗初始显示的提示信息等
@State goodsSelect: string = "弹窗选择"
// 这个变量充当了控制自定义弹窗的关键角色,后续我们会利用它来创建、打开以及管理弹窗的各种行为,比如打开弹窗展示内容、关闭弹窗等操作都需要通过这个控制器来实现。
@State dialogController: CustomDialogController | null = null;
2. 绑定事件,点击打开弹窗
为了让用户能够触发弹窗的显示,需要将打开弹窗的操作与某个用户交互行为进行绑定,常见的就是点击事件。在代码示例中,使用了一个 Text
组件,并对其添加了点击事件处理逻辑。
//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. 自定义弹窗组件
//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自定义组件的生命周期中开始监听
下面主要是自定义了个统一的按钮组件,为了点击按钮组件(上图三个界面的按钮都用的同一块代码)的按钮触发不同界面的回调函数,具体实现见下方
//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区分是哪个界面点击
//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. 数据定义
用变量控制半模态弹窗的弹出
//DefSelect.ets
@State timeSelect: Date = new Date();
@State timeShow: boolean = false;
@State placeSelect: string = "地点选择";
@State placeShow: boolean = false;
2. 弹窗绑定
给组件绑定半模态页面(一个组件不能绑定多个,会乱弹)
//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
两种不同的方式实现将选择的数据同步到主界面
//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;
}
})
}
下面代码可以灵活修改实现自定义弹窗,重点是确定后的数据修改,最后附上两个组件的代码
//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%')
}
}
//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%')
}
}
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
赞
1
收藏
回复
相关推荐