自绘编辑框
介绍
本示例通过输入法框架实现自会编辑框,可以绑定输入法应用,从输入法应用输入内容,显示和隐藏输入法。
效果预览
使用说明
1.点击编辑框可以绑定并拉起输入法,可以从输入法键盘输入内容到编辑框。
2.可以点击attach/dettach、show/hide、on/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
以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!
下面是鸿蒙的完整学习路线,展示如下:
除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下:
内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!
鸿蒙【北向应用开发+南向系统层开发】文档
鸿蒙【基础+实战项目】视频
鸿蒙面经
为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!