flutter数量计步器源码分析

307 阅读3分钟

这是一个用于实现自定义数值增减 Stepper 的Flutter组件,它支持三种样式:system、outlined和textfield。其中,system和outlined样式分别使用IconButton实现增减按钮,而textfield样式使用TextField实现数值输入和增减按钮。

图片.png

在使用该组件时,需要传入一些必要的参数,包括最小值、最大值、步长、初始值和一个回调函数,用于处理数值变化事件。根据传入的样式类型,该组件会渲染出不同的界面,并且在用户点击增减按钮或者输入框中输入数值时,会根据步长和边界条件计算出新的数值,并且通过回调函数将新的数值传递给父组件进行处理。

在该组件的实现过程中,使用了TextEditingController和FocusNode来控制输入框中的数值以及输入框的聚焦状态。同时,使用了setState方法来更新界面上的数值和按钮状态。

如果您需要使用该组件,可以根据自己的需求选择不同的样式,同时通过传入必要的参数和回调函数来实现相应的功能

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/extensions/ddlog.dart';
 
enum NumberStepperStyle {
system,
outlined,
textfield,
}
 
///自定义数值增减 Stepper
class NumberStepper extends StatefulWidget {
  NumberStepper({
    required this.minValue,
    required this.maxValue,
    required this.stepValue,
    this.iconSize = 24,
    required this.value,
    this.color = Colors.blue,
    this.style = NumberStepperStyle.system,
    this.radius = 5.0,
    this.wraps = true,
    required this.block,
  });
 
  final int minValue;
  final int maxValue;
  final int stepValue;
  final double iconSize;
  int value;
 
  final bool wraps;
 
  final Color color;
  final NumberStepperStyle style;
  final double radius;
  void Function(int value) block;
 
 
  @override
  _NumberStepperState createState() => _NumberStepperState();
}
 
class _NumberStepperState extends State<NumberStepper> {
 
  // 控制器
  final _textController = TextEditingController();
  // 焦点
  final focusNode1 = FocusNode();
 
  @override
  void initState() {
    // TODO: implement initState
 
    _textController.text = "${widget.value}";
 
    ddlog(_textController.text);
    super.initState();
  }
 
  @override
  Widget build(BuildContext context) {
    // return buildOther(context);
    switch (widget.style) {
      case NumberStepperStyle.outlined:
        return buildOutlinedStyle(context);
        break;
      case NumberStepperStyle.textfield:
        return buildTexfieldStyle(context);
      default:
        break;
    }
    return buildSystemStyle(context);
  }
 
  Widget buildSystemStyle(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          width: widget.iconSize,
          height: widget.iconSize,
          decoration: BoxDecoration(
            color: widget.color,
            borderRadius: BorderRadius.circular(widget.radius),
            border: Border.all(color: widget.color, width: 1), // 边色与边宽度
          ),
          child: IconButton(
            icon: Icon(Icons.remove, size: widget.iconSize),
            // iconSize: widget.iconSize,
            padding: EdgeInsets.zero,
            color: Colors.white,
            onPressed: () {
              go(-widget.stepValue);
            },
 
          ),
        ),
 
        Container(
          width: widget.value.toString().length*18*widget.iconSize/30,
          // width: widget.iconSize + 20,
          child: Text('${widget.value}',
            style: TextStyle(
              fontSize: widget.iconSize * 0.8,
            ),
            textAlign: TextAlign.center,
          ),
        ),
 
        Container(
          width: widget.iconSize,
          height: widget.iconSize,
          decoration: BoxDecoration(
            color: widget.color,
            borderRadius: BorderRadius.circular(widget.radius),
            border: Border.all(color: widget.color, width: 1), // 边色与边宽度
          ),
          child: IconButton(
            icon: Icon(Icons.add, size: widget.iconSize,),
            // iconSize: widget.iconSize,
            padding: EdgeInsets.zero,
            color: Colors.white,
            onPressed: () {
              setState(() {
                go(widget.stepValue);
              });
            },
          ),
        ),
      ],
    );
  }
 
  Widget buildOutlinedStyle(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          width: widget.iconSize,
          height: widget.iconSize,
          // color: Theme.of(context).primaryColor,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(widget.radius),
            border: Border.all(color: widget.color, width: 1), // 边色与边宽度
          ),
          child: IconButton(
            icon: Icon(Icons.remove, size: widget.iconSize),
            // iconSize: widget.iconSize,
            padding: EdgeInsets.zero,
            color: widget.color,
            onPressed: () {
              go(-widget.stepValue);
            },
          ),
        ),
 
        Container(
          width: widget.value.toString().length*18*widget.iconSize/30,
          // width: widget.iconSize + 20,
          child: Text('${widget.value}',
            style: TextStyle(
              fontSize: widget.iconSize * 0.8,
            ),
            textAlign: TextAlign.center,
          ),
        ),
 
        Container(
          width: widget.iconSize,
          height: widget.iconSize,
          // color: Theme.of(context).primaryColor,
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(widget.radius),
            border: Border.all(color: widget.color, width: 1), // 边色与边宽度
          ),
          child: IconButton(
            icon: Icon(Icons.add, size: widget.iconSize),
            // iconSize: widget.iconSize,
            padding: EdgeInsets.zero,
            color: widget.color,
            onPressed: () {
              setState(() {
                go(widget.stepValue);
              });
            },
          ),
        ),
      ],
    );
  }
 
  Widget buildTexfieldStyle(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Expanded(
          child: TextField(
            enableInteractiveSelection: false,
            toolbarOptions: ToolbarOptions(
              copy:false,
              paste: false,
              cut: false,
              selectAll: false,
              //by default all are disabled 'false'
            ),
            controller: _textController,
            decoration: InputDecoration(
                // labelText: "请输入内容",//输入框内无文字时提示内容,有内容时会自动浮在内容上方
                // helperText: "随便输入文字或数字", //输入框底部辅助性说明文字
                prefixIcon:IconButton(
                  icon: Icon(
                    Icons.remove,
                    size: widget.iconSize,
                  ),
                  onPressed: (){
                    // go(-widget.stepValue);
                    setState(() {
                      go(-widget.stepValue);
                      _textController.text = "${widget.value}";
                    });
                  },
                ),
                border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(4.0) //圆角大小
                ),
                suffixIcon: IconButton(
                  icon: Icon(
                    Icons.add,
                    size: widget.iconSize,
                  ),
                  onPressed: (){
                    // go(widget.stepValue);
                    setState(() {
                      // FocusScope.of(context).requestFocus(FocusNode());
                      go(widget.stepValue);
                      _textController.text = "${widget.value}";
                    });
                  },
                ),
                contentPadding: const EdgeInsets.only(bottom:8)
            ),
            keyboardType: TextInputType.number,
          ),
        ),
    ],
    );
  }
 
  void go(int stepValue) {
    setState(() {
      if (stepValue < 0 && (widget.value == widget.minValue || widget.value + stepValue < widget.minValue)) {
        ddlog("it's minValue!");
        if (widget.wraps) widget.value = widget.maxValue;
        widget.block(widget.value);
        return;
      }
      if (stepValue > 0 && (widget.value == widget.maxValue || widget.value + stepValue > widget.maxValue)) {
        ddlog("it's maxValue!");
        if (widget.wraps) widget.value = widget.minValue;
        widget.block(widget.value);
        return;
      }
      widget.value += stepValue;
    });
    widget.block(widget.value);
  }
}

首先,这是一个用于实现自定义数值增减 Stepper 的Flutter组件。它支持三种样式:system、outlined和textfield。在使用该组件时,需要传入一些必要的参数,包括最小值、最大值、步长、初始值和一个回调函数,用于处理数值变化事件。

接下来,让我们来看看这段代码的具体实现细节。

enum NumberStepperStyle {
  system,
  outlined,
  textfield,
}

首先定义了一个 NumberStepperStyle 枚举类型,用于表示组件的样式类型。

class NumberStepper extends StatefulWidget {
  NumberStepper({
    required this.minValue,
    required this.maxValue,
    required this.stepValue,
    this.iconSize = 24,
    required this.value,
    this.color = Colors.blue,
    this.style = NumberStepperStyle.system,
    this.radius = 5.0,
    this.wraps = true,
    required this.block,
  });
  
  // ...
}

接着定义了一个 NumberStepper 组件类,它继承自 StatefulWidget 类。在该组件的构造函数中,我们传入了一些必要的参数,包括最小值、最大值、步长、初始值、颜色、样式类型等,并且定义了一个回调函数 block,用于处理数值变化事件。

class _NumberStepperState extends State<NumberStepper> {
  // 控制器
  final _textController = TextEditingController();
  // 焦点
  final focusNode1 = FocusNode();
 
  @override
  void initState() {
    // TODO: implement initState
 
    _textController.text = "${widget.value}";
 
    ddlog(_textController.text);
    super.initState();
  }
  
  // ...
}

然后,在 NumberStepper 类中定义了一个 _NumberStepperState 私有类,用于管理组件的状态。在该类中,我们定义了一个控制器 _textController 和一个焦点 focusNode1,用于控制输入框中的数值以及输入框的聚焦状态。在 initState 方法中,我们将初始值设置为输入框中的默认值。

@override
Widget build(BuildContext context) {
  switch (widget.style) {
    case NumberStepperStyle.outlined:
      return buildOutlinedStyle(context);
      break;
    case NumberStepperStyle.textfield:
      return buildTexfieldStyle(context);
    default:
      break;
  }
  return buildSystemStyle(context);
}

在 build 方法中,根据传入的样式类型,选择不同的样式进行渲染。

void go(int stepValue) {
  setState(() {
    if (stepValue < 0 && (widget.value == widget.minValue || widget.value + stepValue < widget.minValue)) {
      ddlog("it's minValue!");
      if (widget.wraps) widget.value = widget.maxValue;
      widget.block(widget.value);
      return;
    }
    if (stepValue > 0 && (widget.value == widget.maxValue || widget.value + stepValue > widget.maxValue)) {
      ddlog("it's maxValue!");
      if (widget.wraps) widget.value = widget.minValue;
      widget.block(widget.value);
      return;
    }
    widget.value += stepValue;
  });
  widget.block(widget.value);
}

最后,在 go 方法中,根据步长和边界条件计算出新的数值,并且通过回调函数将新的数值传递给父组件进行处理。

void go(int stepValue) {
  setState(() {
    // 判断是否到达最小值,如果是,且设置了 wraps 属性为 true,则将值设置为最大值
    if (stepValue < 0 && (widget.value == widget.minValue || widget.value + stepValue < widget.minValue)) {
      ddlog("it's minValue!");
      if (widget.wraps) widget.value = widget.maxValue;
      widget.block(widget.value);
      return; // 直接结束函数
    }
    // 判断是否到达最大值,如果是,且设置了 wraps 属性为 true,则将值设置为最小值
    if (stepValue > 0 && (widget.value == widget.maxValue || widget.value + stepValue > widget.maxValue)) {
      ddlog("it's maxValue!");
      if (widget.wraps) widget.value = widget.minValue;
      widget.block(widget.value);
      return; // 直接结束函数
    }
    // 将当前值加上步长
    widget.value += stepValue;
  });
  // 调用 block 函数通知外部组件当前的值已经发生了变化
  widget.block(widget.value);
}

以上就是该组件的主要实现逻辑和代码结构。