【擁抱鴻蒙】HarmonyOS之構(gòu)建一個自定義彈框

彈窗是一種模態(tài)窗口,通常用來展示用戶當(dāng)前需要的或用戶必須關(guān)注的信息或操作。在UI開發(fā)中,彈框是重要且不可忽視的組件。

HarmonyOS內(nèi)置了多種系統(tǒng)彈框,分別有AlertDialog 、TextPickerDialog 、DatePickerDialog以及TimePickerDialog等。

本文將詳細(xì)介紹系統(tǒng)彈框的封裝和使用,并著重展現(xiàn)自定義彈框的實現(xiàn)。

系統(tǒng)彈框

AlertDialog

AlertDialog是警告彈窗,一般由App主動彈出,用于警告和確認(rèn)用戶的操作行為,需用戶手動點擊操作按鈕來取消或進(jìn)行下一步。

AlertDialog的實現(xiàn)

如下圖中的“刪除聯(lián)系人”彈框,一個AlertDialog包含標(biāo)題、內(nèi)容和操作區(qū)三個部分組成,操作區(qū)包含兩個按鈕,我們可以在按鈕的點擊事件里添加對應(yīng)響應(yīng)邏輯。

[圖片上傳失敗...(image-3d127d-1748180518510)]

以上彈框的實現(xiàn)代碼如下:


AlertDialog.show(

      {

        title: '刪除聯(lián)系人', // 標(biāo)題

        message: '是否需要刪除所選聯(lián)系人?', // 內(nèi)容

        autoCancel: false, // 點擊遮障層時,是否關(guān)閉彈窗。

        alignment: DialogAlignment.Bottom, // 彈窗在豎直方向的對齊方式

        offset: { dx: 0, dy: -20 }, // 彈窗相對alignment位置的偏移量

        primaryButton: {

          value: '取消',

          action: () => {

            console.info('Callback when the first button is clicked');

          }

        },

        secondaryButton: {

          value: '刪除',

          fontColor: '#D94838',

          action: () => {

            console.info('Callback when the second button is clicked');

          }

        },

        cancel: () => { // 點擊遮障層關(guān)閉dialog時的回調(diào)

          console.info('Closed callbacks');

        }

      }

    )

  })

AlertDialog的封裝

我們可以對AlertDialog進(jìn)行封裝,作為工具類調(diào)用。


export class CommonUtils {

/\*\*

   \* Common alert dialog

   \* @param title 標(biāo)題

   \* @param msg 提示信息

   \* @param context 需要保存狀態(tài)的UIAbility所對應(yīng)的context

   \* @param primaryCallback 第一個按鈕點擊事件的回調(diào)

   \* @param secondCallback 第二個按鈕點擊事件的回調(diào)

   \*/

  commonAlertDialog(title:ResourceStr, msg: ResourceStr, context: common.UIAbilityContext, primaryCallback: Function, secondCallback: Function) {

    AlertDialog.show({

      title: title,

      message: msg,

      alignment: DialogAlignment.Bottom,

      offset: {

        dx: 0,

        dy: CommonConstants.DY\_OFFSET

      },

      primaryButton: {

        value: $r('app.string.cancel\_button'),

        action: () => {

          primaryCallback();

        }

      },

      secondaryButton: {

        value: $r('app.string.definite\_button'),

        action: () => {

          context.terminateSelf()

          secondCallback();

        }

      }

    });

  }

}

這里創(chuàng)建了CommonUtils的工具類,把標(biāo)題、提示信息作為創(chuàng)建自定義彈框的參數(shù),按鈕的點擊事件可在回調(diào)里分別實現(xiàn)。

有了這種封裝,我們就能很容易地在App里調(diào)用一個風(fēng)格統(tǒng)一的AlertDialog彈框了。


CommonUtils.commonAlertDialog("提示", "是否退出登錄", context, () => {

  // 取消

  

}, () => {

  // 確認(rèn)

  

});

TextPickerDialog

這是一種文本滑動選擇彈窗,一般用于從多個選項中單選內(nèi)容,再將用戶所選的內(nèi)容返回給調(diào)用方。

如下圖所示,這里實現(xiàn)了一個選擇“足球主隊”的彈窗,用戶上下滑動滑塊再點擊“確認(rèn)”就可以完成選擇。

[圖片上傳失敗...(image-9680c4-1748180659687)]

TextPickerDialog的實現(xiàn)


@Entry

@Component

struct TextPickerDialogDemo {

  @State select: number = 2;

  private fruits: string[] = ['巴塞羅那', '曼城', '利物浦', '邁阿密國際', '拜仁慕尼黑', '多特蒙德', 'AC米蘭', '那不勒斯'];



  build() {

    Column() {

      Button('TextPickerDialog')

        .margin(20)

        .onClick(() => {

          TextPickerDialog.show({

            range: this.fruits, // 設(shè)置文本選擇器的選擇范圍

            selected: this.select, // 設(shè)置初始選中項的索引值。

            onAccept: (value: TextPickerResult) => { // 點擊彈窗中的“確定”按鈕時觸發(fā)該回調(diào)。

              // 設(shè)置select為按下確定按鈕時候的選中項index,這樣當(dāng)彈窗再次彈出時顯示選中的是上一次確定的選項

              this.select = value.index;

              console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));

            },

            onCancel: () => { // 點擊彈窗中的“取消”按鈕時觸發(fā)該回調(diào)。

              console.info("TextPickerDialog:onCancel()");

            },

            onChange: (value: TextPickerResult) => { // 滑動彈窗中的選擇器使當(dāng)前選中項改變時觸發(fā)該回調(diào)。

              console.info('TextPickerDialog:onChange()' + JSON.stringify(value));

            }

          })

        })

    }

    .width('100%')

  }

}

TextPickerDialog的封裝

我們可以將選項作為參數(shù)進(jìn)行TextPickerDialog的封裝,并提供用戶確認(rèn)選項的回調(diào)。

這里的range的類型為:string[] | string[][] | Resource | TextPickerRangeContent[] | TextCascadePickerRangeContent[],提供了多種數(shù)據(jù)源類型,我們一般使用Resource方便多語言適配。


export class CommonUtils {

  /\*\*

   \* Text picker dialog

   \* @param items 選項

   \* @param textCallback 選中返回

   \*/

  textPickerDialog(items: Resource, textCallback: Function) {

    if (this.isEmpty(items)) {

      Logger.error(CommonConstants.TAG\_COMMON\_UTILS, 'item is null')

      return;

    }



    TextPickerDialog.show({

      range: items,

      canLoop: false,

      selected: 0,

      onAccept: (result: TextPickerResult) => {

        textCallback(result.value);

      },

      onCancel: () => {

        Logger.info(CommonConstants.TAG\_COMMON\_UTILS, 'TextPickerDialog canceled')

      }

    });

  }

}

對工具類中的TextPickerDialog的調(diào)用如下:


CommonUtils.textPickerDialog($r('app.strarray.club\_array'), (selectedValue: string) => {

            this.club = selectedValue;

          })

        }

這里的app.strarray.club\_array指向resources中的配置文件stringarray.json5,其內(nèi)容如下:


{

    "strarray": [

        {

            "name": "club\_array",

            "value": [

                {

                    "value": "巴塞羅那"

                },

                {

                    "value": "曼城"

                },

                {

                    "value": "利物浦"

                },

                {

                    "value": "邁阿密國際"

                },

                {

                    "value": "拜仁慕尼黑"

                },

                {

                    "value": "AC米蘭"

                },

                {

                    "value": "多特蒙德"

                },

                {

                    "value": "阿賈克斯"

                }

            ]

        }

    ]

}

DatePickerDialog

DatePickerDialog是日期選擇器彈框,用于選擇特定格式的日期,并返回給調(diào)用方。

[圖片上傳失敗...(image-118d04-1748180703669)]

DatePickerDialog的實現(xiàn)

以“出生日期”選擇器彈框為例,我們通過如下代碼可以實現(xiàn):


let selectedDate = new Date('1949-10-1');

DatePickerDialog.show({

            start: new Date('1900-1-1'), // 設(shè)置選擇器的起始日期

            end: new Date('2000-12-31'), // 設(shè)置選擇器的結(jié)束日期

            selected: selectedDate, // 設(shè)置當(dāng)前選中的日期

            lunar: false,

            onDateAccept: (value: Date) => { // 點擊彈窗中的“確定”按鈕時觸發(fā)該回調(diào)

              // 通過Date的setFullYear方法設(shè)置按下確定按鈕時的日期,這樣當(dāng)彈窗再次彈出時顯示選中的是上一次確定的日期

              selectedDate.setFullYear(value.getFullYear(), value.getMonth() + 1, value.getDate())

              console.info('DatePickerDialog:onDateAccept()' + JSON.stringify(value))

            },

            onCancel: () => { // 點擊彈窗中的“取消”按鈕時觸發(fā)該回調(diào)

              console.info('DatePickerDialog:onCancel()')

            },

            onDateChange: (value: Date) => { // 滑動彈窗中的滑動選擇器使當(dāng)前選中項改變時觸發(fā)該回調(diào)

              console.info('DatePickerDialog:onDateChange()' + JSON.stringify(value))

            }

          })

        })

DatePickerDialog的封裝

日期選擇器包含起始日期、截止日期和默認(rèn)選中日期三個參數(shù),我們只需對用戶確認(rèn)選擇后的回調(diào)里響應(yīng)即可。


export class CommonUtils {

  /\*\*

   \* Date picker dialog

   \* @param dateCallback 確認(rèn)選中日期回調(diào)

   \*/

  datePickerDialog(dateCallback: Function) {

    DatePickerDialog.show({

      start: new Date(CommonConstants.START\_TIME),

      end: new Date(),

      selected: new Date(CommonConstants.SELECT\_TIME),

      lunar: false,

      onDateAccept: (value: Date) => {

        let year: number = value.getFullYear();

        let month: number = value.getMonth() + 1;

        let day: number = value.getDate();

        let selectedDate: string = `${year}${CommonConstants.DATE\_YEAR}`+`${month}${CommonConstants.DATE\_MONTH}`+`${day}${CommonConstants.DATE\_DAY}`;

        dateCallback(selectedDate);

      }

    });

  }

}

基于以上封裝,datePickerDialog的調(diào)用可以簡單地實現(xiàn)如下:


CommonUtils.datePickerDialog((dateValue: string) => {

    this.birthdate = dateValue;

})

自定義彈框

除了系統(tǒng)彈框,還可以對彈框進(jìn)行自定義。自定義彈框更加靈活,適用于更多的業(yè)務(wù)場景。

這里,我們實現(xiàn)一個包含多選器的自定義彈框,其實現(xiàn)效果如下圖所示。

[圖片上傳失敗...(image-8db52c-1748180703669)]

不難看出,這個彈框由標(biāo)題、選擇列表和按鈕操作區(qū)構(gòu)成。

自定義彈框需要使用裝飾器@CustomDialog,

我們創(chuàng)建一個名為CustomDialogWidget的struct,并添加三個屬性。

* items是數(shù)據(jù)源;

* selectedContent是選中結(jié)果拼接而成的字符串;

* controller是自定義彈框的控制器,其類型為CustomDialogController。


export default struct CustomDialogWidget {

  @State items: Array<CustomItem> = [];

  @Link selectedContent: string;

  private controller?: CustomDialogController;

}

在組件的aboutToAppear()中實現(xiàn)數(shù)據(jù)源的獲取,使用到resmgr.ResourceManagergetStringArrayValue方法。


aboutToAppear(): void {

    let context: Context = getContext(this);

    if (CommonUtils.isEmpty(context) || CommonUtils.isEmpty(context.resourceManager)) {

      Logger.error(CommonConstants.TAG\_CUSTOM, 'context or resourceManager is null');

      return;

    }



    let manager = context.resourceManager;

    manager.getStringArrayValue($r('app.strarray.hobbies\_data').id, (error, hobbyArray) => {

      if (!CommonUtils.isEmpty(error)) {

        Logger.error(CommonConstants.TAG\_CUSTOM, 'error = ' + JSON.stringify(error));

      } else {

        hobbyArray.forEach((itemTitle: string) => {

          let item = new CustomItem();

          item.title = itemTitle;

          item.isChecked = false;

          this.items.push(item);



          Logger.info(item.title);

        });

      }

    });

  }

然后在Build()中實現(xiàn)其界面的搭建:


  build() {

    Column() {

      // 標(biāo)題

      Text($r('app.string.title\_hobbies'))

        .fontSize($r('app.float.title\_hobbies\_size'))

        .fontColor($r('app.color.custom\_color'))

        .lineHeight($r('app.float.title\_line\_height'))

        .fontWeight(CommonConstants.BIGGER)

        .alignSelf(ItemAlign.Start)

        .margin({ left: $r('app.float.title\_left\_distance') })



      // 選項列表

      List() {

       ForEach(this.items, (item: CustomItem) => {

         ListItem() {

          Row() {

            Text(item.title)

              .fontSize($r('app.float.label\_size'))

              .fontColor($r('app.color.custom\_color'))

              .layoutWeight(CommonConstants.WEIGHT\_ONE)

              .textAlign(TextAlign.Start)

              .fontWeight(CommonConstants.BIGGER)

              .margin({ left: $r('app.float.label\_left\_distance') })

            Toggle({ type: ToggleType.Checkbox, isOn: false })

              .onChange((isCheck) => {

                item.isChecked = isCheck;

              })

              .width($r('app.float.toggle\_size'))

              .height($r('app.float.toggle\_size'))

              .margin({ right: $r('app.float.toggle\_right\_distance') })

          }

         }

         .height($r('app.float.options\_height'))

         .margin({

           top: $r('app.float.options\_top\_distance'),

           bottom:$r('app.float.options\_bottom\_distance')

         })

       }, (item: CustomItem) => JSON.stringify(item.title))

      }

      .margin({

        top: $r('app.float.list\_top\_distance'),

        bottom: $r('app.float.list\_bottom\_distance')

      })

      .divider({

        strokeWidth: $r('app.float.divider\_height'),

        color: $r('app.color.divider\_color')

      })

      .listDirection(Axis.Vertical)

      .edgeEffect(EdgeEffect.None)

      .width(CommonConstants.FULL\_WIDTH)

      .height($r('app.float.options\_list\_height'))



      // 操作按鈕

      Row() {

        Button($r('app.string.cancel\_button'))

          .dialogButtonStyle()

          .onClick(() => {

            this.controller?.close();

          })



        Blank()

          .backgroundColor($r('app.color.custom\_blank\_color'))

          .width($r('app.float.blank\_width'))

          .opacity($r('app.float.blank\_opacity'))

          .height($r('app.float.blank\_height'))



        Button($r('app.string.definite\_button'))

          .dialogButtonStyle()

          .onClick(() => {

            this.setSelectedItems(this.items);

            this.controller?.close();

          })

      }

    }

  }

在確定按鈕的回調(diào)中,我們調(diào)用setSelectedItems(),其實現(xiàn)如下:


  setSelectedItems(items: CustomItem[]) {

    if (CommonUtils.isEmpty(items)) {

      Logger.error(CommonConstants.TAG\_HOME, "Items is empty")

      return;

    }



    let selectedText: string = items.filter((isCheckedItem: CustomItem) => isCheckedItem?.isChecked)

      .map<string>((checkedItem: CustomItem) => {

        return checkedItem.title!;

      })

      .join(CommonConstants.COMMA);



    if (!CommonUtils.isEmpty(selectedText)) {

      this.selectedContent = selectedText;

    }

  }

}

這里我們還用到了組件的屬性擴(kuò)展方法封裝(用于提取重復(fù)的屬性代碼進(jìn)行復(fù)用):


@Extend(Button)

function dialogButtonStyle() {

  .fontSize($r('app.float.button\_text\_size'))

  .fontColor(Color.Blue)

  .layoutWeight(CommonConstants.WEIGHT\_ONE)

  .height($r('app.float.button\_height'))

  .backgroundColor(Color.White)

}

自定義彈框的調(diào)用

自定義彈框的調(diào)用基于CustomDialogController,將CustomDialogWidget作為它的參數(shù)builder即可實現(xiàn)控制器調(diào)出我們預(yù)期的自定義彈框。


@State birthdate: string = '';

@State sex: string = '';

@State hobbies: string = '';

private sexArray: Resource = $r('app.strarray.sex\_array');



customDialogController: CustomDialogController = new CustomDialogController({

    builder: CustomDialogWidget({

      selectedContent: this.hobbies

    }),

    alignment: DialogAlignment.Bottom,

    customStyle: true,

    offset: {

      dx: 0,

      dy: CommonConstants.DY\_OFFSET

    }

  });

以上,我們總結(jié)了HarmonyOS系統(tǒng)彈框和自定義彈框的實現(xiàn)、封裝及調(diào)用。

我是鄭知魚??,歡迎大家討論與指教。

如果你覺得有所收獲,也請點贊????收藏??關(guān)注??我吧~~

具體代碼見:customDialog

參考:<HarmonyOS第一課>構(gòu)建更加豐富的頁面

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容