基于原生能力的鍵盤控制
- 前言
- 一、進入頁面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])}
}