Flutter 可复用公共组件库设计与落地:AppDialog/BottomSheet 等实战

发布时间:2026/8/8 6:04:12

Flutter 可复用公共组件库设计与落地:AppDialog/BottomSheet 等实战
Flutter 可复用公共组件库设计与落地AppDialog/BottomSheet 等实战作者FungLeo 适用Flutter 3.x / Material 3目标把每个页面都长差不多的那部分 UI 抽成一层薄封装统一风格、统一质量。前言各位看官这是主题系列的最后一篇了。前面 Flutter Material 3 从 0 搭品牌主题系统四件套实战全记录 讲主题四件套、Flutter 把第三方 UI 库渐进迁回 Material 3组件映射总表 四批次替换实战 讲 UI 库迁移、BrandColors 那篇讲颜色收口这篇聊聊公共组件库。先说说我为什么会去抽这个东西。有一次做需求我要在一个列表页上加个确认删除的弹窗。很简单对吧我顺手就写了showDialog(context:context,builder:(ctx)AlertDialog(title:constText(确认),content:constText(确定删除),actions:[TextButton(onPressed:()Navigator.pop(ctx,false),child:constText(取消)),FilledButton(onPressed:()Navigator.pop(ctx,true),child:constText(确定)),],),);写完提交测试同学过来说这个弹窗跟隔壁那个页面的不一样啊。我去看了一眼隔壁好家伙——按钮顺序是反的确定在左文案是是/否而且它用的是Navigator.of(context).pop()不带返回值外面靠一个变量接。再翻第三个页面那个的取消按钮居然是OutlinedButton。三个页面三种弹窗。全都是我们自己人写的而且每一个单独看都没毛病。这其实就是前面 BrandColors 那篇里说的风格漂移只不过那篇漂的是颜色这里漂的是交互。说实话这事儿怪不着谁。写业务的时候谁会专门去翻别的页面看弹窗长什么样呢大家都是凭手感写的手感自然各不相同。所以解法还是那个别靠自觉靠机制。我们抽了一个lib/widgets/公共组件库目前大概 20 个组件。这篇就讲讲怎么设计、怎么落地。本文要点抽组件前先做分析同一个 UI 出现第三次才抽别提前造一堆用不上的弹窗 / Toast 这类调一下就完事的东西用abstract final class 静态方法消灭 builder 样板多种形态的组件用命名构造器把形态编进类型非法状态编译期就不存在公共组件内部只用BrandColors对加个自定义颜色的需求要克制开了口子就守不住组件只管展示不碰数据否则复用性归零光有组件不够配套规范文档 code review 才有据可依一、先别急着抽先做分析我想先泼一盆冷水抽组件之前先搞清楚哪些东西真的值得抽。我见过一种很典型的过度设计项目刚起步先花两周搭一个公共组件库把想象中会用到的组件都写一遍。结果做到后面发现一半组件从来没被第二个页面用过另一半因为当初没预料到实际需求参数设计得完全不合适用的时候还得改。这就是典型的脱裤子放屁。我的判断标准很土同样的 UI 出现第三次的时候才考虑抽。出现一次正常写。出现两次留意一下可能是巧合。出现三次这就是模式了抽。按这个标准我们动手前先跑了一轮扫描把项目里重复的 UI 模式列出来# 看看有多少页面在自己拼搜索框➜grep-rlnTextFieldlib/pages/|wc-l# 看看 showDialog 散落在多少处➜grep-rnshowDialog\|showModalBottomSheetlib/|wc-l# 看看有多少处在手写空态➜grep-rn暂无数据\|没有更多lib/跑完就有数了搜索框 12 个页面各写各的、弹窗 20 多处、空态提示 15 处文案还不统一。这些就是该抽的。而某些我原以为肯定要抽的东西实际只出现过一次那就先放着。别盲目抽先有分析报告。这是我最想说的第一条。二、组件清单节选分析完我们最终沉淀下来的组件大概是这些组件用途AppSearchBar统一搜索栏聚焦边框 透明底AppFormSection表单区块标签 必填 * 间距AppActionBar底部操作栏单提交 / 多按钮AppDialog统一弹窗confirm/alert静态方法AppBottomSheet底部抽屉拖拽手柄 居中标题 关闭AppToast全局轻提示AppTextarea多行输入带字数计数浮层AppErrorBody/AppEmptyBody错误态 / 空态AppFilterChips横滚药丸筛选条AppTag/AppInfoRow状态标签 / 图标信息行AppSegmentedTab/AppScopeToggle分段 Tab / 二选一范围切换AppListFooter/AppStickyHeader加载更多 footer / 吸顶头统一用App前缀好处是打字的时候输入App就能唤起补全一眼看到全部可用组件。这个小细节对新同学特别友好——他不需要读文档IDE 会告诉他有什么。三、设计原则1. 用abstract final class 静态方法消灭样板对于弹窗、Toast 这种调一下就完事的东西我不建议做成 Widget做成静态方法更顺手/// 统一弹窗入口。/// abstract final 表示既不能被继承也不能被实例化纯粹当命名空间用。abstractfinalclassAppDialog{/// 确认弹窗一行调用拿到 bool 结果没有 builder 样板staticFutureboolconfirm({requiredBuildContextcontext,requiredStringtitle,requiredStringcontent,StringcancelText取消,StringconfirmText确定,})async{finalresultawaitshowDialogbool(context:context,builder:(ctx)AlertDialog(title:Text(title),content:Text(content),actions:[TextButton(onPressed:()Navigator.pop(ctx,false),child:Text(cancelText),),FilledButton(onPressed:()Navigator.pop(ctx,true),child:Text(confirmText),),],),);// 用户点遮罩关闭时 result 是 null统一收敛成 falsereturnresult??false;}}业务页里就一行finalokawaitAppDialog.confirm(context:context,title:确认,content:确定删除,);if(ok){// 执行删除}对比一下开头那十几行清爽多了对吧。这里有两个细节值得单独说说一是return result ?? false这个兜底。showDialog返回的是可空类型因为用户可能点遮罩或者按返回键关掉弹窗这时候你的Navigator.pop(ctx, true/false)根本没执行拿到的就是null。如果不收敛调用方就得每次写if (ok true)。而人是会偷懒的总有人写成if (ok)——然后编译报错再改成if (ok!)好了用户点个遮罩就崩溃。把它收敛在组件内部这类问题从源头上就不存在了。二是abstract final class这个写法。这是 Dart 3 的类修饰符含义是这个类既不能被继承也不能被实例化。为什么要这么严因为AppDialog本质上只是个命名空间我们要的就是AppDialog.confirm(...)这个调用形式没人应该去new AppDialog()。用abstract final声明出来编译器直接帮你把错误用法堵死了比写注释请勿实例化靠谱一万倍。如果你的项目还在 Dart 2退而求其次可以用私有构造器classAppDialog{AppDialog._();// Dart 2 时代的老办法防止被实例化staticFutureboolconfirm({...})async{/* ... */}}2. 命名构造器 私有字段把形态编进类型里有些组件会有几种形态。比如底部操作栏有时候是一个大提交按钮有时候是并排几个按钮。最容易想到的写法是加参数AppActionBar(submitText: ..., items: ...)然后内部判断哪个不为 null。但这样有个问题——调用方可以两个都传也可以两个都不传这两种情况你都得处理还得想好优先级。更好的做法是用命名构造器把形态编进类型里classAppActionBarextendsStatelessWidget{/// 形态一单个提交按钮constAppActionBar.submit({super.key,requiredStringtext,requiredVoidCallbackonPressed,}):_itemsconst[],_submitTexttext,_onSubmitonPressed;/// 形态二并排多个操作按钮constAppActionBar.actions({super.key,requiredListActionItemitems,}):_itemsitems,_submitTextnull,_onSubmitnull;finalListActionItem_items;finalString?_submitText;// 非 null 即代表 submit 形态finalVoidCallback?_onSubmit;overrideWidgetbuild(BuildContextcontext){// 靠 _submitText 是否为空来分流两种形态互斥且必有其一return_submitText!null?_buildSubmit():_buildActions(_items);}// ...}这么写之后调用方只有两条路可走// 提交形态AppActionBar.submit(text:保存,onPressed:_onSave);// 多按钮形态AppActionBar.actions(items:[/* ... */]);非法状态在编译期就不存在了你不可能构造出一个既有提交按钮又有多按钮的怪东西。这个思路在 Flutter 里挺常用的官方的ListView/ListView.builder/ListView.separated就是这个路子。字段用下划线开头设为私有是因为它们只是内部实现细节不希望外部读取。3. 所有组件只用BrandColors不接外部色值公共组件是设计系统的门面所以内部颜色一律走 BrandColors 那篇讲的语义色。这条要主动做减法即使有同学来提需求说能不能给AppTag加个color参数我这儿要个紫色的也建议先问一句为什么需要紫色。因为一旦开了这个口子组件就守不住了。今天加个color明天加个borderRadius后天加个padding——加到最后这个组件就退化成了一个参数特别多的 Container它统一风格的价值荡然无存。我的处理方式一般是如果确实是一个新的语义比如待处理状态需要一个新颜色那就在BrandColors里加一个语义常量然后在组件内部支持这个语义枚举如果只是某个页面想搞点特殊那就……委婉拒绝哈。真的特殊到这个地步那就说明它不该用公共组件。4. 组件自己不管数据只管展示这条是红线我单独拎出来说。公共组件里不要出现任何请求、任何全局状态读取。它应该是纯粹的输入参数 → 渲染 UI 抛出回调。// ✅ 好数据从外面来事件往外面抛AppFilterChips(items:options,selected:currentIndex,onChanged:(i)setState(()currentIndexi),);一旦组件内部自己去取数据它就跟具体场景绑死了第二个页面想复用的时候一定会发现它取的不是我要的东西。那就白抽了。四、落地过程中踩的坑光讲原则有点虚说两个我实际踩到的。坑一BuildContext 用错弹窗关不掉写AppDialog的时候我一开始是这么写的// ❌ 错误示范builder 里用了外层的 contextfinalresultawaitshowDialogbool(context:context,builder:(_)AlertDialog(actions:[TextButton(onPressed:()Navigator.pop(context,false),// 注意这里用的是外层 contextchild:constText(取消),),],),);看着好像没问题很多时候也确实能跑。但在嵌套 Navigator 的场景下比如底部 Tab 每个都有自己的 Navigator这个外层context找到的可能是错误的那个 Navigator结果就是点取消把整个页面 pop 掉了弹窗还在那儿杵着。正确做法就是前面代码里那样用 builder 给你的那个ctx。这个 context 是弹窗自己那一层的pop 的一定是弹窗本身。排查这个问题的时候我绕了不少弯路一度以为是路由配置的问题。后来是靠打印 context 的层级才定位到// 临时加一行看看当前 context 找到的是哪个 NavigatordebugPrint(navigator ${Navigator.of(ctx)});坑二异步之后用 contextlint 会警告写异步弹窗的时候你大概率会撞上这个警告info • Dont use BuildContexts across async gaps • use_build_context_synchronously意思是await之后这个 widget 可能已经被销毁了你再用它的context就是访问一个失效的对象运行时可能崩。正确的处理是加mounted检查finalokawaitAppDialog.confirm(context:context,title:确认,content:确定删除);// await 之后先确认组件还活着再用 contextif(!mounted)return;if(ok){AppToast.show(context,已删除);}我知道有些同学嫌烦会用// ignore: use_build_context_synchronously一把把它按掉。我劝各位看官别这么干哈——这个警告是真能救命的用户在弹窗还开着的时候点了返回键就是妥妥的崩溃现场。五、光有组件不够还得有规则最后说个非技术但很关键的点。组件抽出来了不代表大家就会用。总有人因为不知道、或者觉得我这个场景有点特殊绕过去自己写一个。所以组件库一定要配套规则写进团队的 UI 规范文档关于硬性规则的章节比如列表页筛选条一律使用AppFilterChips禁止自绘确认类弹窗一律使用AppDialog.confirm禁止直接调showDialog空态 / 错误态一律使用AppEmptyBody/AppErrorBody。有了这几条code review 的时候就有据可依了。看到 diff 里出现裸的showDialog一句用AppDialog哈就能打回去不用每次都重新论证一遍为什么。小结好啦公共组件库这块就聊完了顺带整个主题系列也告一段落。捋一下核心的几条公共组件的价值不在少写几行而在风格统一 质量门禁。一个组件被全站复用的前提是它自己的质量过得去。抽组件前先做分析同一个 UI 出现第三次再抽。别盲目抽更别提前造一堆用不上的。abstract final class 静态方法 / 命名构造器是 Flutter 组件 API 的优雅范式。把非法状态在编译期干掉比运行时判断强得多。组件内部只用BrandColors对加个自定义颜色参数的需求要克制开了口子就守不住。组件只管展示不碰数据否则复用性归零。配套规范文档否则总会有人绕过去。说实话这一层封装的代码量真不大二十个组件加起来也就一两千行。但它带来的变化挺明显的新同学接手页面不用再纠结弹窗该长什么样AppDialog.confirm一行搞定样式自动就对了。这种想写错都难的状态才是组件库真正的价值。那么各位看官您的项目里公共组件是怎么划边界的有没有遇到过抽早了或者抽过头了的情况欢迎在评论区聊聊哈。如果这篇对您有点帮助也希望看官您用发财的小手点个小赞哈本文由 FungLeo 主导Deepseek 优化校阅转发请注明首发地址谢谢大家相关阅读Flutter/Android Release 包连不上网AndroidManifest INTERNET 权限排查实录Flutter dart-define 实现 dev/正式双构建调试代码正式包零残留Flutter 接入 Alice 调试浮窗一个顶层 final 抢跑把 release 网络整没了Flutter Android 构建突发红字一个跟通知无关的库逼你开 core library desugaringFlutter Debug 红屏、Release 灰屏你的 release-only bug只是异常被藏起来了Flutter Material 3 从 0 搭品牌主题系统四件套实战全记录Flutter 把第三方 UI 库渐进迁回 Material 3组件映射总表 四批次替换实战Flutter BrandColors 设计 Token 集中管理消灭硬编码色值实战

相关新闻

MIMO-OFDM系统信道估计算法实现与性能对比

MIMO-OFDM系统信道估计算法实现与性能对比

2026/8/8 6:04:12

1. MIMO-OFDM系统信道估计概述在无线通信领域,MIMO(多输入多输出)与OFDM(正交频分复用)技术的结合已成为现代通信系统的核心技术方案。这种组合能够有效对抗多径效应,提高频谱利用率,实现高速率…

全国123年1km降水栅格数据的技术解析与应用实践

全国123年1km降水栅格数据的技术解析与应用实践

2026/8/8 6:04:12

1. 项目概述:全国逐年降水栅格数据的价值与应用降水数据是气候研究、农业生产、水利规划等领域的基础性资源。这套覆盖全国范围、时间跨度长达123年(1901-2024)、空间分辨率达到1公里的降水栅格数据集,堪称国内气象数据领域的&quo…

开源AI终端Kaku实测:本地部署、分屏协作与性能调优全解析

开源AI终端Kaku实测:本地部署、分屏协作与性能调优全解析

2026/8/8 6:04:12

1. 项目概述:Kaku,一个值得关注的AI终端新选择最近在开发者圈子里,一个名为Kaku的开源AI终端工具突然火了起来。看到“又一个顶级 AI 终端诞生了”这样的标题,我的第一反应是:又来一个?毕竟,从早…

UDS诊断-85服务

UDS诊断-85服务

2026/8/8 7:24:24

一、引言在ECU刷写过程中,目标ECU进入编程模式后不再发送正常的应用报文。此时,总线上的其他ECU(对手件) 会因为收不到预期信号而纷纷记录故障码(DTC)。一次刷写下来,可能产生几十甚至上百个&qu…

精准测试与 AI 系统设计·从0到1工程手册

精准测试与 AI 系统设计·从0到1工程手册

2026/8/8 7:24:24

定位 这是一套面向平台工程师、后端工程师和测试基础设施工程师的深度技术专辑。重点不是功能介绍,而是把一套精准测试平台的关键工程问题讲透:运行时数据怎么采,静态源码怎么补,覆盖率怎么归一化,版本差异怎么驱动测试…

汇川H5U PLC远程IO配置实战:从EtherCAT组态到IO映射全解析

汇川H5U PLC远程IO配置实战:从EtherCAT组态到IO映射全解析

2026/8/8 7:24:24

大家好,我是专注于工业自动化技术分享的博主。在汇川H5U系列PLC的实际项目应用中,远程IO模块的配置与映射是构建分布式控制系统的基础,也是许多工程师从入门到精通必须跨越的一道坎。你是否曾遇到过远程站点的信号无法读取、映射地址混乱导致…

基于ACP模型选择器与Reasonix框架的DeepSeek V4推理部署实战

基于ACP模型选择器与Reasonix框架的DeepSeek V4推理部署实战

2026/8/8 7:24:24

1. 项目概述:当开源推理框架遇上国产大模型最近在折腾大模型本地部署和推理优化,发现了一个挺有意思的组合:用开源的Reasonix 1.x推理框架来跑DeepSeek V4模型。这听起来可能有点“跨界”,毕竟DeepSeek V4是国产大模型中的佼佼者&…

Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?

Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工?

2026/8/8 7:24:24

Dify 中级实验(03):模板转换实战——如何用零 Token 完成文本加工? Dify 实验系列 中级 03/20 | 实验编号:DIFY-102-04 上篇:中级实验(02):问题分类器——智能路由引擎如…

AI 应用方案设计

AI 应用方案设计

2026/8/8 7:14:24

根据需求,实现具备多轮对话能力的 AI 恋爱大师应用,整体方案围绕 2 个核心展开:系统提示词的设计多轮对话的实现1、系统提示词设计系统提示词相当于 AI 应用的 “灵魂”,直接决定 AI 的行为模式、专业性和交互风格。简单提示写法直…

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

2026/8/6 19:19:00

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾经从网易云音乐下载了心爱的歌曲&am…

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

2026/8/8 5:17:40

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比工程导读:本文深入讨论 分布式配置中心选型实战:Nacos与Consul在创业场景下的对比 在生产工程实践中的核心落地方案。基于 分布式架构与微服务设计 视角,剖析实际痛点、架…

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

2026/8/5 8:19:55

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案 【免费下载链接】MoneyPrinterPlus AI一键批量生成各类短视频,自动批量混剪短视频,自动把视频发布到抖音,快手,小红书,视频号上,赚钱从来没有这么容易过! 支持本地语音模型chatTTS,fasterwhisper,…

昇腾AI代理实现多号通话自动化

昇腾AI代理实现多号通话自动化

2026/8/8 0:03:20

基于昇腾(Ascend)硬件与AtomGit AI社区的开源生态,结合AI Agent技术,可以实现一个模拟“通话重复使用机号复制”功能的安卓手机应用原型。其核心是利用AI Agent进行意图理解、任务编排和自动化操作,模拟或管理多号码的…

2026年Graph+AI Agents最新创新思路

2026年Graph+AI Agents最新创新思路

2026/8/8 0:03:20

本次围绕GraphAI Agents这个方向筛选了15篇高质量论文,都是近年来具有较高引用价值或方法创新的研究工作,其中部分来自IJCAI、AAAI、ICRA。 对于论文er来说,这些论文方法结构清晰、可复现性较强,在多个任务上都有可延展的空间。如…

Wand-Enhancer 指南:5分钟解锁Wand专业版功能,永久移除2小时限制

Wand-Enhancer 指南:5分钟解锁Wand专业版功能,永久移除2小时限制

2026/8/8 0:03:20

Wand-Enhancer 指南:5分钟解锁Wand专业版功能,永久移除2小时限制 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer 还在为Wan…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/8 5:07:31

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/7 8:02:42

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/8 2:30:15

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…