基于原生能力的键盘控制
- 前言
- 一、进入页面TextInput获焦
- 二、点击按钮或其他事件触发TextInput获焦
- 三、键盘弹出后只上抬特定的输入组件
- 四、监听键盘高度
- 五、设置窗口在键盘抬起时的页面避让模式为上抬,压缩
- demo代码
前言
应用通常使用键盘的方式,系统键盘的弹出收起,获焦失焦,高度监听,安全避让等。
应用经常会遇到如下的业务诉求:
场景一:进入页面TextInput获焦,弹出系统键盘。
场景二:点击按钮或其他事件触发TextInput获焦,弹出系统键盘。
场景三:键盘弹出后只上抬特定的输入组件。
场景四:监听键盘高度,在键盘弹出后让组件上移,键盘收起后让组件恢复。
场景五:设置窗口在键盘抬起时的页面避让模式为上抬,压缩。
一、进入页面TextInput获焦
1、方案
通过defaultFocus通用属性设置,实现第一次进入页面后弹出键盘
2、核心代码
bash
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、核心代码
bash
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、核心代码
bash
Column(){
// 在页面跟容器设置安全区域这样页面就不会上抬了只有对应的输入框会进行上抬
}
.expandSafeArea([SafeAreaType.KEYBOARD], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
四、监听键盘高度
1、方案
先通过expandSafeArea禁止自动键盘弹出上移页面。然后通过window.on('avoidAreaChange')和windowClass.on('keyboardHeightChange'),实现监听键盘高度,上抬整个页面view,让输入框(生命周期价值输入框)组件能显示在键盘上方
2、核心代码
bash
@State screenHeight: Length = 0
onPageShow(): 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、核心代码
bash
onPageShow(): void {
// ...
this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
// 在RESIZE模式下当键盘弹起的时候页面会进行压缩
// ...
}
onPageHide(): void {
this.windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.OFFSET)
// 在OFFSET模式下当键盘弹起的时候页面会进行上抬
}
demo代码
Index.ets
bash
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
bash
import { promptAction, router } from '@kit.ArkUI'
interface LoginForm {
username: string
password: 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 = false
private controller: TextInputController = new TextInputController()
@State routerParams: routerParam = { pageId: '0' }
passwordChange() {
this.showPasswordIcon = !!this.password
}
onPageShow(): void {
this.routerParams = router.getParams() as routerParam
console.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
bash
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.WindowStage
onPageShow(): void {
this.routerParams = router.getParams() as routerParam
if (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])
}
}