You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter技术咨询:如何创建此类自定义单选按钮组

嘿,我来教你怎么在Flutter里做出符合你需求的自定义单选按钮组!其实核心就是灵活利用状态管理和自定义布局,下面给你两种实用方案,你可以根据自己的样式需求来选:

方案一:基于RadioListTile快速改造(适合轻度定制)

如果只是想调整默认单选组的颜色、图标这类细节,用Flutter自带的RadioListTile改造最省事,不用从零搭布局:

import 'package:flutter/material.dart';

class CustomRadioGroup extends StatefulWidget {
  const CustomRadioGroup({super.key});

  @override
  State<CustomRadioGroup> createState() => _CustomRadioGroupState();
}

class _CustomRadioGroupState extends State<CustomRadioGroup> {
  String? selectedOption;
  final List<String> options = ['选项A', '选项B', '选项C', '选项D'];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: options.map((option) {
        return RadioListTile(
          title: Text(option),
          value: option,
          groupValue: selectedOption,
          onChanged: (value) => setState(() => selectedOption = value),
          // 自定义选中时的主色调
          activeColor: Colors.blueAccent,
          // 选中时的背景高亮色
          selectedTileColor: Colors.blueAccent.withOpacity(0.1),
          // 把单选按钮放在文字右边(默认在左边)
          controlAffinity: ListTileControlAffinity.trailing,
          // 替换默认的单选图标
          secondary: selectedOption == option
              ? const Icon(Icons.check_circle, color: Colors.blueAccent)
              : const Icon(Icons.radio_button_unchecked, color: Colors.grey),
        );
      }).toList(),
    );
  }
}

你可以随便调整activeColor、selectedTileColor,甚至替换图标,快速得到符合设计的样式。

方案二:完全自定义单选组(适合高度定制)

如果你的设计要求很独特(比如自定义按钮形状、渐变背景、动画效果),那就得自己从头构建布局,用InkWell或者GestureDetector来处理点击,手动管理选中状态:

import 'package:flutter/material.dart';

class FullyCustomRadioGroup extends StatefulWidget {
  const FullyCustomRadioGroup({super.key});

  @override
  State<FullyCustomRadioGroup> createState() => _FullyCustomRadioGroupState();
}

class _FullyCustomRadioGroupState extends State<FullyCustomRadioGroup> {
  String? selectedValue;
  // 可以存储更多选项信息,比如图标、描述等
  final List<Map<String, dynamic>> options = [
    {'id': 'opt1', 'label': '自定义选项1'},
    {'id': 'opt2', 'label': '自定义选项2'},
    {'id': 'opt3', 'label': '自定义选项3'},
  ];

  @override
  Widget build(BuildContext context) {
    return Column(
      children: options.map((opt) {
        final isSelected = selectedValue == opt['id'];
        return InkWell(
          onTap: () => setState(() => selectedValue = opt['id']),
          borderRadius: BorderRadius.circular(8),
          // 用AnimatedContainer实现平滑过渡动画
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
            margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
            decoration: BoxDecoration(
              color: isSelected ? Colors.purple[100] : Colors.white,
              border: Border.all(
                color: isSelected ? Colors.purple : Colors.grey.shade300,
                width: 2,
              ),
              borderRadius: BorderRadius.circular(8),
              boxShadow: isSelected
                  ? [
                      BoxShadow(
                        color: Colors.purple.withOpacity(0.2),
                        blurRadius: 4,
                        offset: const Offset(0, 2),
                      )
                    ]
                  : null,
            ),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(
                  opt['label'],
                  style: TextStyle(
                    fontSize: 16,
                    color: isSelected ? Colors.purple : Colors.black87,
                    fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
                  ),
                ),
                // 自定义选中标记
                Container(
                  width: 24,
                  height: 24,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: isSelected ? Colors.purple : Colors.transparent,
                    border: Border.all(
                      color: isSelected ? Colors.purple : Colors.grey.shade400,
                      width: 2,
                    ),
                  ),
                  child: isSelected
                      ? const Icon(
                          Icons.check,
                          size: 16,
                          color: Colors.white,
                        )
                      : null,
                ),
              ],
            ),
          ),
        );
      }).toList(),
    );
  }
}

这个方案里你能完全控制每个元素的样式,甚至可以给选中状态加阴影、渐变,或者调整动画时长,完全贴合你的设计图。

如果需要在多个页面共享选中状态,还可以用Provider、Riverpod这类状态管理工具来替代setState,不过上面的代码已经能满足大部分场景啦!

内容的提问来源于stack exchange,提问作者Shyju M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:06:08