查看: 100|回复: 3

Flutter鸿蒙Radio单选按钮:五个场景开发与适配

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在做鸿蒙上的Flutter设置页时,单选按钮(Radio)是绕不开的基础组件。ArkTS中有Radio+RadioGroup,Flutter中也提供Radio和RadioListTile。实际开发中发现,Flutter的Radio组件在鸿蒙上虽然兼容性没问题,但细节处理与ArkTS不同,需要特别注意。

本文基于真实项目,用五个场景拆解Radio的使用,涵盖基础性别选择、颜色选择、支付方式卡片、无默认值尺寸选择和评分组件。所有代码均在nova 12 Ultra(鸿蒙6.0)和Flutter 3.22上验证通过。

先统一展示五种状态变量的声明:
  1. String _gender = 'male'; // 场景一:性别
  2. String _color = 'blue'; // 场景二:颜色
  3. String _payment = 'alipay'; // 场景三:支付方式
  4. String? _size; // 场景四:尺寸(无默认值)
  5. int? _rating = 3; // 场景五:评分
复制代码

Flutter的Radio是泛型组件<T>,三个必传参数:value、groupValue、onChanged。选中逻辑:value==groupValue时显示为选中。与Checkbox不同,Radio没有半选状态。RadioListTile是带文字和布局的版本,支持title、subtitle、secondary(左侧图标)、activeColor等属性,点击区域覆盖整个tile。

ArkTS用RadioGroup容器管理组,子Radio声明value,父子关系天然形成互斥组。Flutter则更扁平——通过将多个Radio共享同一个groupValue变量建立关联,没有显式容器。两种设计各有优劣:ArkTS声明式更直观,Flutter泛型约束更安全(编译期检查类型)。

场景一:基础Radio自定义样式——性别选择
默认Radio仅一个小圆点,太素。我习惯自定义成卡片样式,带图标和文字,选中时加蓝色边框。用GestureDetector包Container,根据选中状态切换背景色和边框。实际并未使用Radio组件,而是模拟单选行为。
  1. Widget _buildRadio(String value, IconData icon, String label) {
  2.   final isSelected = _gender == value;
  3.   return GestureDetector(
  4.     onTap: () => setState(() => _gender = value),
  5.     child: Container(
  6.       padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
  7.       decoration: BoxDecoration(
  8.         color: isSelected ? Colors.blue.shade50 : Colors.grey.shade50,
  9.         borderRadius: BorderRadius.circular(12),
  10.         border: Border.all(
  11.           color: isSelected ? Colors.blue : Colors.grey.shade300,
  12.           width: isSelected ? 2 : 1,
  13.         ),
  14.       ),
  15.       child: Row(
  16.         mainAxisSize: MainAxisSize.min,
  17.         children: [
  18.           Icon(icon, color: isSelected ? Colors.blue : Colors.grey),
  19.           const SizedBox(width: 6),
  20.           Text(label),
  21.         ],
  22.       ),
  23.     ),
  24.   );
  25. }
复制代码
调用时一行一个。这种自定义方式样式可控,但失去标准Radio的无障碍语义,需要时可外包Semantics或改用InkWell加涟漪效果。

场景二:RadioListTile颜色选择
RadioListTile适合设置页,左侧显示色块图标。
  1. ...['blue', 'green', 'orange', 'purple'].map((c) {
  2.   return RadioListTile<String>(
  3.     title: Text(c),
  4.     subtitle: Text('选择 $c 色'),
  5.     secondary: Icon(Icons.palette, color: _parseColor(c)),
  6.     value: c,
  7.     groupValue: _color,
  8.     onChanged: (v) => setState(() => _color = v!),
  9.   );
  10. })
复制代码
注意groupValue类型必须与value一致,编译期检查。鸿蒙上Material Design的涟漪反馈效果正常,体验接近原生。

场景三:自定义卡片Radio——支付方式选择
电商App常见。将支付方式做成卡片,选中时边框变色,Radio圆点颜色跟随。使用Card+RadioListTile组合。
  1. Widget _buildPaymentRadio(
  2.     String value, String label, IconData icon, MaterialColor color) {
  3.   final isSelected = _payment == value;
  4.   return Card(
  5.     margin: const EdgeInsets.only(bottom: 8),
  6.     shape: RoundedRectangleBorder(
  7.       borderRadius: BorderRadius.circular(12),
  8.       side: BorderSide(
  9.         color: isSelected ? color : Colors.grey.shade300,
  10.         width: isSelected ? 2 : 1,
  11.       ),
  12.     ),
  13.     child: RadioListTile<String>(
  14.       value: value,
  15.       groupValue: _payment,
  16.       onChanged: (v) => setState(() => _payment = v!),
  17.       title: Text(label),
  18.       secondary: Icon(icon, color: isSelected ? color : Colors.grey),
  19.       activeColor: color,
  20.     ),
  21.   );
  22. }
复制代码
activeColor控制圆点颜色。Card边框通过shape.side设置,但阴影和边框是两套系统。保持统一elevation,用边框颜色区分选中态更稳妥。

场景四:尺寸选择——无默认值 + 自定义样式
某些场景需要用户主动选择,初始无默认值。如S/M/L/XL尺寸。
  1. String? _size;
  2. Widget _buildSizeRadio(String size) {
  3.   final isSelected = _size == size;
  4.   return GestureDetector(
  5.     onTap: () => setState(() => _size = size),
  6.     child: Container(
  7.       width: 50, height: 50,
  8.       decoration: BoxDecoration(
  9.         color: isSelected ? Colors.teal.shade50 : Colors.grey.shade50,
  10.         borderRadius: BorderRadius.circular(12),
  11.         border: Border.all(
  12.           color: isSelected ? Colors.teal : Colors.grey.shade300,
  13.           width: isSelected ? 2 : 1,
  14.         ),
  15.       ),
  16.       child: Center(child: Text(size, style: TextStyle(...))),
  17.     ),
  18.   );
  19. }
复制代码
UI上通过_size==null显示“请选择尺寸”。注意groupValue也得是String?,否则编译报错。也可以采用空字符串默认值,避免处理nullable。

场景五:评分——Wrap+Radio模拟
评分用星星图标表示程度,而非选项。使用InkWell+Icon模拟,用Wrap布局适应换行。
  1. Wrap(
  2.   spacing: 4,
  3.   children: List.generate(5, (i) {
  4.     final star = i + 1;
  5.     return InkWell(
  6.       onTap: () => setState(() => _rating = star),
  7.       child: Padding(
  8.         padding: const EdgeInsets.all(4),
  9.         child: Icon(
  10.           star <= _rating! ? Icons.star : Icons.star_border,
  11.           color: Colors.amber, size: 36,
  12.         ),
  13.       ),
  14.     );
  15.   }),
  16. )
复制代码
Wrap的spacing控制主轴间距,runSpacing控制交叉轴间距,若换行必须设置。

Radio与Checkbox的选择区分:互斥用Radio,多选用Checkbox。不同场景也可考虑ChoiceChip,更紧凑。

开发中踩过的坑总结:
1. groupValue类型必须严格一致,泛型编译期检查。
2. 裸Radio只有圆点,必须包文字或使用RadioListTile。
3. 默认Radio不支持取消选中,若需要可设toggleable:true,但onChanged会返回null。
4. RadioListTile布局固定,要自定义需手写GestureDetector+Container。
5. onChanged回调参数可空,需解包(v!),但要注意groupValue非null时不会返回null。
6. Wrap设置间距时不要遗漏runSpacing。
7. RadioListTile默认高度56dp,视觉密度紧凑可用visualDensity: VisualDensity.compact。
8. fillColor通过MaterialStateProperty可分别控制选中/未选中颜色。
9. RadioListTile的contentPadding默认左右16dp,调整需手动设置。

在鸿蒙上运行原生的Flutter Material组件,Radio的点击反馈、水波纹动画完全正常,兼容性没问题。但注意鸿蒙上Material 3主题渲染颜色饱和度略低于Android(如activeColor在鸿蒙上偏淡),这是平台差异,代码逻辑无需修改。

以上五个场景覆盖了Radio的主要使用模式:用标准Radio组件(RadioListTile)或模拟Radio(GestureDetector/InkWell)。前者适合列表设置页,后者适合需要视觉差异化或自定义布局的场景。在鸿蒙Flutter开发中,用好Radio组件能显著提升设置页的用户体验。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Flutter鸿蒙Radio单选按钮:五个场景开发与适配

感谢楼主非常详细的分享!这五个场景基本覆盖了实际开发中Radio的常见用法,很有参考价值。 特别赞同你对比Flutter和ArkTS设计差异的部分——Flutter通过泛型groupValue做类型约束确实更安全,编译期就能发现问题,而ArkTS的父子容器写法在简单场景下更直白。你提到的GestureDetector模拟单选并保留Semantics的建议也很实用,很多自定义样式的确需要在灵活性和无障碍之间做平衡。 场景三支付卡片用Card+RadioListTile的组合、边框变色跟随选中状态,这套方案在电商App里很常见,看起来在鸿蒙上表现也不错。另外问一下,在场景五评分组件那块,如果用多颗星替代Radio圆点,鸿蒙上触控响应和动画有没有遇到什么需要特别处理的地方?
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Flutter鸿蒙Radio单选按钮:五个场景开发与适配

楼主这个分享很实用,特别是在鸿蒙上做Flutter开发时,Radio组件虽然兼容但细节确实需要踩坑。五个场景覆盖了大部分设置页的常见需求,尤其是自定义卡片样式和无默认值的处理,对新手很有参考价值。方便问一下,在场景四的“无默认值尺寸选择”里,如果用户没选就直接提交,你是怎么处理校验逻辑的?另外,鸿蒙上Flutter Radio的点击响应延迟或触摸反馈效果,跟原生ArkTS相比有明显差异吗?
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Flutter鸿蒙Radio单选按钮:五个场景开发与适配

感谢楼主分享这么详细的实践!代码清晰,场景也很有代表性,尤其是自定义卡片样式那个思路,在设置页确实比默认RadioListTile更灵活。 我注意到您开头提到了五个场景,但正文里只详细写了场景一(性别)、场景二(颜色选择)和场景三(支付方式),场景四(无默认值尺寸选择)和场景五(评分组件)只显示了变量声明,没有贴出具体实现代码。是忘记了吗?还是这两个场景的实现方式有特殊之处? 另外,您提到自定义样式时失去标准Radio的无障碍语义,可以简单分享一下用Semantics包裹的具体写法吗?在实际鸿蒙设备上,这种自定义的Radio是否能通过无障碍焦点测试?期待您补充后两部分的内容。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-22 15:34 , Processed in 0.024474 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部