自定义单选按钮

一个可动画的单选按钮,可以最大程度地进行自定义。

我发现 Flutter 只提供两个单选按钮小部件:Radio 和 RadioListTile 令人奇怪。这两个小部件的主要问题是它们都强制使用了默认的 Android 开头动画圆形图标。该小部件通过允许用户提供自己的构建器函数来处理所有内容。此外,还可以提供动画构建器。这会传递一个父级动画控制器,用户可以使用该控制器来创建一系列动画,以动画化小部件在状态之间的过渡。

安装

只需在 pubspec.yaml 文件中将 custom_radio: ^0.1.2 添加为依赖项。
然后,在需要它的任何地方 import 'package:custom_radio/custom_radio.dart';

示例

example-1

如果只需要一种动画类型,则可以将其指定为启用更强的类型。

CustomRadio<String, double>(
  value: 'First',
  groupValue: widget.radioValue,
  duration: Duration(milliseconds: 500),
  animsBuilder: (AnimationController controller) => [
    CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOut
    )
  ],
  builder: (BuildContext context, List<double> animValues, Function updateState, String value) {
    final alpha = (animValues[0] * 255).toInt();
    return GestureDetector(
      onTap: () {
        setState(() {
          widget.radioValue = value;
        });
      },
      child: Container(
        padding: EdgeInsets.all(32.0),
        margin: EdgeInsets.all(12.0),
        alignment: Alignment.center,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Theme.of(context).primaryColor.withAlpha(alpha),
          border: Border.all(
            color: Theme.of(context).primaryColor.withAlpha(255 - alpha),
            width: 4.0,
          )
        ),
        child: Text(
          value,
          style: Theme.of(context).textTheme.body1.copyWith(fontSize: 24.0),
        )
      )
    );
  }
)

simple_example

但任何动画类型的组合都得到支持。

CustomRadio<String, dynamic>(
  value: 'First',
  groupValue: widget.radioValue,
  animsBuilder: (AnimationController controller) => [
    CurvedAnimation(
      parent: controller,
      curve: Curves.easeInOut
    ),
    ColorTween(
      begin: Colors.white,
      end: Colors.deepPurple
    ).animate(controller),
    ColorTween(
      begin: Colors.deepPurple,
      end: Colors.white
    ).animate(controller),
  ],
  builder: (BuildContext context, List<dynamic> animValues, Function updateState, String value) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widget.radioValue = value;
        });
      },
      child: Container(
        alignment: Alignment.center,
        margin: EdgeInsets.all(18.0),
        padding: EdgeInsets.all(32.0 + animValues[0] * 12.0),
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: animValues[1],
          border: Border.all(
            color: animValues[2],
            width: 2.0
          )
        ),
        child: Text(
          value,
          style: Theme.of(context).textTheme.body1.copyWith(
            fontSize: 20.0,
            color: animValues[2]
          ),
        )
      )
    );
  },
)

dynamic_example

您甚至可以重现Radio提供的默认动画,并添加您自己的个性化风格!
注意:完整示例可在 example 目录中找到

CustomRadio<int, double>(
  value: value,
  groupValue: widget.radioValue,
  duration: Duration(milliseconds: 400),
  animsBuilder: (AnimationController controller) => [
    CurvedAnimation(
      parent: controller,
      curve: Curves.ease
    )
  ],
  builder: ({ BuildContext context, List<double> animValues, Function updateState, bool checked }) {
    return GestureDetector(
      onTapDown: (TapDownDetails details) {
        setState(() {
          if (_controller.status != AnimationStatus.completed)
            _controller.forward();
        });
      },
      onTapUp: (TapUpDetails details) {
        setState(() {
          if (_controller.status != AnimationStatus.dismissed)
            _controller.reverse();
        });
      },
      onTap: () {
        setState(() {
          widget.radioValue = value;
        });
      },
      child: Container(
        margin: EdgeInsets.all(8.0),
        width: 38.0,
        height: 38.0,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
        ),
        child: Stack(
          alignment: Alignment.center,
          children: <Widget>[
            Container(
              width: 38.0 * _animation.value,
              height: 38.0 * _animation.value,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Theme.of(context).primaryColor.withAlpha(40)
              ),
            ),
            Container(
              width: 18.0,
              height: 18.0,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.transparent,
                border: Border.all(
                  color: checked ? Theme.of(context).primaryColor : Theme.of(context).hintColor,
                  width: 2.0
                )
              ),
            ),
            Container(
              width: 11.0 * animValues[0],
              height: 11.0 * animValues[0],
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Theme.of(context).primaryColor,
              ),
            ),
          ]
        ),
      )
    );
  }
)

radio_clone_example

GitHub

https://github.com/Eternali/custom_radio