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

介绍

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

效果图预览

实现思路

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

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

2. 自定义键盘布局

键盘枚举类型:

  • 键盘类型分为数字键盘,大写、小写键盘,特殊字符键盘
  • 键盘按键类型分为输入操作INPUT、删除操作DELETE、切换数字键盘操作NUMERIC、切换大小写键盘CAPSLOCK、切换数字键盘SPECIAL共五种类型
typescript 复制代码
/**
 * 键盘类型枚举
 */
export enum EKeyboardType {
  NUMERIC,    //数字键盘
  UPPERCASE,  // 大写字母键盘
  LOWERCASE,  // 小写字母键盘
  SPECIAL,    // 特殊字符键盘
}

/**
 * 键盘按键类型枚举
 */
export enum EKeyType {
  INPUT,   // 输入类型,输入具体的值
  DELETE,  // 删除一个输入字符
  NUMERIC, // 切换数字键盘
  CAPSLOCK, // 切换大小写键盘
  SPECIAL, //  切换特殊字符键盘
}

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

typescript 复制代码
/**
 * 键盘按键数据接口
 */
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,这样就保证每个按键都要占用整数单元。

typescript 复制代码
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不为空,父子组件数据双向更新。

typescript 复制代码
@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
相关推荐
毕设源码-赖学姐20 分钟前
【开题答辩全过程】以 高校学科竞赛管理系统安全开发为例,包含答辩的问题和答案
安全
麦聪聊数据23 分钟前
智慧医疗数据互联互通:使用 QuickAPI 构建实时诊疗数据交换层
数据库·sql·安全
Miguo94well34 分钟前
Flutter框架跨平台鸿蒙开发——地理知识速记APP的开发流程
flutter·华为·harmonyos·鸿蒙
时光慢煮1 小时前
【Flutter × OpenHarmony】跨端开发实现全局Toast提示卡片
flutter·华为·开源·openharmony
IT陈图图2 小时前
Flutter × OpenHarmony 混合布局实战:在一个容器中优雅组合列表与网格
flutter·鸿蒙·openharmony
讯方洋哥2 小时前
HarmonyOS App开发——鸿蒙音乐播放机应用App开发
华为·harmonyos
小雨青年3 小时前
【鸿蒙原生开发会议随记 Pro】 会议随记 Pro v1.1 发布 详解 HarmonyOS NEXT 原生国际化(i18n)架构与最佳实践
华为·harmonyos
啥都想学点3 小时前
kali基础介绍(Reconnaissance侦察)
安全
木斯佳4 小时前
HarmonyOS 6实战(源码教学篇)— Speech Kit TextReader:【仿某云音乐接入语音朗读控件】
华为·harmonyos
kirk_wang5 小时前
Flutter艺术探索-Flutter渲染优化:Widget生命周期与性能分析
flutter·移动开发·flutter教程·移动开发教程