自定义选择弹窗,半模态弹窗 原创

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
收藏
回复
举报
回复
    相关推荐