鸿蒙应用框架开发【自绘编辑框】 输入法框架

自绘编辑框

介绍

本示例通过输入法框架实现自会编辑框,可以绑定输入法应用,从输入法应用输入内容,显示和隐藏输入法。

效果预览

1

使用说明

1.点击编辑框可以绑定并拉起输入法,可以从输入法键盘输入内容到编辑框。

2.可以点击attach/dettachshow/hideon/off按钮来绑定/解绑、显示/隐藏、开启监听/关闭监听。

3.输入光标信息后点击updateCursor向输入法应用发送光标信息,发送成功会右toast提示。

4.输入选中文本的开始和结束位置,点击changeSelection可以选中文本。

5.选择文本输入类型和Enter键类型后,点击updateAttribute可以更新拉起的输入法的输入类型和Enter键类型,依赖输入法应用是否适配。

具体实现

  • 自绘编辑框

    • 使用输入法框架实现组件绑定输入法应用,监听输入法事件,显示和隐藏输入法,发送光标和编辑框属性到输入法应用功能。
    • 源码链接:[Index.ets]
/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { inputMethod } from '@kit.IMEKit';
import { promptAction } from '@kit.ArkUI';
import { CustomInputText } from '../components/CustomInputText';
import { inputAttribute } from '../utils/InputAttributeInit';
import { logger } from '../utils/Logger';
import { BusinessError } from '@kit.BasicServicesKit';


@Extend(Button) function buttonStyle() {
  .type(ButtonType.Capsule)
  .backgroundColor($r('app.color.button_color'))
  .fontColor(Color.White)
  .fontSize(16)
  .height(40)
  .fontWeight(500)
  .width('100%')
}

@Extend(Select) function selectStyle() {
  .fontColor($r('app.color.text_color'))
  .font({ size: 16, weight: 500 })
}

@Extend(Text) function textStyle() {
  .fontColor($r('app.color.text_color'))
  .fontSize(16)
  .fontWeight(500)
}

@Extend(TextInput) function inputStyle() {
  .type(InputType.Number)
  .height(40)
  .placeholderFont({ size: 14 })
  .margin({ top: 8, bottom: 8 })
  .maxLength(4)
  .layoutWeight(1)
}

@Styles function cardStyle() {
  .padding(12)
  .width('100%')
  .backgroundColor(Color.White)
  .borderRadius(23)
}

@Entry
@Component
struct Index {
  private inputTypeArray: Array<SelectOption> = [];
  private enterKeyArray: Array<SelectOption> = []
  private inputController: inputMethod.InputMethodController = inputMethod.getController();
  private cursorInfo: inputMethod.CursorInfo = { top: 0, left: 0, width: 0, height: 0 };
  @Provide selectStart: number = 0;
  @Provide selectEnd: number = 0;
  @Provide isAttached: boolean = false;
  @Provide isOn: boolean = false;
  @Provide isShow: boolean = false;
  @Provide isChangeSelection: boolean = false;
  @Provide inputTypeIndex: number = 0;
  @Provide enterKeyIndex: number = 0;

  build() {
    Scroll() {
      Column({ space: 12 }) {
        CustomInputText({ inputController: this.inputController })
        this.OperateView()
        this.UpdateView()
        this.AttributeView()
      }
      .width('100%')
    }
    .padding(12)
    .height('100%')
    .width('100%')
    .align(Alignment.Top)
    .backgroundColor($r('app.color.background'))
  }

  @Builder
  OperateView() {
    GridRow({
      columns: { sm: 2, md: 3, lg: 3 }, gutter: 12 }) {
      GridCol({ span: 1 }) {
        Button(this.isAttached ? $r('app.string.detach') : $r('app.string.attach'))
          .buttonStyle(ButtonStyleMode.NORMAL)
          .id('btnAttach')
          .onClick(() => {
            this.isAttached = !this.isAttached;
          })
      }

      GridCol({ span: 1 }) {
        Button(this.isShow ? $r('app.string.hide') : $r('app.string.show'))
          .buttonStyle(ButtonStyleMode.NORMAL)
          .id('btnShow')
          .onClick(() => {
            if (!this.isAttached) {
              promptAction.showToast({ message: $r('app.string.noattach_tips'), bottom: 100 });
              return;
            }
            this.isShow = !this.isShow;
          })
      }

      GridCol({ span: { sm: 2, md: 1, lg: 1 } }) {
        Button(this.isOn ? $r('app.string.off') : $r('app.string.on'))
          .buttonStyle(ButtonStyleMode.NORMAL)
          .id('btnOn')
          .onClick(() => {
            if (!this.isAttached) {
              promptAction.showToast({ message: $r('app.string.noattach_tips'), bottom: 100 });
              return;
            }
            this.isOn = !this.isOn;
          })
      }
    }
    .cardStyle()
  }

  @Builder
  UpdateView() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        TextInput({ placeholder: 'left' })
          .inputStyle()
          .id('cursorLeft')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.cursorInfo.left = Number(value).valueOf();
          })
        TextInput({ placeholder: 'top' })
          .inputStyle()
          .id('cursorTop')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.cursorInfo.top = Number(value).valueOf();
          })
        TextInput({ placeholder: 'width' })
          .inputStyle()
          .id('cursorWidth')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.cursorInfo.width = Number(value).valueOf();
          })

        TextInput({ placeholder: 'height' })
          .inputStyle()
          .id('cursorHeight')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.cursorInfo.height = Number(value).valueOf();
          })
      }
      .width('100%')

      Button($r('app.string.update_cursor'))
        .buttonStyle(ButtonStyleMode.NORMAL)
        .id('btnUpdateCursor')
        .onClick(() => {
          this.inputController.updateCursor(this.cursorInfo, (err:BusinessError) =>{
            promptAction.showToast({ message: $r('app.string.update_cursor_tips'), bottom: 100 });
          });
        })
      Row({ space: 8 }) {
        TextInput({ placeholder: 'start' })
          .inputStyle()
          .id('selectStart')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.selectStart = Number(value).valueOf();
          })
        TextInput({ placeholder: 'end' })
          .inputStyle()
          .id('selectEnd')
          .enableKeyboardOnFocus(false)
          .onChange((value: string) => {
            this.selectEnd = Number(value).valueOf();
          })
      }
      .width('100%')

      Button($r('app.string.change_selection'))
        .buttonStyle(ButtonStyleMode.NORMAL)
        .id('btnChangeSelection')
        .onClick(() => {
          this.isChangeSelection = true;
        })
    }
    .cardStyle()
  }

  @Builder
  AttributeView() {
    Column({ space: 12 }) {
      Row() {
        Row() {
          Text($r('app.string.text_input_type'))
            .textStyle()
          Select(this.inputTypeArray)
            .value(inputAttribute.getInputTypeValue(this.inputTypeIndex))
            .selectStyle()
            .id('inputTypeSelect')
            .onSelect((index: number) => {
              this.inputTypeIndex = index;
              focusControl.requestFocus('inputTypeSelect')
            })
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)

        Row() {
          Text($r('app.string.enter_key_type'))
            .textStyle()
          Select(this.enterKeyArray)
            .value(inputAttribute.getEnterTypeValue(this.enterKeyIndex))
            .selectStyle()
            .id('enterKeySelect')
            .onSelect((index: number) => {
              this.enterKeyIndex = index;
              focusControl.requestFocus('enterKeySelect')
            })
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
      }

      Button($r('app.string.update_attribute'))
        .buttonStyle(ButtonStyleMode.NORMAL)
        .id('btnUpdateAttribute')
        .onClick(() => {
          this.inputController.updateAttribute({
            textInputType: inputAttribute.getInputType(this.inputTypeIndex),
            enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex)
          })
          promptAction.showToast({ message: $r('app.string.update_attribute'), bottom: 100 });
        })
    }
    .cardStyle()
  }

  aboutToDisappear() {
    logger.info('Index', 'aboutToDisappear')
    this.inputController.stopInputSession();
  }

  aboutToAppear() {
    logger.info('Index', 'aboutToAppear')
    inputAttribute.getInputTypeSource().forEach((item: Resource) => {
      this.inputTypeArray.push({ value: item });
    })
    inputAttribute.getEnterTypeSource().forEach((item: Resource) => {
      this.enterKeyArray.push({ value: item });
    })
  }
}

源码:[CustomInputText.ets]

/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { inputMethod } from '@kit.IMEKit';
import { promptAction } from '@kit.ArkUI';
import { logger } from '../utils/Logger';
import { inputAttribute } from '../utils/InputAttributeInit';

const LINE_HEIGHT: number = 20;
const END_FLAG: number = 1000;
const TAG: string = 'CustomInputText';

@Component
export struct CustomInputText {
  @State inputText: string = '';
  @State lastInput: string = '';
  @State selectInput: string = '';
  @State cursorInfo: inputMethod.CursorInfo = { top: 0, left: 0, width: 1, height: 25 };
  @State cursorLeft: number = 0;
  @State cursorIndex: number = 0;
  @State selectIndex: number = 0;
  @State inputWidth: number = 320;
  @Consume @Watch('isAttachedChange') isAttached: boolean;
  @Consume @Watch('isOnChange') isOn: boolean;
  @Consume @Watch('isShowChange') isShow: boolean;
  @Consume @Watch('changeSelection') isChangeSelection: boolean;
  @Consume enterKeyIndex: number;
  @Consume inputTypeIndex: number;
  @Consume selectStart: number;
  @Consume selectEnd: number;
  private inputController: inputMethod.InputMethodController = inputMethod.getController();
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);

  build() {
    Stack() {
      Row() {
        Text(this.inputText)
          .fontSize(16)
          .fontFamily('sans-serif')
          .id('inputText')
          .lineHeight(LINE_HEIGHT)
          .maxLines(1)
          .constraintSize({ maxWidth: this.inputWidth })
        Text(this.selectInput)
          .fontSize(16)
          .fontFamily('sans-serif')
          .lineHeight(LINE_HEIGHT)
          .id('selectInput')
          .maxLines(1)
          .backgroundColor($r('app.color.select_color'))
        Text(this.lastInput)
          .fontSize(16)
          .fontFamily('sans-serif')
          .lineHeight(LINE_HEIGHT)
          .id('lastInput')
          .maxLines(1)
      }
      .width(this.inputWidth)

      Text('')
        .width(this.cursorInfo.width)
        .height(this.cursorInfo.height)
        .backgroundColor($r('app.color.cursor_color'))
        .margin({ top: 5 })
        .position({ x: this.cursorLeft, y: 0 })
        .onAreaChange((oldArea: Area, newArea: Area) => {
          if (newArea.globalPosition.x as number !== this.cursorInfo.left) {
            this.cursorInfo.left = newArea.globalPosition.x as number;
            this.cursorInfo.top = newArea.position.y as number;
            this.cursorInfo.width = newArea.width as number;
            this.cursorInfo.height = newArea.height as number;
            logger.info(TAG, `cursor change: this.cursorInfo=${JSON.stringify(this.cursorInfo)}`);
            this.inputController.updateCursor(this.cursorInfo);
          }
        })

      Canvas(this.context)
        .width('100%')
        .height(45)
        .onReady(() => {
          let px = vp2px(16);
          this.context.font = px + 'px sans-serif';
          this.inputWidth = this.context.width;
        })
    }
    .id('customInputText')
    .width('100%')
    .borderRadius(20)
    .backgroundColor($r('app.color.input_text_background'))
    .padding({ left: 10, right: 10, top: 5, bottom: 5 })
    .height(45)
    .onClick((event?: ClickEvent) => {
      if (event) {
        logger.info(TAG, `click event= ${JSON.stringify(event)}`);
        this.initTextInput(event);
      }
    })
  }

  async initTextInput(event: ClickEvent): Promise<void> {
    focusControl.requestFocus('customInputText');
    this.inputController.updateAttribute({
      textInputType: inputAttribute.getInputType(this.inputTypeIndex),
      enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex)
    })
    await this.inputController.attach(false, {
      inputAttribute: {
        textInputType: inputAttribute.getInputType(this.inputTypeIndex),
        enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex)
      }
    });
    this.inputController.showTextInput();
    this.isAttached = true;
    this.isShow = true;
    this.isOn = true;
    this.calculateCursor(event.x);
  }

  async isAttachedChange(): Promise<void> {
    if (this.isAttached) {
      focusControl.requestFocus('customInputText');
      await this.inputController.attach(false, {
        inputAttribute: {
          textInputType: inputAttribute.getInputType(this.inputTypeIndex),
          enterKeyType: inputAttribute.getEnterType(this.enterKeyIndex)
        }
      });
    } else {
      this.detach();
    }
  }

  isShowChange(): void {
    if (this.isShow) {
      inputMethod.getController().showTextInput();
    } else {
      inputMethod.getController().hideTextInput();
    }
  }

  isOnChange(): void {
    if (this.isOn) {
      this.initListener();
    } else {
      this.off();
    }
  }

  changeSelection(): void {
    if (this.isChangeSelection) {
      let message = this.inputText + this.selectInput + this.lastInput;
      if (this.selectStart <= this.selectEnd) {
        this.selectIndex = this.selectStart;
        this.cursorIndex = this.selectEnd;
      }
      if (this.selectStart > this.selectEnd) {
        this.selectIndex = this.selectEnd;
        this.cursorIndex = this.selectStart;
      }
      if (this.cursorIndex > message.length) {
        this.cursorIndex = message.length;
      }
      this.inputText = message.substring(0, this.selectIndex);
      this.selectInput = message.substring(this.selectIndex, this.cursorIndex);
      this.lastInput = message.substring(this.cursorIndex, message.length);
      let cursorText = this.inputText + this.selectInput;
      this.cursorLeft = this.context.measureText(cursorText).width;
      this.isChangeSelection = false;
    }
  }

  async detach(): Promise<void> {
    logger.info(TAG, `detach`);
    await this.off();
    this.isOn = false;
    this.isShow = false;
    this.inputController.detach();
  }

  async off(): Promise<void> {
    logger.info(TAG, `off`);
    this.inputController.off('insertText');
    this.inputController.off('deleteLeft');
    this.inputController.off('deleteRight');
    this.inputController.off('moveCursor');
    this.inputController.off('selectByMovement');
    this.inputController.off('selectByRange');
    this.inputController.off('sendFunctionKey')
    this.inputController.off('handleExtendAction');
    this.inputController.off('sendKeyboardStatus');
  }

  initListener(): void {
    this.inputController.on('insertText', (text: string) => {
      logger.info(TAG, `insertText, text: ${text}`);
      if ((this.cursorLeft + this.context.measureText(text).width + this.context.measureText(this.lastInput)
        .width) > this.context.width) {
        return;
      }
      this.inputText += text;
      this.cursorIndex = this.inputText.length;
      this.selectIndex = this.cursorIndex;
      this.selectInput = '';
      this.cursorLeft = this.context.measureText(this.inputText).width;
    })
    this.inputController.on('deleteRight', (length: number) => {
      let message = this.inputText + this.selectInput + this.lastInput;
      if (this.cursorIndex < message.length) {
        this.selectIndex = this.cursorIndex;
        this.selectInput = '';
        let deleteIndex = this.cursorIndex + length;
        if (deleteIndex > message.length) {
          deleteIndex = message.length;
        }
        this.lastInput = message.substring(this.cursorIndex + length, message.length);
      }
    })
    this.inputController.on('deleteLeft', (length: number) => {
      this.inputText = this.inputText.substring(0, this.inputText.length - length);
      this.cursorIndex = this.inputText.length;
      this.selectIndex = this.cursorIndex;
      this.cursorLeft = this.context.measureText(this.inputText).width;
    })
    this.inputController.on('moveCursor', (direction: inputMethod.Direction) => {
      logger.info(TAG, `Succeeded in moveCursor, direction: ${direction}`);
      let message = this.inputText + this.selectInput + this.lastInput;
      this.selectInput = '';
      if (direction === inputMethod.Direction.CURSOR_UP) {
        this.cursorIndex = 0;
      }
      if (direction === inputMethod.Direction.CURSOR_DOWN) {
        this.cursorIndex = message.length;
      }
      if (direction === inputMethod.Direction.CURSOR_LEFT) {
        this.cursorIndex--;
      }
      if (direction === inputMethod.Direction.CURSOR_RIGHT) {
        if (this.cursorIndex < message.length) {
          this.cursorIndex++;
        }
      }
      this.selectIndex = this.cursorIndex;
      this.inputText = message.substring(0, this.cursorIndex);
      this.lastInput = message.substring(this.cursorIndex, message.length);
      this.cursorLeft = this.context.measureText(this.inputText).width;
    });
    this.inputController.on('selectByMovement', (movement: inputMethod.Movement) => {
      logger.info(TAG, `Succeeded in selectByMovement, direction: ${movement.direction}`);
      let message = this.inputText + this.selectInput + this.lastInput;
      if (movement.direction === inputMethod.Direction.CURSOR_UP) {
        this.selectIndex = 0;
      }
      if (movement.direction === inputMethod.Direction.CURSOR_LEFT) {
        if (this.selectIndex > 0) {
          this.selectIndex--;
        }
      }
      if (movement.direction === inputMethod.Direction.CURSOR_RIGHT) {
        if (this.selectIndex < message.length) {
          this.selectIndex++;
        }
      }
      if (movement.direction === inputMethod.Direction.CURSOR_DOWN) {
        this.selectIndex = message.length;
      }
      if (this.selectIndex > this.cursorIndex) {
        this.inputText = message.substring(0, this.cursorIndex);
        this.selectInput = message.substring(this.cursorIndex, this.selectIndex);
        this.lastInput = message.substring(this.selectIndex, message.length);
      } else {
        this.inputText = message.substring(0, this.selectIndex);
        this.selectInput = message.substring(this.selectIndex, this.cursorIndex);
        this.lastInput = message.substring(this.cursorIndex, message.length);
      }
    });
    this.inputController.on('selectByRange', (range: inputMethod.Range) => {
      logger.info(TAG, `selectByRange this.range: ${JSON.stringify(range)}`);
      let message = this.inputText + this.selectInput + this.lastInput;
      if (range.start === 0 && range.end === 0) {
        this.cursorIndex = 0;
        let message = this.inputText + this.selectInput + this.lastInput;
        this.selectInput = '';
        this.selectIndex = this.cursorIndex;
        this.inputText = message.substring(0, this.cursorIndex);
        this.lastInput = message.substring(this.cursorIndex, message.length);
        this.cursorLeft = this.context.measureText(this.inputText).width;
      } else if (range.end > range.start) {
        if (range.end === END_FLAG) {
          this.lastInput = '';
          this.selectIndex = message.length;
          this.inputText = message.substring(0, this.cursorIndex);
          this.selectInput = message.substring(this.cursorIndex, this.selectIndex);
        } else {
          this.selectIndex = 0;
          this.inputText = ''
          this.selectInput = message.substring(0, this.cursorIndex);
          this.lastInput = message.substring(this.cursorIndex, message.length);
        }
      } else {
        this.cursorIndex = message.length;
        this.selectIndex = this.cursorIndex;
        this.inputText = message.substring(0, this.cursorIndex);
        this.lastInput = message.substring(this.cursorIndex, message.length);
        this.cursorLeft = this.context.measureText(this.inputText).width;
      }
      logger.info(TAG, `selectByRange this.selectInput: ${this.selectInput}`);
    })
    this.inputController.on('sendFunctionKey', (enterKey: inputMethod.FunctionKey) => {
      promptAction.showToast({ message: `enterKey Clicked ${enterKey.enterKeyType.toString()}`, bottom: 500 });
    })
    this.inputController.on('sendKeyboardStatus', (keyBoardStatus: inputMethod.KeyboardStatus) => {
      logger.info(TAG, `sendKeyboardStatus keyBoardStatus: ${keyBoardStatus}`);
    });
    this.inputController.on('handleExtendAction', (action: inputMethod.ExtendAction) => {
      if (action === inputMethod.ExtendAction.SELECT_ALL) {
        let message = this.inputText + this.selectInput + this.lastInput;
        this.cursorIndex = message.length;
        this.selectIndex = 0;
        this.inputText = ''
        this.selectInput = message.substring(0, this.cursorIndex);
        this.lastInput = '';
        this.cursorLeft = this.context.measureText(this.selectInput).width;
      }
    })
  }

  calculateCursor(x: number): void {
    let message = this.inputText + this.selectInput + this.lastInput;
    let charWidth = this.context.measureText(message).width / message.length;
    this.cursorIndex = Math.floor(x / charWidth);
    if (this.cursorIndex < 0) {
      this.cursorIndex = 0;
      this.inputText = '';
      this.lastInput = message;
    } else if (this.cursorIndex > message.length) {
      this.cursorIndex = message.length;
      this.inputText = message;
      this.lastInput = '';
    } else {
      this.inputText = message.substring(0, this.cursorIndex);
      this.lastInput = message.substring(this.cursorIndex, message.length);
    }
    this.selectIndex = this.cursorIndex;
    this.selectInput = '';
    this.cursorLeft = this.context.measureText(message.substring(0, this.cursorIndex)).width;
  }
}
  • 参考接口:@ohos.inputMethod

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
1

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

在这里插入图片描述

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值