鸿蒙开发:如何解决软键盘弹出后的间距
前言
本文基于Api13
近日在查看github中的issue时,发现了一个问题,说的是当自定义弹窗中有TextInput组件时,触摸焦点弹起软键盘后,组件和软键盘之间有一个间距,看到问题后,“我就在想,自定义弹窗,所有的UI都是传入的,是不是在绘制的时候,这个间距是开发者加的呢?”,本着对问题负责的态度,于是就验证问题出现的原因,最后却发现这是鸿蒙系统的问题。
为了验证问题,我传入的自定义UI,没有带有一丝间距,弹起后,发现了开发者所提的问题非虚,和软键盘之间,还真有一个小小的间距。
看着这个间距,发呆了3秒,彻底怀疑了人生,我明明没设置间距啊,但是这个间距从哪来的呢?是不是软键盘弹起后,本身就会有一个间距?
为了验证问题的真实性,我写了一个无比简单的代码,就一个Column组件包裹着一个TextInput组件,让TextInput组件在最底部,代码如下:
Column() {TextInput()}.width("100%").height("100%").justifyContent(FlexAlign.End)}
运行,点击输入框:
运行之后,看着组件和软键盘之间的间距,陷入了沉思,“给我提问题那个朋友,这个问题,我不背锅”。
是不是TextInput组件有这个问题,换一个其他的输入组件就没这个问题了?于是,我又验证了TextArea,RichEditor,发现问题依然存在,显然和组件是没有关系,和软键盘有关系。
接着便查看了Dom结构,发现这是root根节点自带的一个间距。
打开Dom树结构,可以发现,父节点Column是没有间距的,但是距离软键盘那确实有一个间距。
点击root节点后,就会发现,这个间距是root所带的。
这就证明了一个问题,那就是,输入框和软键盘之间的间距是系统自带的,对比前后的高度,可以得出,这个间距是44px。
其实站在我的角度上去看,我觉得这个间距挺好,起码在UI视觉上看着美观。当然了不能我以为就以为,还是要考虑到实际的需求开发,毕竟有的场景下,就不需要间距。
有问题,就会有解决问题的办法,经过一系列的研究,其实解决起来也十分的简单,总结了有三种方式,大家可以选择适合的方式。
方式一:设置页面避让模式
当我们不设置虚拟键盘的避让模式时,默认是OFFSET模式,也就是上抬模式,就会出现间距的问题,我们可以改为压缩模式RESIZE。
this.getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
设置完避让模式后,我们再次运行看下,组件和软键盘之间的间距已经取消了。
方法二:设置沉浸式布局
设置沉浸式布局时,布局不会避让状态栏与导航栏,组件可能产生与其重叠的情况,这种情况下需要自己设置距离顶部和底部的距离。
window.getLastWindow(getContext(), (_, win) => {win.setWindowLayoutFullScreen(true)})
设置完之后,效果和方式一是一样的。
虽然说解决了间距问题,但是,沉浸式之后,由于不会避让状态栏与导航栏,会出现底部的组件被遮挡的情况,也就是如下图所示:
这种情况下,如果你想实现软键盘弹出后无间距,软键盘收起后,组件在底部导航栏上面,那么就需要代码上的动态设置,两种方式,一种是监听输入框的输入状态变化,另一种是监听软键盘弹出的状态,根据不同的状态,然后给组件设置距离底部的距离即可。
监听输入框的输入状态
全部代码如下,在aboutToAppear方法里,设置沉浸式,获取底部的导航栏高度,在onEditChange方法中,监听输入状态的变化,动态赋值给底部距离属性。
import { window } from '@kit.ArkUI'@Entry
@Component
struct Index {@State marginBottom: number = 0private bottomRectHeight: number = 0 //底部导航栏高度aboutToAppear(): void {window.getLastWindow(getContext(), (_, win) => {win.setWindowLayoutFullScreen(true)let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATORlet avoidArea = win.getWindowAvoidArea(type)// 获取到导航条区域的高度this.bottomRectHeight = px2vp(avoidArea.bottomRect.height)this.marginBottom = this.bottomRectHeight //初始化默认距离底部距离})}build() {Column() {TextInput().margin({ bottom: this.marginBottom }).onEditChange((isEditing: boolean) => {this.marginBottom = isEditing ? 0 : this.bottomRectHeight})}.width("100%").height("100%").justifyContent(FlexAlign.End)}
}
运行之后,可以看到,默认在导航栏上面,软键盘弹起后,也没有任何间距。
监听软键盘弹出状态
无非就是把输入框的输入状态切换为了软键盘的弹出状态。
// 监听软键盘的隐藏和显示win.on('keyboardHeightChange', (height) => {this.marginBottom = height > 0 ? 0 : this.bottomRectHeight})
按照正常逻辑而言,应该和上面的效果是一样的,但偏偏剑走了弯路,当软键盘弹出后输入框明显被遮挡。
我真实服了这个老六,无非就是换了一个监听方式,怎么还给了一个惊喜呢,针对这种情况,建议直接使用第一种方式,当然了,如果你仍然要用这种方式,也不是不行,软键盘弹出后,需要加上被遮挡的高度,也就是44px。
// 监听软键盘的隐藏和显示win.on('keyboardHeightChange', (height) => {this.marginBottom = height > 0 ? px2vp(44) : this.bottomRectHeight})
方式三、动态设置位置
所谓的动态设置,就是根据软键盘的高度,动态设置组件的位置,也就是需要获取软键盘的高度,当软键盘弹起时,让组件距离底部的距离正好是软键盘的高度,这种方式和沉浸式布局方式有点类似,但是不需要设置沉浸式。
有一点需要注意,那就是需要配置扩展安全区域的类型为SafeAreaType.KEYBOARD,目的防止组件随着软键盘抬起而置顶。
import { window } from '@kit.ArkUI'@Entry
@Component
struct Index {@State marginBottom: number = 0aboutToAppear(): void {window.getLastWindow(getContext(), (_, win) => {let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATORlet avoidArea = win.getWindowAvoidArea(type)// 获取到导航条区域的高度let bottomRectHeight = px2vp(avoidArea.bottomRect.height)// 监听软键盘的隐藏和显示win.on('keyboardHeightChange', (height) => {this.marginBottom = height > 0 ? px2vp(height) - bottomRectHeight : 0})})}build() {Column() {TextInput().margin({ bottom: this.marginBottom })}.width("100%").height("100%").justifyContent(FlexAlign.End).expandSafeArea([SafeAreaType.KEYBOARD])}
}
以上的代码运行之后,可以发现和上面的效果是一样的,当然了位置,并不一定通过margin,你也可以通过offset属性,或者padding属性等等。
offset({y:-this.marginBottom})
相关总结
还是那句话,自我感觉,鸿蒙系统对于这个间距的处理,我觉得是正常的,毕竟更加符合视觉美观,如果紧挨着展示,反而觉得不太美观;但话又回来,需求是多变的,人无完人,金无足赤,每个产品都有自己的思想,而作为研发的我们,如果你没有反向修改的勇气,那么只管找解决办法即可。
三种方式,比较推荐方式一,简单便捷,一行代码便可以搞定,当然,另外两种也是实现的办法,在实际的开发中,选择适合的即可。