当前位置: 首页 > 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.jsqmd.com/news/1017896/

相关文章:

  • 3分钟让你的Windows 11重获新生:Win11Debloat终极优化指南
  • 2026年长沙美业培训选择指南:零基础创业就业全解决方案 - 企业名录优选推荐
  • 2026年长沙零基础学美业、美业创业培训机构深度评测与官方对接指南 - 企业名录优选推荐
  • 2026年6月临平黄金名包名表回收标杆商家:首选临平黄金名包名表回收的TOP 1,杭州名家奢侈品,临平区回收价高口碑可靠 - 人间半盏茶
  • NXP PXD10 MCU硬件设计核心:电源、时钟、复位与系统集成实战
  • 佛山包包回收实体门店,透明交易更放心 - 讯息早知道
  • 分层强化学习HRL实战:解决长程依赖与稀疏奖励
  • 历时数月测评!贵阳十大靠谱装修公司,刚需 / 大宅全覆盖 - 装修新知
  • 2026实木地板品牌排行榜:林昌地板凭什么稳坐榜首?这份选购指南请收好 - 936品牌测评网
  • 大模型加知识图谱:实现精准逻辑推理
  • ALC888S-VD2-GR,多系统兼容可直接替代多款音频 Codec
  • 高效汉化去码完整方案:5分钟解锁Honey Select 2全部功能
  • 2026 成都黄金回收综合榜单更新,收的顶实力稳居前列 - 奢侈品回收评测
  • G-Helper架构解析:华硕笔记本轻量级控制工具的技术实现与性能优化深度评测
  • 闲置黄金如何变现划算 宜兴正规回收门店全解析 - 润富黄金回收
  • Visual C++运行库终极解决方案:告别程序无法启动的烦恼
  • 嵌入式内存控制器UPM编程:RAM Word位域详解与FPM DRAM时序实战
  • GIS工程师的遥感+机器学习实战指南:空间约束优先的AI落地路径
  • 靠谱的云渲染公司怎么选?7个避坑标准一文说清 - 资讯快报
  • ALC897-VA2-CG,高清音频解码,内置降噪 DSP,102dB 信噪比告别电流杂音干扰
  • 跨越平台边界:用命令行工具优雅下载M3U8流媒体视频
  • 深度解析EASY-HWID-SPOOFER:Windows内核级硬件指纹伪装技术实战
  • 2026 成都商圈包包回收门店测评,春熙路 / 高新区好店汇总 - 开心测评
  • 2026年最新亲测15款降AI率软件红黑榜!
  • 2026洛阳米皮与小吃创业投资指南:如何用3000元快速启动轻资产餐饮项目 - 年度推荐企业名录
  • 深入解析SoC XBAR从端口:状态机、仲裁与停车模式实战
  • 成都本地闲置名表处理 百达翡丽劳力士线下回收全攻略 - 开心测评
  • 在Mac上无缝运行Windows应用:Whisky让跨平台工作更简单
  • PgAdmin4连接PostgreSQL 16.1失败?别慌,这5步配置帮你搞定远程连接(附pg_hba.conf详解)
  • 5大优势掌握Vulkan图形编程:从零到高性能渲染实战