当前位置: 首页 > news >正文

一套代码适配四种屏幕——StyleConfiguration 键盘多设备适配方案

文章目录

      • 问题在哪?
      • StyleConfiguration 的设计思路
      • KeyStyle 接口定义
      • StyleConfiguration.getInputStyle 完整逻辑
      • 资源文件命名规范
      • 组件如何使用 StyleConfiguration
      • 屏幕旋转适配完整流程
      • 这种设计模式的通用价值
      • 踩坑记录
      • 写在最后

搞输入法开发最头疼的事情之一就是屏幕适配:手机竖屏、手机横屏、平板竖屏、平板横屏,键盘的尺寸、按键大小、字体都不一样。KikaInputMethod 里的StyleConfiguration用了一套挺优雅的方案,值得借鉴。

问题在哪?

不同设备键盘的差异点:

场景特点挑战
手机竖屏键盘宽,高度有限按键小,字体要适配
手机横屏键盘更宽,高度更有限按键要更扁
平板竖屏分辨率高,空间充足按键可以更大
平板横屏宽度极大按键宽度要重新分配
RK 开发板分辨率特殊(720×1280)完全独立的样式集

如果每个场景都用 if-else 直接写死数值,代码会变成噩梦。

StyleConfiguration 的设计思路

核心思路:把"样式选择"和"样式使用"分离

KeyStyle 接口定义

所有设备共享同一套接口,只是资源值不同:

exportinterfaceKeyStyle{deviceType?:string;// === 按键尺寸 ===basicButtonWidth:Resource;// 普通字母键宽度basicButtonHeight:Resource;// 按键高度switchButtonWidth:Resource;// 切换键(数字/符号)宽度returnButtonWidthType1:Resource;// Enter 键宽度(类型1)returnButtonWidthType2:Resource;// Enter 键宽度(类型2)spaceButtonWidth1:Resource;// 空格键宽度1spaceButtonWidth2:Resource;// 空格键宽度2// === 图标尺寸 ===featurePicSize:Resource;// 功能图标大小returnPicSize:Resource;// 回车图标大小editPicSize:Resource;// 编辑图标大小editImageSize:Resource;// 编辑图片大小// === 间距 ===paddingTop:Resource;// 键盘顶部内边距paddingLeftRight:Resource;// 左右内边距// === 下拉菜单 ===downMenuHeight:Resource;downMenuPicWidth:Resource;downMenuPicHeight:Resource;downMenuWidth:Resource;subMenuWidth:Resource;// === 字体 ===litterNumberFontSize:Resource;// 小号数字字体en_fontSize:Resource;// 英文字体大小symbol_fontSize:Resource;// 符号字体大小switchNumberFontSize:Resource;// 切换按钮字体// === 其他 ===keyboardHeight:Resource;// 键盘总高度editCircleSize:Resource;// 编辑圆圈大小editSmallCircle:Resource;editSmallCircleMargin:Resource;editButtonSize:Resource;editFontSize:Resource;editDriverLeft:Resource;}

StyleConfiguration.getInputStyle 完整逻辑

import{deviceInfo}from'@kit.BasicServicesKit';exportclassStyleConfiguration{// 组件初始化时调用,从 AppStorage 读取已计算好的样式// 如果 AppStorage 里还没有(第一次),就重新计算staticgetSavedInputStyle():KeyStyle{letstyle=AppStorage.get<KeyStyle>('inputStyle');if(style){returnstyle;}// AppStorage 里没有,用默认值计算letisLandscape=AppStorage.get<boolean>('isLandscape')??false;letisRkDevice=AppStorage.get<boolean>('isRkDevice')??false;returnStyleConfiguration.getInputStyle(isLandscape,isRkDevice,deviceInfo.deviceType);}// 根据设备信息选择对应样式集staticgetInputStyle(isLandscape:boolean,isRkDevice:boolean,deviceType:string):KeyStyle{// 优先级1:RK 开发板独立适配if(isRkDevice){return{basicButtonWidth:$r('app.float.rk_basic_button_width'),basicButtonHeight:$r('app.float.rk_basic_button_height'),en_fontSize:$r('app.float.rk_en_fontSize'),// ... 其他 rk_* 资源};}// 优先级2:平板设备if(deviceType==='tablet'){if(isLandscape){return{deviceType:'tablet_landSpace',basicButtonWidth:$r('app.float.landSpace_basic_button_width'),basicButtonHeight:$r('app.float.landSpace_basic_button_height'),en_fontSize:$r('app.float.landSpace_en_fontSize'),symbol_fontSize:$r('app.float.landSpace_symbol_fontSize'),// ... 其他 landSpace_* 资源};}else{return{deviceType:'tablet_portrait',basicButtonWidth:$r('app.float.portrait_basic_button_width'),basicButtonHeight:$r('app.float.portrait_basic_button_height'),en_fontSize:$r('app.float.portrait_en_fontSize'),// ... 其他 portrait_* 资源};}}// 优先级3:手机if(!isLandscape){// 手机竖屏return{basicButtonWidth:$r('app.float.s_basic_button_width'),basicButtonHeight:$r('app.float.s_basic_button_height'),en_fontSize:$r('app.float.en_fontSize'),keyboardHeight:$r('app.float.keyboard_height'),// ... 其他 s_* 资源};}else{// 手机横屏return{basicButtonWidth:$r('app.float.h_basic_button_width'),basicButtonHeight:$r('app.float.h_basic_button_height'),en_fontSize:$r('app.float.en_fontSize'),// ... 其他 h_* 资源};}}}

资源文件命名规范

每种设备的样式值存在resources/base/element/float.json里,用前缀区分:

rk_* → RK 开发板 s_* → 手机(small/standard)竖屏 h_* → 手机横屏(horizontal) landSpace_* → 平板横屏 portrait_* → 平板竖屏

比如按键高度:

{"float":[{"name":"s_basic_button_height","value":"40.0"},{"name":"h_basic_button_height","value":"32.0"},{"name":"portrait_basic_button_height","value":"48.0"},{"name":"landSpace_basic_button_height","value":"36.0"},{"name":"rk_basic_button_height","value":"36.0"}]}

组件如何使用 StyleConfiguration

所有键盘组件通过@StorageLink响应式读取样式:

// SpaceItem.ets@Componentexportstruct SpaceItem{@StorageLink('inputStyle')inputStyle:KeyStyle=StyleConfiguration.getSavedInputStyle();build(){Stack(){Text('space').fontSize(this.inputStyle.symbol_fontSize)// 根据设备自动适配字体}.width(this.spaceWith).height('100%').onClick(()=>{InputHandler.getInstance().insertText(' ');})}}// KeyItem.ets@Componentexportstruct KeyItem{@StorageLink('inputStyle')inputStyle:KeyStyle=StyleConfiguration.getSavedInputStyle();build(){Stack(){Text(this.keyValue).fontSize(this.inputStyle.en_fontSize)// 字体自适应}.width(this.inputStyle.basicButtonWidth)// 按键宽度自适应.height(this.inputStyle.basicButtonHeight)// 按键高度自适应}}

当屏幕旋转时,KeyboardController.resizePanel()重新计算inputStyle并更新 AppStorage,所有组件自动重新渲染。

屏幕旋转适配完整流程

@startuml title 屏幕旋转适配流程 :用户旋转设备; :display.on('change') 触发; :KeyboardController.resizePanel(); :display.getDefaultDisplaySync()\n获取新的宽高; :重新判断 isLandscape; :StyleConfiguration.getInputStyle(\n newIsLandscape, isRkDevice, deviceType\n); :AppStorage.setOrCreate('inputStyle', newStyle); note right: @StorageLink 自动通知\n所有键盘 UI 组件重新渲染 :panel.resize(newWidth, newHeight); note right: 同步更新面板尺寸\n保证键盘显示正确 @enduml

这种设计模式的通用价值

StyleConfiguration的设计不只适合输入法,任何需要多设备适配的应用都可以用类似的模式:

// 通用适配模式classThemeConfig{// 1. 定义统一接口interfaceAppTheme{fontSize:Resource;padding:Resource;buttonHeight:Resource;// ...}// 2. 根据设备类型选择具体值staticgetTheme(deviceType:string,isLandscape:boolean):AppTheme{if(deviceType==='tablet'){returnisLandscape?tabletLandscapeTheme:tabletPortraitTheme;}returnisLandscape?phoneLandscapeTheme:phonePortraitTheme;}}// 3. 应用启动时存入 AppStorageAppStorage.setOrCreate('appTheme',ThemeConfig.getTheme(deviceType,isLandscape));// 4. 组件通过 @StorageLink 使用@StorageLink('appTheme')theme:AppTheme=ThemeConfig.getTheme('phone',false);

踩坑记录

坑1:getSavedInputStyle 的兜底逻辑要完整

getSavedInputStyle会在组件初始化时调用,这时KeyboardController.initWindow可能还没执行完(Panel 还没创建好),AppStorage里可能没有inputStyle。所以getSavedInputStyle必须有完整的兜底计算逻辑,不能直接返回undefined

坑2:resizePanel 要同时更新样式和面板尺寸

旋转时只调panel.resize但不更新inputStyle,键盘 UI 会显示旧样式;只更新inputStyle但不调panel.resize,面板大小不对。两个操作都要做。

坑3:$r() 资源引用在 StyleConfiguration 里不是问题

StyleConfiguration是一个普通类,不是 ArkUI 组件,但$r()在非组件环境里仍然有效,它返回的是Resource类型,组件里用的时候才实际解析。

写在最后

StyleConfiguration + AppStorage + @StorageLink这套组合拳用在输入法适配上效果很好:运行时根据设备信息选一次样式存入 AppStorage,所有组件响应式读取,屏幕旋转时重新计算更新,整个过程不需要手动通知任何组件。这个设计模式值得在其他多设备项目里复用。

http://www.jsqmd.com/news/853243/

相关文章:

  • CANN ops-fft安全最佳实践:确保AI计算平台FFT算子的安全运行
  • 别再只用DS18B20了!用51单片机+ADC0804做个PT100温度计(附完整代码和Proteus仿真)
  • 虚拟显示器驱动ParsecVDD:解决游戏串流与远程办公的显示难题
  • Windows缩略图加载太慢?这款智能预加载工具让文件浏览快如闪电
  • CANN/catlass精度分析基础
  • CANN/catlass A2至950迁移指导
  • C++二叉树构建、深拷贝与可视化输出实战解析
  • 电力系统时序一致性保障:elec-ops-prediction的长时序稳定性约束实现
  • TTK开发者指南:如何贡献代码和扩展功能的10个实用技巧
  • DS18B20时序不稳?一个中值滤波函数帮你搞定所有异常数据(附C代码)
  • 解析2026年新能源PPS材料供应商关键技术与发展路径
  • 昇腾C解交织API文档
  • G-Helper完整指南:3分钟掌握华硕笔记本性能优化神器
  • CANN/catlass LayoutTag(旧版Layout)
  • 靠谱的远程手机控制软件 远程控制手机推荐用无界趣连2.0
  • CANN/.gitcode缺陷报告模板深度解析:如何高效提交昇腾AI问题反馈
  • RV1126B嵌入式OCR实战:CTPN+CRNN模型部署与优化全解析
  • YOLO-ONNX-Java 模型评估指标完全指南:从理论到实践
  • 3大AI创作效率瓶颈的模块化解法:ComfyUI企业级工作流自动化实践
  • Onyx Core API完全手册:RESTful接口详解与实战案例
  • Serverless-Devs插件开发教程:如何扩展工具的核心功能
  • ncmdump终极指南:5分钟解锁网易云音乐NCM加密文件
  • 程序员学习指南【非常详细】|零基础入门到精通
  • 用C语言刷PTA数据结构题:我是如何把链表合并和哈希表删除函数写到面试官满意的
  • 深圳市火灵鸟技术有限公司深度解析:从国产芯到全景可视化,一家执法装备企业的成长路径 - 品牌优选官
  • Wolverine性能优化终极秘籍:从基础配置到高级调优
  • Windows风扇控制实战:3种场景下的智能散热解决方案
  • Linux/Win双平台实战:MinIO安装后第一件事,如何正确设置并牢记你的ROOT密码?
  • 手把手教你为展锐平台新摄像头(如OV08A10)添加驱动:Sensor配置与AF驱动集成详解
  • Intel 14代酷睿接口更迭:技术推演与用户决策指南