Flutter之滚动数字动画 widget

介绍一下:
该组件是显示类似在线人数或累计人数可滚动式的 Animation Widget
可以通过 Controller 控制里面的数字

没有gif(是懒 ←,←),总之就是一个可以自定义样式的数字,数字变化时会上下滚动

多的不说,先上 code。


import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class DigitCounterController extends ValueNotifier<int>{
  DigitCounterController(int initialValue) : super(initialValue);
  
  bool _dispose = false;

  @override
  void dispose() {
    _dispose = true;
    super.dispose();
  }


  void setValue(int newValue){
    if (!_dispose) {
      value += newValue;
    }
  }

  void resetValue(int newValue){
    if (!_dispose) {
      value = newValue;
    }
  }
} 

class MultipleDigitCounter extends StatefulWidget {

  final DigitCounterController controller;
  final TextStyle textStyle;
  final BoxDecoration boxDecoration;

  MultipleDigitCounter({Key key, this.textStyle, this.controller, this.boxDecoration,}): super(key: key);

  @override
  MultipleDigitCounterState createState() {
    return MultipleDigitCounterState();
  } 
    
}

class MultipleDigitCounterState extends State<MultipleDigitCounter> {

  int get digits => _value.toString().length;

  final List<SingleDigitCounter> animatedDigits = [];

  int _value;
  String _oldValue;
  int get value => _value;
  set value(int newValue) {
    _oldValue = value.toString();
    _value = newValue;

    if (mounted) {
      setState(() {});
    }
  }
  String getValueAsString() => _value.toString();

  @override
  void initState() { 
    super.initState();
    _oldValue = _value.toString();
    widget.controller.addListener(_onListenChangeValue);
  }

  @override
  void dispose() { 
    widget.controller.removeListener(_onListenChangeValue);
    super.dispose();
  }

  void _onListenChangeValue(){
    value = widget.controller.value;
  }

  @override
  Widget build(BuildContext context) {
    String newValue = getValueAsString();
    if (newValue.length == animatedDigits.length ) {
      for (var i = 0; i < newValue.length; i++) {
        if (i < _oldValue.length) {
          final old = _oldValue[i];
          final curr = newValue[i];
          if (old != curr) {
            final sdsKey = animatedDigits[i].key as GlobalKey<_SingleDigitCounterState>;
            sdsKey.currentState.setValue(int.parse(curr));
          }
        }
      }
    }
    else{
      animatedDigits.clear();
      for (var i = 0; i < newValue.length; i++) {
        var initialDigit = int.parse(newValue[i]);
        if (i < _oldValue.length) {
          final String old = _oldValue[i];
          final String curr = newValue[i];
          initialDigit = int.parse(old != curr ? curr : old);
          animatedDigits.add(
            SingleDigitCounter(
              initialValue: initialDigit, 
              textStyle: widget.textStyle, 
              boxDecoration: widget.boxDecoration,
            )
          );
        }
        else{
          animatedDigits.add(
            SingleDigitCounter(
              initialValue: initialDigit, 
              textStyle: widget.textStyle, 
              boxDecoration: widget.boxDecoration,
            )
          );
        }
      }
    }
    
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: animatedDigits
    );
    
  }
}


class SingleDigitCounter extends StatefulWidget {

  final TextStyle textStyle;
  final BoxDecoration boxDecoration;
  final int initialValue;


  SingleDigitCounter({ 
    this.boxDecoration: const BoxDecoration(color: Colors.black), 
    this.textStyle: const TextStyle(color: Colors.grey, fontSize: 30),
    this.initialValue: 0
  }): super(key: GlobalKey<_SingleDigitCounterState>());
  
  @override
  State<StatefulWidget> createState() {
    return _SingleDigitCounterState();
  }

}

class _SingleDigitCounterState extends State<SingleDigitCounter> {

  TextStyle get _textStyle => widget.textStyle;
  BoxDecoration get _boxDecoration => widget.boxDecoration;

  Size digitSize;
  int currentValue;

  ScrollController scrollController = ScrollController();

  @override
  void initState() {
    currentValue = widget.initialValue
    digitSize = _getSingleDigitCounterSize();
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      animateTo();
    });
  }


  void setValue(newValue) {
    _setValue(newValue);
  }

  void animateTo() {
    scrollController.animateTo(currentValue * digitSize.height, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut);
  }

  @override
  void dispose() {
    scrollController.dispose();
    super.dispose();
  }

  _setValue(int newValue) {
    this.currentValue = newValue;
    animateTo();
  }

  @override
  Widget build(BuildContext context) {
    return AbsorbPointer(
      absorbing: true,
      child: Container(
        width: digitSize.width, height: digitSize.height,
        decoration: _boxDecoration,
        child: ListView(
          controller: scrollController,
          padding: EdgeInsets.zero,
          children: <Widget>[
            for (var i = 0; i < 10; i++)
              SizedBox.fromSize(
                size: digitSize,
                child: Center(child: Text(i.toString(), style: _textStyle)),
              )
          ],
        ),
      ),
    );
  }

  Size _getSingleDigitCounterSize() {
    TextPainter painter = TextPainter(
      textDirection: TextDirection.ltr,
      text: TextSpan(
        text: "0",
        style: _textStyle
      )
    );
    painter.layout();
    return painter.size;
  }
}

使用示例代码:

  1. 声明一个数字控制器
final DigitCounterController controller = DigitCounterController(0); // 0 是初始值
  1. 布局
// 布局
@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(
      child: Center(
        child: MultipleDigitCounter(
          controller: controller,
          textStyle: TextStyle(fontSize: 26, color: Color(0xFFFB8C2E))
        )
      )
    )
  );
}
  1. 控制器 api
//原有基础上累加数字
controller.setValue(520);

//重置数字
controller.resetValue(1314);

<・)))><<
以上代码可以随意 copy, 吃瓜~

祝大家开心每一天()

上一篇:实现一个 mini-vue


下一篇:4.24observer中并不会出现类似obj.data.name读取时,obj的data与data的name都出现被读取的现象。(改正错误!)