纯血鸿蒙APP实战开发——自定义安全键盘案例

慈云数据 2024-05-14 技术支持 50 0

介绍

金融类应用在密码输入时,一般会使用自定义安全键盘。本示例介绍如何使用TextInput组件实现自定义安全键盘场景,主要包括TextInput.customKeyboard绑定自定义键盘、自定义键盘布局和状态更新等知识点。

效果图预览

实现思路

1. 使用TextInput的customKeyboard的属性方法来设置自定义键盘

当设置自定义键盘时,输入框激活后不会打开系统输入法,而是加载应用指定的自定义组件,针对系统键盘的enterKeyType属性设置将无效。自定义键盘采用覆盖原始界面的方式呈现,不会对应用原始界面产生压缩或者上提。默认在输入控件失去焦点时,关闭自定义键盘,开发者也可以通过TextInputController.stopEditing方法控制键盘关闭。

2. 自定义键盘布局

键盘枚举类型:

  • 键盘类型分为数字键盘,大写、小写键盘,特殊字符键盘
  • 键盘按键类型分为输入操作INPUT、删除操作DELETE、切换数字键盘操作NUMERIC、切换大小写键盘CAPSLOCK、切换数字键盘SPECIAL共五种类型
    /**
     * 键盘类型枚举
     */
    export enum EKeyboardType {
      NUMERIC,    //数字键盘
      UPPERCASE,  // 大写字母键盘
      LOWERCASE,  // 小写字母键盘
      SPECIAL,    // 特殊字符键盘
    }
    /**
     * 键盘按键类型枚举
     */
    export enum EKeyType {
      INPUT,   // 输入类型,输入具体的值
      DELETE,  // 删除一个输入字符
      NUMERIC, // 切换数字键盘
      CAPSLOCK, // 切换大小写键盘
      SPECIAL, //  切换特殊字符键盘
    }
    

    在真实业务场景下,自定义安全键盘数据包括值、UI属性、位置等都通过数据请求来下发,键盘按键数据接口定义如下:

    /**
     * 键盘按键数据接口
     */
    export interface IKeyAttribute {
      label: string | Resource;
      value?: string;
      type?: EKeyType;
      fontSize?: number;
      fontColor?: string | Color;
      backgroundColor?: string | Color;
      position?: [number, number, number, number];
    }
    

    自定义键盘布局:分为标题栏和键盘两部分,键盘使用Grid布局,每个按键GridItem的值、UI属性和位置都通过数据请求下发,不需要额外计算。

    数字键盘为4*3的网格布局,但是大小写键盘和特殊字符键盘的布局为不规则布局,如果设置为4 * 10的网格,有的按键占用1 * 1.5,但是GridItem属性不支持占用非整数列。本文将该场景下将网格拆分为更小的单元,为4 * 20网格布局,每个字母按键占1 * 2,删除按键则占1 * 3,空格则占1 * 10,这样就保证每个按键都要占用整数单元。

    Column() {
      this.titleBar();
      Grid() {
        ForEach(this.items, (item: IKeyAttribute) => {
          GridItem() {
            this.myGridItem(item)
          }
          .width('100%')
          .height(this.itemHeight)
          .rowStart(item?.position?.[0])
          .columnEnd(item?.position?.[1])
          .columnStart(item?.position?.[2])
          .columnEnd(item?.position?.[3])
          .backgroundColor(item.backgroundColor)
          .borderRadius($r("app.integer.key_border_radius"))
          .onClick(() => {
                ....
          })
        }, (item: IKeyAttribute, index: number) => JSON.stringify(item) + index)
      }
      .margin({ bottom: $r("app.integer.key_board_marin_bottom") })
      .columnsTemplate(this.curKeyboardType === EKeyboardType.NUMERIC ? "1fr 1fr 1fr" :
        "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr")
      .rowsTemplate("1fr 1fr 1fr 1fr") // Grid高度均分成4份
      .rowsGap(this.rowSpace) // 设置行间距
      .columnsGap(this.columnSpace) // 设置列间距
      .width('100%')
      .height(this.itemHeight * this.rowCount + this.rowSpace * (this.rowCount - 1))
    }
    .width('100%')
    .padding({ left: this.columnSpace, right: this.columnSpace })
    .backgroundColor(Color.Black)
    }
    
    3. 状态更新

    主要是子组件自定义键盘的按键事件如何传递到父组件,可以在父组件定义好键盘按键事件响应函数onKeyboardEvent,传递给子组件,然后子组件按键时调用父组件传递过来的onKeyboardEvent即可。需要注意的是,在子组件中,必须定义inputValue且使用@Link装饰器,这样能保证子组件调用时onKeyboardEvent时inputValue不为空,父子组件数据双向更新。

    @Component
    export struct CustomSafeKeyboardView {
      @State inputValue: string = '';
      @State items: IKeyAttribute[] = numericKeyData;
      @State curKeyboardType: EKeyboardType = EKeyboardType.NUMERIC;
      controller: TextInputController = new TextInputController();
      /**
       * 键盘按键事件响应函数
       * @param item
       */
      onKeyboardEvent(item: IKeyAttribute) {
        switch (item.type) {
          // 输入类型,更新输入内容
          case EKeyType.INPUT:
            this.inputValue += item.value;
            break;
          // 删除一个已输入的末尾字符
          case EKeyType.DELETE:
            this.inputValue = this.inputValue.slice(0, -1);
            break;
          // 切换数字字符键盘
          case EKeyType.NUMERIC:
            if (this.curKeyboardType !== EKeyboardType.NUMERIC) {
              this.curKeyboardType = EKeyboardType.NUMERIC;
              this.items = numericKeyData;
            }
            break;
          // 切换大小写
          case EKeyType.CAPSLOCK:
            if (this.curKeyboardType === EKeyboardType.LOWERCASE) {
              // 切换大写字母键盘
              this.curKeyboardType = EKeyboardType.UPPERCASE;
              this.items = upperCaseKeyData;
            } else {
              // 切换小写字母键盘
              this.curKeyboardType = EKeyboardType.LOWERCASE;
              this.items = lowerCaseKeyData;
            }
            break;
          // 切换特殊字符键盘
          case EKeyType.SPECIAL:
            if (this.curKeyboardType !== EKeyboardType.SPECIAL) {
              this.curKeyboardType = EKeyboardType.SPECIAL;
              this.items = specialKeyData;
            }
            break;
          default:
            console.info(`Sorry, we are out of input type.`);
        }
      }
      /**
       * 自定义键盘组件Builder
       */
      @Builder
      customKeyboardBuilder() {
        CustomKeyboard({
          items: this.items,
          inputValue: this.inputValue,
          curKeyboardType: this.curKeyboardType,
          onKeyboardEvent: this.onKeyboardEvent,
          controller: this.controller
        })
      }
      build() {
        Column() {
          Row()
            .height($r("app.integer.row_height"))
          Image($r("app.media.avatar"))
            .width($r("app.integer.avatar_weight"))
            .height($r("app.integer.avatar_height"))
            .objectFit(ImageFit.Fill)
          Text($r("app.string.account_name"))
            .fontSize($r("app.integer.text_font_size"))
            .margin({ top: $r("app.integer.common_margin_padding") })
          TextInput({
            text: this.inputValue,
            placeholder: $r("app.string.placeholder"),
            controller: this.controller
          })// 绑定自定义键盘
            .type(InputType.Password)
            .customKeyboard(this.customKeyboardBuilder())// 绑定自定义安全键盘
            .height($r("app.integer.text_input_height"))
            .border(null)
            .margin({ top: $r("app.integer.common_margin_padding") })
          Button($r("app.string.login_button_label"))
            .type(ButtonType.Capsule)
            .fontSize($r("app.integer.login_button_font_size"))
            .width($r("app.integer.login_button_width"))
            .height($r("app.integer.login_button_height"))
            .margin({ top: $r("app.integer.login_button_margin") })
            .backgroundColor(Color.Pink)
            .onClick(() => {
              this.controller.stopEditing();
            })
        }
        .width($r("app.string.one_hundred_percent"))
        .height($r("app.string.one_hundred_percent"))
        .padding($r("app.integer.common_margin_padding"))
      }
    }
    

    高性能知识点

    不涉及

    工程结构&模块类型

    customsafekeyboard              // har类型
    |---components                  // 自定义组件
    |   ---CustomKeyboard.ets  
    |---model                       // 模型层
    |   ---Constants                // 定义常量数据
    |---CustomSafeKeyboardView.ets  // 主页面
    

    模块依赖

    1. 依赖common模块来实现日志的打印
    2. 依赖路由模块,供entry模块实现路由导航

    参考资料

    TextInput

    鸿蒙全栈开发全新学习指南

    也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】。

    本路线共分为四个阶段:

    第一阶段:鸿蒙初中级开发必备技能

    第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

    第三阶段:应用开发中高级就业技术

    第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

    《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

    如何快速入门?

    1.基本概念

    2.构建第一个ArkTS应用

    3.……

    开发基础知识:gitee.com/MNxiaona/733GH

    1.应用基础知识

    2.配置文件

    3.应用数据管理

    4.应用安全管理

    5.应用隐私保护

    6.三方应用调用管控机制

    7.资源分类与访问

    8.学习ArkTS语言

    9.……

    基于ArkTS 开发

    1.Ability开发

    2.UI开发

    3.公共事件与通知

    4.窗口管理

    5.媒体

    6.安全

    7.网络与链接

    8.电话服务

    9.数据管理

    10.后台任务(Background Task)管理

    11.设备管理

    12.设备使用信息统计

    13.DFX

    14.国际化开发

    15.折叠屏系列

    16.……

    鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

    鸿蒙入门教学视频:

    美团APP实战开发教学:gitee.com/MNxiaona/733GH

    写在最后

    • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
    • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
    • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
    • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon