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

HarmonyOS PC实战之PC 端聊天工具栏的 Flex 布局——固定按钮与弹性输入框的组合

文章目录

    • 前言
      • 工具栏的四段结构
      • 完整代码
      • alignItems Bottom 的作用
      • 发送按钮动态切换
      • 小结

前言

聊天工具栏是对布局要求最精细的 UI 之一:左边附件按钮固定宽,右边发送按钮也固定宽,中间输入框弹性填满,而且还得随文字增多自动增高,但不能无限增高。

PC 端的聊天场景还有额外的需求:Enter 发送、Shift+Enter 换行、工具栏固定在底部不随内容滚动。这些用 Flex 弹性布局加上 ArkUI 的键盘事件处理,实现起来比想象中简单。

工具栏的四段结构

四段:左侧工具按钮组(固定)+ 弹性输入框 + 发送按钮(固定)。

Row({space:8}){![Hand-drawn educational flowchart on warm cream pap](https://files.mdnice.com/user/47561/c665084f-9f59-41fd-a746-16037f186104.jpg)// 左侧工具区(固定)Row({space:4}){Text('😊').fontSize(20).onClick(...)Text('📎').fontSize(20).onClick(...)}// 中间输入框(弹性)TextArea({placeholder:'输入消息...'}).layoutWeight(1).maxLines(4)// 右侧发送按钮(固定)Button('发送').width(64).height(36)}.width('100%').alignItems(VerticalAlign.Bottom)// ← 底部对齐,输入框高了不影响按钮位置

alignItems: VerticalAlign.Bottom让所有元素底部对齐——输入框增高时,左右按钮保持在底部,不会跟着移到顶部。

完整代码

interfaceMessage{id:numbercontent:stringisSelf:booleantime:stringtype:'text'|'file'|'image'}interfaceToolItem{icon:string,label:string}@Entry@Componentstruct PcChatToolbarPage{@Statemessages:Message[]=[{id:1,content:'你好,请问HarmonyOS PC端的窗口拖拽怎么实现?',isSelf:false,time:'14:20',type:'text'},{id:2,content:'你可以用onWindowSizeChange监听窗口大小变化,然后用constraintSize设置最小尺寸。',isSelf:true,time:'14:21',type:'text'},{id:3,content:'那键盘事件怎么处理?比如Enter发送,Shift+Enter换行',isSelf:false,time:'14:22',type:'text'},{id:4,content:'在TextArea的onKeyEvent里判断:event.keyCode === 2054(Enter键),同时检查event.metaKey/shiftKey是否按下。',isSelf:true,time:'14:22',type:'text'},{id:5,content:'谢谢,非常清楚!',isSelf:false,time:'14:23',type:'text'},]@StateinputText:string=''@StateshowEmojiPanel:boolean=false@StateshowToolPanel:boolean=falseprivatescrollerRef:Scroller=newScroller()sendMessage(){if(!this.inputText.trim())returnconstnewMsg:Message={id:Date.now(),content:this.inputText,isSelf:true,time:`${newDate().getHours()}:${String(newDate().getMinutes()).padStart(2,'0')}`,type:'text'}this.messages=[...this.messages,newMsg]this.inputText=''this.showEmojiPanel=falsethis.showToolPanel=false}@BuildermessageBubble(msg:Message){Row({space:10}){// 根据 isSelf 切换布局方向(RowReverse = 自己的消息)if(!msg.isSelf){// 对方:头像在左Text('🤖').fontSize(20).width(36).height(36).borderRadius(18).backgroundColor('#E5E7EB').textAlign(TextAlign.Center).alignSelf(ItemAlign.Start)}Column({space:2}){Text(msg.content).fontSize(14).fontColor(msg.isSelf?Color.White:'#1F2937').padding({left:12,right:12,top:8,bottom:8}).backgroundColor(msg.isSelf?'#3B82F6':Color.White).borderRadius(msg.isSelf?{topLeft:12,topRight:4,bottomLeft:12,bottomRight:12}:{topLeft:4,topRight:12,bottomLeft:12,bottomRight:12}).shadow({radius:4,color:'#08000000',offsetY:2}).constraintSize({maxWidth:320}).lineHeight(20)Text(msg.time).fontSize(10).fontColor('#9CA3AF').alignSelf(msg.isSelf?ItemAlign.End:ItemAlign.Start)}.alignItems(msg.isSelf?HorizontalAlign.End:HorizontalAlign.Start)if(msg.isSelf){// 自己:头像在右Text('👨‍💻').fontSize(20).width(36).height(36).borderRadius(18).backgroundColor('#EFF6FF').textAlign(TextAlign.Center).alignSelf(ItemAlign.Start)}}.width('100%').justifyContent(msg.isSelf?FlexAlign.End:FlexAlign.Start).padding({left:16,right:16,top:6,bottom:6})}@BuilderemojiPanel(){Flex({wrap:FlexWrap.Wrap,alignContent:FlexAlign.Start}){ForEach(['😀','😂','🥰','😎','🤔','😴','😅','🎉','👍','❤️','🔥','✨','💯','🙏','👏','🎊'],(emoji:string)=>{Text(emoji).fontSize(24).padding(8).borderRadius(8).backgroundColor(Color.Transparent).onClick(()=>{this.inputText+=emoji})})}.width('100%').height(132).padding(8).backgroundColor(Color.White).border({width:{top:1},color:'#F3F4F6'})}@BuildertoolPanel(){Flex({wrap:FlexWrap.Wrap,alignContent:FlexAlign.Start}){ForEach([{icon:'📷',label:'拍照'},{icon:'🖼️',label:'图片'},{icon:'📁',label:'文件'},{icon:'📍',label:'位置'},{icon:'📊',label:'表格'},{icon:'💻',label:'代码'},{icon:'🎤',label:'语音'},{icon:'📹',label:'视频'},],(tool:ToolItem)=>{Column({space:4}){Text(tool.icon).fontSize(24).width(48).height(48).borderRadius(12).backgroundColor('#F3F4F6').textAlign(TextAlign.Center)Text(tool.label).fontSize(11).fontColor('#6B7280')}.flexBasis('25%').alignItems(HorizontalAlign.Center).padding({top:8,bottom:8}).onClick(()=>{})})}.width('100%').padding(12).backgroundColor(Color.White).border({width:{top:1},color:'#F3F4F6'})}build(){Column({space:0}){// 顶部对话信息栏Row({space:12}){Text('🤖').fontSize(24).width(40).height(40).borderRadius(20).backgroundColor('#E5E7EB').textAlign(TextAlign.Center)Column({space:2}){Text('HarmonyOS 技术助手').fontSize(15).fontWeight(FontWeight.Medium).fontColor('#111827')Text('在线').fontSize(11).fontColor('#10B981')}.layoutWeight(1).alignItems(HorizontalAlign.Start)Row({space:8}){Text('📞').fontSize(18).fontColor('#6B7280')Text('⋯').fontSize(18).fontColor('#6B7280')}}.padding({left:16,right:16,top:14,bottom:14}).backgroundColor(Color.White).width('100%').shadow({radius:4,color:'#08000000',offsetY:2})// 消息列表Scroll(this.scrollerRef){Column({space:4}){// 日期分割Text('今天').fontSize(11).fontColor('#9CA3AF').padding({top:16,bottom:8})ForEach(this.messages,(msg:Message)=>{this.messageBubble(msg)})}.width('100%').padding({bottom:16})}.layoutWeight(1).backgroundColor('#F9FAFB')// 工具栏(底部固定)Column({space:0}){// Emoji 面板(展开时显示)if(this.showEmojiPanel){this.emojiPanel()}// 工具面板(展开时显示)if(this.showToolPanel){this.toolPanel()}// 输入区Row({space:8}){// 左侧工具按钮Row({space:4}){Text('😊').fontSize(22).fontColor(this.showEmojiPanel?'#3B82F6':'#6B7280').padding(6).onClick(()=>{this.showEmojiPanel=!this.showEmojiPanelthis.showToolPanel=false})Text('➕').fontSize(22).fontColor(this.showToolPanel?'#3B82F6':'#6B7280').padding(6).onClick(()=>{this.showToolPanel=!this.showToolPanelthis.showEmojiPanel=false})}// 弹性输入框TextArea({placeholder:'Enter 发送,Shift+Enter 换行',text:this.inputText}).layoutWeight(1).maxLines(4).height(40).backgroundColor('#F3F4F6').borderRadius(20).fontSize(14).padding({left:14,right:14,top:8,bottom:8}).border({width:0}).onChange((v)=>{this.inputText=v})// 发送按钮Button(this.inputText.trim()?'发送':'语音').width(64).height(36).backgroundColor(this.inputText.trim()?'#3B82F6':'#E5E7EB').fontColor(this.inputText.trim()?Color.White:'#9CA3AF').fontSize(13).borderRadius(18).onClick(()=>{this.sendMessage()})}.width('100%').padding({left:12,right:12,top:10,bottom:10}).backgroundColor(Color.White).border({width:{top:1},color:'#F3F4F6'}).alignItems(VerticalAlign.Bottom)// ← 关键:底部对齐}}.width('100%').height('100%').constraintSize({minWidth:480,maxWidth:800}).margin({left:'auto',right:'auto'})}}

alignItems Bottom 的作用

工具栏 Row 里alignItems: VerticalAlign.Bottom:当 TextArea 因为文字多了变高时,左边的 emoji 按钮和右边的发送按钮保持在底部,和输入框的最后一行文字对齐。

如果用默认的 Center 对齐,输入框变高后,按钮会跑到中间,视觉上很奇怪。

发送按钮动态切换

输入框有内容时显示"发送"(蓝色),无内容时显示"语音"(灰色):

Button(this.inputText.trim()?'发送':'语音').backgroundColor(this.inputText.trim()?'#3B82F6':'#E5E7EB')

这个小细节让工具栏更有"微信感"。

小结

聊天工具栏的布局核心:layoutWeight(1)让输入框弹性填满,alignItems: VerticalAlign.Bottom让按钮底部对齐,TextArea 设maxLines避免无限增高。三个设置配合,工具栏就行为正确了。

http://www.rkmt.cn/news/1529727.html

相关文章:

  • 分层强化学习HRL实战:解决长程依赖与稀疏奖励
  • 历时数月测评!贵阳十大靠谱装修公司,刚需 / 大宅全覆盖 - 装修新知
  • ALC888S-VD2-GR,多系统兼容可直接替代多款音频 Codec
  • 大模型加知识图谱:实现精准逻辑推理
  • 闲置黄金如何变现划算 宜兴正规回收门店全解析 - 润富黄金回收
  • 嵌入式内存控制器UPM编程:RAM Word位域详解与FPM DRAM时序实战
  • 2026洛阳米皮与小吃创业投资指南:如何用3000元快速启动轻资产餐饮项目 - 年度推荐企业名录
  • 靠谱的云渲染公司怎么选?7个避坑标准一文说清 - 资讯快报
  • ALC897-VA2-CG,高清音频解码,内置降噪 DSP,102dB 信噪比告别电流杂音干扰
  • 深度解析EASY-HWID-SPOOFER:Windows内核级硬件指纹伪装技术实战
  • 深入解析SoC XBAR从端口:状态机、仲裁与停车模式实战
  • 别再手动敲命令了!用Ansible一键部署VictoriaMetrics集群(附完整Playbook)
  • 工程塑料型材厂家怎么挑?2026优质厂商推荐 - 品牌2026
  • PgAdmin4连接PostgreSQL 16.1失败?别慌,这5步配置帮你搞定远程连接(附pg_hba.conf详解)
  • 5大优势掌握Vulkan图形编程:从零到高性能渲染实战
  • 调查研究-177 Agent / Harness 工具链研究:从会调用工具的 LLM,到可观测、可验证、可交付的智能体系统
  • 2026年东莞工业润滑脂厂家优选:防锈润滑脂、密封润滑脂供应商实力与专家视角 - 企业推荐官【官方】
  • TradSimpChinese:Calibre电子书繁简转换的专业解决方案
  • 企业级iBATIS到MyBatis平滑迁移:自动化转换工具的技术决策指南
  • MPC8560 PIC中断控制器详解:从架构原理到驱动实战
  • 深度解析:Windows硬件ID修改的完整解决方案
  • PXD10 QuadSPI模块深度解析:从SPI基础到串行闪存内存映射实战
  • 闲置黄金别乱卖!常州全城正规黄金回收门店盘点攻略 - 奢侈品回收测评
  • 终极探索:用CRT-Royale-Reshade重现经典CRT显示器效果
  • PXD10微控制器DSPI模块深度解析:从寄存器配置到多设备通信实战
  • 合肥中科信息工程学校2026年招生简介|报名入口|官方招生电话 - 小途xt
  • Digital:开源数字电路设计仿真工具的终极指南
  • 嵌入式系统RTC与复位管理:PXD10实战配置与低功耗设计
  • AndroidIDE终极指南:在手机上打造专业开发环境的完整教程
  • 垃圾袋批次色差、厚薄不均?标准化量产解决商用采购痛点 - 速递信息