我给老爸做了个听广播的 App,零广告,体验贼好
我爸他们圈子中的人有个习惯,每天早上起床第一件事打开收音机,去钓鱼也是听广播,晚上睡觉前也要听着睡,也许是那个年代的人没电视看养成的一些习惯。这个习惯保持了几十年,从收音机换到手机 App,一直没变。
但最近他跟我抱怨:那些 App 越来越难用了。
广告多到点不到播放按钮,字小得看不清,功能藏得深,找个收藏的电台要点好几层。他说他有时候就想简简单单听个中国之声,怎么就这么难。
我说,爸,我给你做一个,这对于我来说,并不是什么难事。


然后花了一天时间想怎么做一个极简的,画了一天时间规划,就真的做了一个。今天聊聊这个过程,踩过的坑,以及我对"适老化设计"这个事的真实理解。
市面上的 App 为什么不行
我调研了一圈,发现一个问题:大多数 App 把"适老化"理解成"把字放大"。
字大一点当然好,但这远远不够。
我总结了几个核心痛点:
说实话,我觉得这些 App 不是做不好,是不想做。因为适老化意味着更少的广告位、更简单的功能、更低的变现空间。商业上不合算。
但我不需要考虑商业。我只有一个用户人群,就是像我爸这样的广播爱好者,极简可用就行。
所以,我的技术选型很明确,够用就好
选 Flutter 不是因为它最牛,是因为它最合适:
• 跨平台,一套代码以后可以支持 iOS • UI 控制力强,适老化设计需要精确控制字体、间距、触控区域 • 生态成熟,音频播放这块已经有现成的解决方案
最终的技术栈:
Flutter 3.35 + Dart 3.9├── just_audio # 音频播放,支持 HLS 直播流├── audio_service # 后台播放 + 锁屏控制 + 通知栏├── provider # 状态管理,简单直接├── hive # 收藏本地存储,无需 SQL└── cached_network_image # Logo 图片缓存没有复杂的架构,没有冗余的抽象,没有 Riverpod、没有 Bloc、没有 Clean Architecture。
够用就好。 一个听广播的 App,不需要那些。
适老化设计,可不是只把字放大
这是我觉得最值得聊的部分。
做这个项目之前我做了深度调研,用了 102 个子智能体,跑了 20 多个来源,提取了 69 条声明,最后通过对抗验证(3 个 skeptics 投票)确认了 8 条核心发现。
这些发现直接影响了我后面的每一个设计决策。
字体缩放要叠加,不能替换
很多人不知道,Flutter 的字体缩放是可以叠加的。
系统设置里的字体缩放是一层,App 内部可以再加一层。两层叠加,字可以变得非常大。
// 系统缩放 × 应用内大字模式final systemScale = mq.textScaler;final appScale = settings.isLargeFontMode ? 1.4 : 1.0;return MediaQuery( data: mq.copyWith( textScaler: TextScaler.linear( systemScale.scale(1.0) * appScale ), ), child: child!,);我爸在系统设置里把字体调到最大,App 里再开"大字模式",字就变得很大很大。他说看得清楚,这就够了。
注意,这里是叠加不是替换。如果用户在系统里已经调大了字体,App 不应该把他的设置覆盖掉,而是在那个基础上再放大。
触控区域必须够大
WCAG 2.2 标准对触控目标有明确要求:
• Level AA:最小 24×24 CSS 像素 • Level AAA:最小 44×44 CSS 像素 • Android 官方推荐:48×48 dp
我在 Flutter 里全局设置了 MaterialTapTargetSize.padded,所有按钮、列表项、图标都保证触控区域 ≥ 48dp。
播放/暂停按钮做到了 80dp。老人手指不灵活,有时候点不准,大一点总没错。
对比度要过关
所有文字和背景的对比度都过了 WCAG AA 标准(≥ 4.5:1):
class AppColors{ static const primaryText = Color(0xFF1A1A1A); // 在白色上 ~16:1 static const secondaryText = Color(0xFF4A4A4A); // 在白色上 ~9:1 static const primary = Color(0xFFB71C1C); // 深红,在白色上 ~7:1 static const onPrimary = Color(0xFFFFFFFF); // 白色在深红上 ~7:1}这个没什么好说的,就是死磕数字。
状态不能只靠颜色传达
这一点我觉得很多 App 都忽略了。
色盲、色弱的用户很多,老人视力下降,对颜色的敏感度也在降低。如果你只用颜色区分状态,他们可能真的看不出来。
我的做法是"图标 + 文字 + 颜色"三件套:
• 播放中:🎵 图标 + "播放中" 文字 + 绿色 • 加载中:⏳ 图标 + "加载中…" 文字 + 黄色 • 已暂停:⏸ 图标 + "已暂停" 文字 + 灰色
颜色是辅助信息,不是唯一信息。即使看不到颜色,也能通过图标和文字理解当前状态。
核心功能的设计,简单但不简陋
整个 App 就 3 个 Tab:电台、收藏、设置。
电台列表
6 个分类(国家台、地方台、音乐台、新闻台、交通台、故事评书),33 个电台,我把他听过的几乎全部搬迁进来了,全部带 Logo。
横向滑动的分类筛选芯片,点击切换,不需要下拉菜单。列表里每个电台是一个卡片,左侧 Logo,中间电台名和描述,右侧收藏按钮。
点击卡片直接播放,没有多余的步骤。
全屏播放器
播放器界面只有三个核心按钮:收藏、播放/暂停、停止。
没有进度条,因为直播流不需要。没有倍速播放,因为广播不需要。没有复杂的设置,就是纯粹地听。
锁屏和通知栏也能控制播放,这个靠 audio_service 自动搞定:
mediaItem.add(MediaItem( id: station.id, title: station.name, album: station.fullName, artUri: station.logoUrl != null ? Uri.parse(station.logoUrl!) : null,));锁屏状态下能看到电台 Logo、电台名、播放/暂停按钮。
收藏功能
用 Hive 做本地存储,收藏的电台可以一键直达,这里考虑也可以使用简单的 lru,做一个 rank,让畅听的自动收藏,然后排序。
这个完全没有账号系统,没有云同步,就是本地存储。简单可靠。
定时关闭
他们这些人听广播经常听着听着就睡了,定时关闭是刚需。
所以,我设计了一个底部弹窗,4 个快捷按钮(15/30/45/60 分钟),还有滚轮选择自定义时间,考虑要不啊,直接加多几个按钮也许最省事。
这里有个细节:滚轮滑动只记录值,必须点"确认"按钮才生效。 因为 CupertinoPicker 的 onSelectedItemChanged 在触摸时就触发,如果碰到就启动定时,老人很容易误操作。
设定定时后,全局顶部会显示一个橙色横幅,实时倒计时。在任何页面都能看到,不会"莫名其妙就关了还不知道"。
说说我踩过的坑
坑 1:电台直播流 URL 失效
这是最头疼的问题。
一开始我用的 live-play.cctvnews.cctv.com 域名,全部返回 403。后来换成 ngcdn*.cnr.cn,只有中国之声和经济之声能用,其他限国内 IP。
最后找到了两个可用的 CDN:
• satellitepull.cnr.cn:CNR 的中转 CDN,省级台都能用• live.fanmingming.com:开源社区维护的电台 CDN,Logo 也从这里拿
33 个电台全部可用。
教训:直播流地址会变,最好做成远程配置,而不是硬编码。 但这个 MVP 版本先这样,以后再做远程配置。
坑 2:Android HTTP 明文流量被拦截
Android 9 以上默认禁止 HTTP 明文流量,而很多电台源是 http:// 不是 https://。
报错信息是 CleartextNotPermittedException。
解决方法很简单,在 AndroidManifest.xml 的 <application> 标签加一行:
<application android:usesCleartextTraffic="true">坑 3:滚轮选择器"碰到就选中"
CupertinoPicker 的 onSelectedItemChanged 在触摸时就触发,不是滑动结束后。
如果直接用这个回调启动定时器,老人手指碰到滚轮就会启动定时,体验很差。
改成 StatefulWidget,滑动只记录值到 _selectedMinutes,加一个"确认"按钮,点击确认后才启动定时器。
坑 4:锁屏没有电台图标

一开始锁屏媒体卡片只有电台名,没有 Logo。
原因是 MediaItem 缺少 artUri。加上 artUri: Uri.parse(station.logoUrl!) 就解决了。
同时把 AudioServiceConfig 的 preloadArtwork 改成 true,确保 audio_service 主动下载 Logo 图片到通知栏。
性能几何
启动到可交互大概 1 秒(哈哈,传说中的秒启动,因为基本没有任何资源消耗,就是个播放器而已,列表也不用从网络加载本地),列表滚动丝滑,播放延迟在 1-2 秒(直播流正常延迟),多数情况就是秒播放。
内存占用大概 60-80MB,对4 年前的手机压力都不是很大,体验很友好,主要是 flutter 占用内存了一些,其实如果不考虑跨平台,直接纯 Android,肯定是 20M 左右了。
没有启动页广告,没有开屏广告,没有任何广告。没有账号注册,没有登录弹窗,打开就能用。
写在最后
这个项目从调研到完成大概花了一周时间。技术上没有什么高深的东西,用的都是成熟的库和简单的架构。
但我觉得这可能是我做过最有价值的项目之一。
因为目前它的用户只我爸他们那群朋友。我相信后面可能有更多。
他现在每天早上打开这个 App 听中国之声,晚上听着文艺之声入睡。没有广告打扰,没有复杂操作,字够大,按钮够明显,定时关闭也好用。
他们那些老炮儿说好用,这就够了。
有时候我们做技术,追求的不是多牛的实现,多复杂的架构,多优雅的代码。而是真的解决了某个人的某个问题。
话说,他们最近也是在学吹葫芦丝啥的,是不是给他们在这个上面安排上一个五线谱啥的,这样点广播,吹器乐两不误,岂不是精准命中一些人的简单诉求。
如果你家里也有爱听广播的长辈,不妨也给他们做一个。代码在此
https://github.com/coder-brzhang/flutter_radio注意,本项目仅在小张的520 多个人的小群中分享。
夜雨聆风