基于原生能力的键盘控制
- 前言
- 一、进入页面TextInput获焦
- 1、方案
- 2、核心代码
- 二、点击按钮或其他事件触发TextInput获焦
- 1、方案
- 2、核心代码
- 三、键盘弹出后只上抬特定的输入组件
- 1、方案
- 2、核心代码
- 四、监听键盘高度
- 1、方案
- 2、核心代码
- 五、设置窗口在键盘抬起时的页面避让模式为上抬,压缩
- 1、方案
- 2、核心代码
- demo代码
- Index.ets
- Login.ets
- Register.ets
前言
应用通常使用键盘的方式,系统键盘的弹出收起,获焦失焦,高度监听,安全避让等。
应用经常会遇到如下的业务诉求:
场景一:进入页面TextInput获焦,弹出系统键盘。
场景二:点击按钮或其他事件触发TextInput获焦,弹出系统键盘。
场景三:键盘弹出后只上抬特定的输入组件。
场景四:监听键盘高度,在键盘弹出后让组件上移,键盘收起后让组件恢复。
场景五:设置窗口在键盘抬起时的页面避让模式为上抬,压缩。
一、进入页面TextInput获焦
1、方案
通过defaultFocus通用属性设置,实现第一次进入页面后弹出键盘
2、核心代码
TextInput({ text: $$this.username, placeholder: '请输入用户名' }).placeholderColor('#D4D3D1').backgroundColor(this.isUserNameFocus ? '#80FFFFFF' : '#ffffff').width(260).borderRadius(8).id('username').margin({ top: 10, bottom: 10 }).onFocus(() => {this.isUserNameFocus = true}).onBlur(() => {this.isUserNameFocus = false}).defaultFocus(true) // 进入页面默认获焦
二、点击按钮或其他事件触发TextInput获焦
1、方案
通过focusControl.requestFocus,实现输入账号后,点击登录按钮后,代码主动设置TextInput获取焦点
2、核心代码
Button('登 录').width(200).height(45).fontSize(28).type(ButtonType.Normal).backgroundColor('#30FFFFFF').border({ width: 1, color: Color.White, radius: 8 }).margin({ top: 50, bottom: 60 }).onClick(() => {let LoginForm: LoginForm = {username: this.username,password: this.password}let requestId = ''if (!LoginForm.username) {requestId = 'username'} else if (!LoginForm.password) {requestId = 'password'} else {promptAction.showToast({ message: 'Login success' })return}let res = focusControl.requestFocus(requestId) // 使选中的this.selectId的组件获焦,这里要注意获焦的id要与组件的id保持一致promptAction.showToast({ message: requestId + '不能为空' })})
三、键盘弹出后只上抬特定的输入组件
1、方案
通过expandSafeArea通用属性设置,实现只上抬红框圈住的特定组件
2、核心代码
Column(){// 在页面跟容器设置安全区域这样页面就不会上抬了只有对应的输入框会进行上抬
}
.expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
四、监听键盘高度
1、方案
先通过expandSafeArea禁止自动键盘弹出上移页面。然后通过window.on(‘avoidAreaChange’)和windowClass.on(‘keyboardHeightChange’),实现监听键盘高度,上抬整个页面view,让输入框(生命周期价值输入框)组件能显示在键盘上方
2、核心代码
@State screenHeight: Length = 0onPageShow(): void {// ...window.getLastWindow(getContext(this)).then(currentWindow => {let property = currentWindow.getWindowProperties();let avoidArea = currentWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);// 初始化显示区域高度this.screenHeight = px2vp(property.windowRect.height - avoidArea.topRect.height - avoidArea.bottomRect.height);// 监视软键盘的弹出和收起currentWindow.on('keyboardHeightChange', async data => {// 这里通过监听键盘高度变化来改变跟容器高度变化this.screenHeight = px2vp(property.windowRect.height - avoidArea.topRect.height - data);})})// ...
}Column() {...
}
.width('100%')
.height(this.screenHeight)
.expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
五、设置窗口在键盘抬起时的页面避让模式为上抬,压缩
1、方案
点击生命周期输入框,通过windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode,实现设置窗口在键盘抬起时的页面避让模式,这种情况下当键盘弹起的时候页面会自动压缩
2、核心代码
onPageShow(): void {// ...this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)// 在RESIZE模式下当键盘弹起的时候页面会进行压缩// ...
}onPageHide(): void {this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET)// 在OFFSET模式下当键盘弹起的时候页面会进行上抬
}
demo代码
Index.ets
import { router } from '@kit.ArkUI';@Entry
@Component
struct Index {build() {Row() {Column() {Button('进入页面获焦').margin({ top: 10, bottom: 10 }).onClick(() => {router.pushUrl({ url: 'pages/Login', params: { pageId: '1' } })})Button('事件触发主动获焦').margin({ top: 10, bottom: 10 }).onClick(() => {router.pushUrl({ url: 'pages/Login', params: { pageId: '2' } })})Button('键盘弹出不上移组件').margin({ top: 10, bottom: 10 }).onClick(() => {router.pushUrl({ url: 'pages/Register', params: { pageId: '3' } })})Button('监听键盘高度变化').margin({ top: 10, bottom: 10 }).onClick(() => {router.pushUrl({ url: 'pages/Register', params: { pageId: '4' } })})Button('设置页面避让模式').margin({ top: 10, bottom: 10 }).onClick(() => {router.pushUrl({ url: 'pages/Register', params: { pageId: '5' } })})}.width('100%')}.height('100%')}
}
Login.ets
import { promptAction, router } from '@kit.ArkUI'interface LoginForm {username: stringpassword: string
}export interface routerParam {pageId: string
}@Entry
@Component
struct Login {@State username: string = ''@Watch('passwordChange') @State password: string = ''@State isUserNameFocus: boolean = false@State isPasswordFocus: boolean = false@State rememberPassword: boolean = false@State showPasswordIcon: boolean = falseprivate controller: TextInputController = new TextInputController()@State routerParams: routerParam = { pageId: '0' }passwordChange() {this.showPasswordIcon = !!this.password}onPageShow(): void {this.routerParams = router.getParams() as routerParamconsole.log(this.routerParams.pageId)}build() {Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {Row() {Image($r('app.media.avatar')).width(60).height(60)}.borderRadius(50).width(100).height(100).justifyContent(FlexAlign.Center).backgroundColor('#FFFFFF').margin({ bottom: 30 })TextInput({ text: $$this.username, placeholder: '请输入用户名', controller: this.controller }).placeholderColor('#D4D3D1').backgroundColor(this.isUserNameFocus ? '#80FFFFFF' : '#ffffff').width(260).borderRadius(8).id('username').margin({ top: 10, bottom: 10 }).onFocus(() => {this.isUserNameFocus = true}).onBlur(() => {this.isUserNameFocus = false}).defaultFocus(this.routerParams.pageId === '1') // 进入页面默认获焦TextInput({ text: $$this.password, placeholder: '请输入密码', controller: this.controller }).placeholderColor('#D4D3D1').type(InputType.Password).showPasswordIcon(this.showPasswordIcon).backgroundColor(this.isPasswordFocus ? '#80FFFFFF' : '#ffffff').width(260).borderRadius(8).id('password').margin({ top: 10, bottom: 10 }).onFocus(() => {this.isPasswordFocus = true}).onBlur(() => {this.isPasswordFocus = false})Row() {Checkbox().select($$this.rememberPassword).unselectedColor('#ffffff').width(16).borderRadius(8).backgroundColor(Color.White)Text('记住密码').fontColor('#ffffff').onClick(() => {this.rememberPassword = !this.rememberPassword})}.width(260)Button('登 录').width(200).height(45).fontSize(28).type(ButtonType.Normal).backgroundColor('#30FFFFFF').border({ width: 1, color: Color.White, radius: 8 }).margin({ top: 50, bottom: 60 }).onClick(() => {let LoginForm: LoginForm = {username: this.username,password: this.password}let requestId = ''// todo: 无法使用for..in遍历对象if (!LoginForm.username) {requestId = 'username'} else if (!LoginForm.password) {requestId = 'password'} else {promptAction.showToast({ message: 'Login success' })return}focusControl.requestFocus(requestId) // 使选中的this.selectId的组件获焦promptAction.showToast({ message: requestId + '不能为空' })})Row() {Text('忘记密码').fontColor(Color.White)Text('注册').fontColor(Color.White).onClick(() => {router.pushUrl({ url: 'pages/Register', params: { pageId: '3' } })})}.justifyContent(FlexAlign.SpaceBetween).width(260)}.width('100%').height('100%').expandSafeArea([SafeAreaType.SYSTEM, SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]).backgroundImage($r('app.media.bg')).backgroundImageSize(ImageSize.Cover).onTouch(() => {this.controller.stopEditing()})}
}
Register.ets
import { KeyboardAvoidMode, router, window } from '@kit.ArkUI';
import { routerParam } from './Login'@Entry
@Component
struct Register {@State routerParams: routerParam = { pageId: '0' }@State screenHeight: Length = '100%'private windowStage = AppStorage.get('windowStage') as window.WindowStageonPageShow(): void {this.routerParams = router.getParams() as routerParamif (this.routerParams.pageId == '5') {this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)}if (this.routerParams.pageId == '4') {window.getLastWindow(getContext(this)).then(currentWindow => {let property = currentWindow.getWindowProperties();let avoidArea = currentWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM);// 初始化显示区域高度this.screenHeight = px2vp(property.windowRect.height - avoidArea.topRect.height - avoidArea.bottomRect.height);// 监视软键盘的弹出和收起currentWindow.on('avoidAreaChange', async data => {if (data.type !== window.AvoidAreaType.TYPE_KEYBOARD) {return;}this.screenHeight =px2vp(property.windowRect.height - avoidArea.topRect.height - data.area.bottomRect.height);})})}}onPageHide(): void {this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET)}build() {Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {Row() {Image($r('app.media.back')).width(24).position({ x: 16, y: 10 }).onClick(() => {router.back()})Text('用户注册').fontSize(24).fontWeight(FontWeight.Bold)}.height(45).borderWidth({ bottom: 1 }).borderColor('#EAEEF5').width('100%').justifyContent(FlexAlign.Center)Column() {Image($r('app.media.logo')).width(240)}// 表单主体List({ space: 16 }) {ListItem() {FormItem({ label: '用户名', placeholder: '请输入用户名' })}ListItem() {FormItem({ label: '密码', placeholder: '请输入密码' })}ListItem() {FormItem({ label: '手机号', placeholder: '请输入手机号' })}ListItem() {FormItem({ label: '邮箱', placeholder: '请输入邮箱地址' })}ListItem() {Column() {Text('个人简介').margin({ bottom: 16 })TextArea({ placeholder: '介绍点啥吧...' }).borderRadius(0).borderWidth({ bottom: 1 }).borderColor('#EAEEF5').height(100)}.alignItems(HorizontalAlign.Start)}}.padding(10).layoutWeight(1).edgeEffect(EdgeEffect.None).scrollBar(BarState.Off).expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])Row() {Button('提交').type(ButtonType.Normal).fontColor(Color.White).backgroundColor('#59BEB7').width('100%')}.padding(16).shadow({ radius: 20, color: '#30000000' }).width('100%').expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])}.width('100%').height(this.screenHeight).expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])}
}@Component
struct FormItem {@Prop label: string@Prop placeholder: string@State value: string = ''build() {Column() {Text(this.label).margin({ bottom: 16 })TextInput({ text: this.value, placeholder: this.placeholder }).backgroundColor(Color.Transparent).borderRadius(0).borderWidth({ bottom: 1 }).borderColor('#EAEEF5')}.alignItems(HorizontalAlign.Start).expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])}
}