第一次看到 AFAN 这组基于 Flutter 开发设计的追番看漫 App 界面时我的第一反应不是“好看”而是“它好像知道我要先看哪里”。这句话听起来有点玄但如果你同时打开过两三个追番类应用应该能立刻理解我在说什么。很多同类产品的首页会把运营位、推荐横幅、排行入口全部堆在一起视觉上很热闹可你真正想知道的“昨天追的那部番更新了没有”“我上次看到第几话”反而要在一堆卡片里找半天。AFAN 在 UI 鉴赏的语境里更像是一个设计提案而不是某个已经上架的产品。它的价值不在于某一个控件有多特别而在于它把追番、看漫这两个高频动作用一套很克制的界面语言重新组织了一遍。这篇文章我想从三个角度来聊它到底替用户解决了什么信息组织问题、用 Flutter 实现这类 UI 时哪些技术点最值得琢磨、以及从一份 UI 稿走到可运行 App中间到底差哪些工程化能力。核心判断先放在这里追番看漫类 App 的 UI 之争表面是审美之争深层是信息优先级之争。1. 追番和看漫是两个需要被拆开设计的使用场景1.1 时间线和连续阅读逻辑完全不同追番的核心信息单元是“作品 更新进度 更新时间”。用户关心的是时间维度这周更新到第几集、上一集看到哪里、新季度有没有续作。它的信息组织方式天然接近日历和时间轴。看漫不一样。看漫的核心是连续阅读上一话停在 32 页、新章是否已经连载到第 200 话、继续阅读要能直接回到上次的位置。漫画阅读的连续性比“更新提醒”更重要信息组织方式是章节树和阅读位置。这两个场景如果揉在同一个页面里很容易互相干扰。很多追番 App 把“继续看”和“最近更新”混在一个信息流里没有明显的层级区分。AFAN 这类 UI 设计稿做得比较好的一点是它明显把“继续观看 / 继续阅读”提到首屏最高的信息层级用横向卡片单独隔离出来再往下才是推荐和排行。这个决策看起来很普通但恰恰是大多数同类产品没有坚持住的。1.2 大多数同类 App 的问题不是丑而是信息层级混乱我平时会看很多 UI 鉴赏类的设计稿一个很直观的感受是追番类 App 的视觉天花板普遍不低但交互上经常出现同样的毛病。导航项太多底部 Tab 动不动就五六个用户每次都要判断“我现在该点哪里”。运营位和推荐位过于强势封面墙变成广告墙。状态表达不明确“已看完”“在看”“想看”“搁置”经常只是一个小图标颜色、位置都不统一扫一眼根本分不清。列表卡片缺少主次每个卡片都在抢注意力结果是没有一个被真正看到。AFAN 在 UI 稿里处理的思路是把状态做成进度条和颜色语义。比如已读部分用高对比色未读部分弱化更新提醒用明确的角标而不是一排字。这些细节单看都不复杂合在一起才是“界面知道我要看哪里”的来源。1.3 UI 鉴赏项目真正的参考价值是它把优先级重新排了一遍这里要先说清楚UI 鉴赏稿和正式产品之间天然存在距离。设计稿里没有真实数据的复杂度没有十几个运营位的争夺也没有用户长期使用后的数据膨胀。所以看这类项目时不要只盯着视觉好不好看更要看它的信息层级是不是可以用一句话概括。AFAN 的首页如果用一句话概括就是“先让用户继续再让用户发现”。这个优先级的排序比任何单个控件的设计都值得抄。反过来说这也是判断一份 UI 稿有没有价值的标准你能不能把它的首页逻辑压缩成一句话。如果压缩不出来那它就还停留在素材拼贴阶段。2. 拆解界面每个页面其实都在回答一个问题2.1 首页不是展示墙而是“上次读到哪里”的入口从 UI 稿的布局逻辑看首页通常会分成三个层级。第一层是顶部工具区包括搜索、头像和个人入口。这里信息密度最低作用只是让用户知道“我在哪个 App 里”。第二层是继续阅读 / 继续观看区通常是一组横向滑动的高比例卡片。卡片上的关键是封面、标题、进度条和“更新到第几话”的角标。这里的交互目标是用户只需要一次横向滑动就能定位到自己手上在追的所有内容。第三层是发现区包括热门推荐、新作上架、分类入口。这一层可以做得丰富但它不能反过来吞掉第二层。UI 稿里常见的处理是给发现区配一个明确的分区标题和前面的内容在视觉上拉开距离。在 Flutter 里实现这个结构横向列表可以用ListView加scrollDirection: Axis.horizontal或者PageView做分页卡片。重点不是控件多高级而是数据模型上“继续阅读”不能用普通推荐列表的数据模型硬套它需要额外的 progress 字段、更新时间字段和断点位置字段。2.2 详情页把“追番”这个动作做得足够低门槛详情页的核心问题不是展示信息而是“用户在这里最想完成什么动作”。对追番看漫类 App 来说这个动作通常只有一个收藏 / 追番。其次是看到相关推荐和评论区。UI 稿里值得注意的设计是主操作按钮的优先级。收藏 / 追番按钮通常被放在封面下方或右上角颜色、尺寸都要显著而分享、下载、评分这些次要操作被弱化成图标。这个取舍的逻辑是详情页的转化目标越单一用户做决定的成本就越低。从 Flutter 实现看详情页比较容易出问题的是封面大图和下方内容的滚动衔接。比较稳妥的做法是用CustomScrollView把封面、标题、操作区放在SliverToBoxAdapter把章节列表或推荐列表放进SliverList。这样整个页面是一个统一的滚动体验不会出现两个ListView嵌套后手势冲突的问题。我见过不少项目在这里图省事直接用一个Column套ListView结果列表一长页面直接报 “Vertical viewport was given unbounded height”这是追番类 App 新手最常见的报错之一。2.3 阅读器真正决定用户会不会留下的页面很多 UI 鉴赏项目在首页和详情页花了大量精力阅读器却只给了一张静态图。实际上漫画阅读器才是用户停留时间最长的页面也是开发成本最高的页面。阅读器至少要覆盖这几个点阅读模式单页翻页和卷动模式都要支持用户习惯差异很大。断点记忆离开页面再回来要能回到上次停的位置。图片加载预加载当前页和相邻页不能每翻一页都白屏。亮度、背景色、横竖屏在不同光线场景下要可调节。章节目录可以快速跳转且跳转后进度能正确写入本地缓存或服务端。在 Flutter 里单页模式可以用PageView加PageController卷动模式可以用ListView.builder。两者共用同一套图片加载逻辑和进度记录逻辑而不是各自写一套。这样后续做性能优化时只需要优化一个公共方法。UI 稿里往往不会显示这些东西但它们是“设计稿到产品”之间最大的隐性成本。3. 用 Flutter 实现这类 UI真正值得关注的几个技术点3.1 布局选型Sliver 体系才是长页面滚动的主力追番看漫 App 里几乎每个页面都是长页面首页是封面流详情页是封面加列表漫画目录是长列表。这意味着布局选型会直接影响滚动流畅度和代码可维护性。我的建议是优先理解并使用CustomScrollView和 Sliver 系列组件而不是到处嵌套NestedScrollView。CustomScrollView的思路是把一个页面拆成多个 Sliver每个 Sliver 负责自己的滚动和布局行为但它们共享同一个Scrollable。这样做最大的好处是你不需要关心多个滚动视图之间的手势冲突只需要组织好 Sliver 的先后顺序。如果确实需要“顶部封面大头图折叠 下方列表吸顶”的效果可以用SliverAppBar的flexibleSpace配合pinned参数。常见写法是CustomScrollView( slivers: [ SliverAppBar( expandedHeight: 280, pinned: true, flexibleSpace: FlexibleSpaceBar( background: coverImage, ), ), SliverToBoxAdapter( child: operationArea(), ), SliverList( delegate: SliverChildBuilderDelegate( (context, index) chapterItem(index), childCount: chapterCount, ), ), ], )这个结构基本能覆盖详情页的需求。这里要特别提一句expandedHeight不要设成写死的 280 就完事最好根据封面图宽高比计算否则不同屏幕宽高比下封面会变形或者露出大块留白。实际开发时我会先确保封面图有确定的aspectRatio再决定expandedHeight。3.2 图片加载与缓存这类 App 最大的性能风险追番看漫 App 几乎每一个页面都是图片密集型的。封面图、漫画页、横滑推荐卡甚至头像全部是网络图片。如果直接使用Image.network会出现几个问题图片无缓存反复滚动会重复请求内存占用不可控加载失败时缺少占位。在 Flutter 的常见实践里我会先引入cached_network_image这样的缓存方案并在列表项中配置占位图和错误图CachedNetworkImage( imageUrl: coverUrl, placeholder: (context, url) shimmerPlaceholder(), errorWidget: (context, url, error) const Icon(Icons.broken_image), memCacheWidth: 300, // 按列表显示尺寸控制内存缓存 )memCacheWidth这个参数容易被忽略但它对内存控制很重要。列表里的小封面图没必要按原图尺寸解压到内存。按控件实际需要的像素宽设置缓存尺寸能明显降低低端机上的 OOM 风险。漫画阅读页的情况更特殊。漫画页图片往往很大而且用户会连续翻多页。我的建议是不要一次性加载整本漫画的所有页面而是做一个预加载窗口当前页、前两页、后两页加上 LRU 淘汰策略。用 Flutter 的PageView做单页模式时可以在PageController的监听里根据当前页码主动调用图片缓存的预取方法或者用ImageProvider的evict控制缓存池大小。3.3 交互动效动效的价值是解释层级不是制造热闹UI 鉴赏稿里动效总是很加分。但到了实现阶段动效要克制。Flutter 的Hero动画是列表到详情页最常用的转场方式它能让用户视觉上感觉到“我点开的那个卡片变成了这一页”这是有明确信息价值的动效。Hero( tag: cover_${animeId}, child: coverImage, )要注意的是Hero的 tag 必须全局唯一。列表里用 id 拼接而不是直接用标题文本否则遇到同名内容就冲突了。另外Hero动画在页面销毁、异步加载完成的场景下偶尔会出现一闪而过或者找不到 tag 的异常。排查时先看 tag 是否唯一再看目标页面是否真的包含对应的Hero组件。至于页面上的其他动效比如卡片透明度渐变、按钮弹性缩放、进度条扫光我建议在核心流程上做不要在列表滚动中到处加。滚动过程中频繁触发动画在低端 Android 设备上会直接表现为掉帧。判断标准可以很简单删掉这个动效用户会不会觉得界面少了信息如果不会就不加。3.4 主题与暗黑模式UI 稿里最容易漏掉真机必须处理很多 UI 鉴赏稿只给一套亮色设计但追番看漫 App 的典型使用时段偏偏是晚上暗黑模式几乎是刚需。在 Flutter 里建立主题的正确路径是用ThemeData和ColorScheme统一语义色而不是到处写死颜色。比如背景色、卡片色、主色、文字主次色都应该从主题里取final colorScheme Theme.of(context).colorScheme; Container( color: colorScheme.surface, child: Text( title, style: TextStyle(color: colorScheme.onSurface), ), )暗色模式不是把背景调成黑色就结束了。它需要重新检查整条颜色链卡片和背景之间的对比度、进度的强调色在暗背景下的可读性、图片周围是否有突兀的白色边缘。UI 稿如果只画了亮色落到真机时至少要自己补一套暗色校验。注意不要在页面里散落几十个硬编码颜色。统一走主题之后暗黑模式和后续品牌换色都会轻松很多不统一的话每一次改颜色都是一次大工程。4. 从鉴赏到复刻能直接用的设计决策和需要质疑的地方4.1 三个可以直接借鉴的设计决策第一首屏只保留两个主入口。AFAN 这类稿子里底部导航或者顶部入口通常很克制把“首页”和“书架 / 追番”放在最高优先级其他功能收进二级页面。对内容型 App 来说入口越少用户越不需要思考。第二进度可视化。无论是追番的集数进度还是看漫的章节进度用一条明确的进度条或环形进度表达比“第 3 话”这种文字更直观。这里要注意进度条的数据来源UI 稿可以画一条很好看的光带但真实项目里必须明确进度是本地存储还是服务端同步、多设备之间会不会冲突。否则视觉效果越精致数据错误时用户越困惑。第三空状态和加载状态要成套设计。这大概是 UI 稿和新手实现之间差距最大的地方。设计稿里永远有完美的封面图但真实场景里会出现图挂了、列表为空、接口超时。如果这些状态没有统一的视觉规范App 一遇到异常就会露馅。我的建议是做 UI 排期时把空状态、加载状态、错误状态算进正常工作量而不是等测试发现了再补。4.2 可以借鉴但落地时要小心的几个点设计决策值得借鉴的地方落地时要小心的点首屏只保留两个主入口降低认知负担用户不用判断功能入口变少后搜索、设置、历史记录要能兜得住路径进度可视化一眼看到“看到哪了”进度指标必须来自真实进度不能只是视觉装饰统一的加载 / 空 / 错误状态异常场景不露馅需要覆盖网络失败、无数据、图片加载失败等分支并不是所有设计稿里的选择都值得原样搬进产品。常见的几个问题过大的封面圆角。圆角能让卡片柔和但圆角过大会裁掉主体内容尤其是人物脸部。真实封面基本是方形构图圆角控制在 8 到 16 像素通常更安全。半透明层叠。UI 稿里半透明背景很好看但文字压在半透明图上对比度一旦不够真机上就不好读。动画密度过高。封面流里加太多缩放和浮动效果视觉上高级但滚动性能会打折扣。只有亮色方案。前面已经说过暗黑模式在这类 App 里不是加分项是基本项。判断一个设计是否值得落地可以先做个简单测试把设计稿截成一个 360x800 的小屏原型去掉所有装饰性元素只看信息层级能不能在 3 秒内被看清楚。看不清就说明信息设计本身还没有立住。4.3 从 UI 稿走向可运行 App建议按这个顺序落地如果想把 AFAN 这类 UI 鉴赏稿改造成自己的练习项目我建议不要一上来就铺开所有页面。更稳的顺序是先搭工程骨架创建 Flutter 项目配置主题、路由、多环境入口。用假数据把首页和详情页做出来重点是验证滚动结构、卡片布局、Hero转场。实现一个最小阅读器能够加载图片、翻页、记录进度。这一步技术风险最高但也是看起来最像产品的部分。一切跑通后再考虑接入真实数据源。先用接口返回的 JSON 替代写死的 model不要一开始就接入很重的状态管理方案。最后做性能验证列表滚动帧率、图片内存占用、低端机上的表现。这个顺序的核心逻辑是先验证流程再补工程化。UI 鉴赏稿最大的风险不是画得不够好而是落到 Flutter 实现后交互手感、图片加载、数据状态这些看不见的部分出了问题。先跑通最小闭环后面加东西才有一块稳定地基。5. 审美之外决定这类 App 能不能长期用的三块拼图5.1 数据与状态管理进度到底存在哪里追番看漫 App 有一个所有内容产品都会遇到的难题用户进度和收藏列表是存在本地还是服务端。如果只是个人练习项目用本地存储比如shared_preferences或sqflite完全够用。但真实产品里用户换设备、清缓存、重装 App都要求进度能同步。这就意味着后端需要一套用户数据和进度同步接口UI 只是最上面的一层。对普通开发者来说我的建议是先用本地存储跑通全部流程明确划分出“本地进度模型”和“接口数据模型”等需要多端同步时再替换数据层。不要在 UI 逻辑里混着写存储代码否则后面一旦要迁移会到处是坑。5.2 阅读体验的长期维护图片加载策略和缓存清理漫画阅读页上线初期看起来很流畅但长期使用后内存和缓存会慢慢膨胀。实际落地时要考虑本地缓存目录的大小上限、超过上限后的淘汰策略、更新图片版本后的缓存失效机制。这些在 UI 稿里完全不会出现但恰恰是决定用户会不会在第三个月卸载 App 的因素。如果阅读器出现“图片越翻越慢”或者闪退我建议按这个顺序排查先看现象是翻页卡顿还是翻到某一页直接退出还是白屏超时。再看输入图片 URL 是否有效、图片尺寸是否异常、后端返回的格式是否正确。再看环境设备剩余内存、Flutter 版本、图片缓存库版本是否匹配。再看参数预加载窗口是不是开得太大、memCacheWidth是否设置、缓存池上限是否合理。最后看工具边界当前缓存库对大图的解码限制、平台本身的内存限制是不是已经被触碰。这类问题通常不是动画或布局导致的而是图片缓存缺少管理。先把缓存策略定清楚再去调界面细节方向才不会错。5.3 平台适配Android 和 iOS 的差异不能只在模拟器里验证Flutter 做跨平台很方便但不同平台的行为差异很具体。比如返回手势、滚动回弹、键盘弹出、系统字体缩放、导航栏高度都可能导致同一套 UI 在两个平台上的表现不一致。UI 鉴赏稿通常只画一套视觉但要变成能上架的 App必须至少在 Android 真机和 iOS 真机上各过一遍核心流程。另外还有一个容易被忽略的点列表滚动性能在低端 Android 设备上需要单独验证。设计稿里精致的封面墙在 60Hz 都跑不满的旧设备上可能需要压缩图片尺寸、减少阴影和模糊效果、关闭不必要的动画。我的做法是先把克制的性能基线定下来列表页在主力中端机上滚动不掉帧图片加载不白屏内存峰值在可控范围内。有了这条基线后续加功能时才不会把页面拖垮。提醒不要只拿 iOS 模拟器或者高端 Android 真机做全部验证。模拟器不代表真实渲染性能高端机也掩盖了很多问题。这类 App 的用户设备分布很分散低端机的表现才是真实口碑。最后说回 UI 鉴赏这件事AFAN 这组基于 Flutter 的追番看漫 App 界面放在一堆 UI 稿里不是最抢眼的但它是少数会让你停下来想“为什么这样排”的那一种。它的启示不在某一个渐变、某一处圆角而在于始终把“继续阅读”“继续追番”放在界面最顺手的路径上。这个判断本身就是产品思维只是借 UI 表达出来了。如果你也想做类似的项目先不要急着铺视觉。我的建议是把首页“继续阅读”区域和阅读器进度记录这两个核心功能先做透其他页面可以慢慢补。因为一个追番看漫 App 会让用户留下来的理由从来不是壁纸级别的封面墙而是每次打开它都恰好知道自己该从哪里继续。