AnimationController.stop() 在 AnimationController.dispose() 之后调用 - 在 Flutter 上使用 ScaleTap + TabBar 时

问题描述 投票:0回答:2

我有一个 UI,每个屏幕上都有一个 TabBar 和一个按钮(我使用 ScaleTap 包作为按钮https://pub.dev/packages/flutter_scale_tap

我注意到,当我快速滑动 TabBar 上的页面时,出现以下错误:

AnimationController.stop() called after AnimationController.dispose() AnimationController methods should not be used after calling dispose. 'package:flutter/src/animation/animation_controller.dart': Failed assertion: line 772 pos 7: '_ticker != null'

我无法探究到底。该错误并不容易复制,因为您必须非常快地滑动才能发生它(仅当我用三个手指滑动以快速跳过 TabBar 页面时才会发生)。这似乎实际上并没有影响应用程序的实际使用,但我很好奇为什么会发生这种情况以及是否有办法修复错误。

对这里发生的事情有什么想法吗?

`

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_scale_tap/flutter_scale_tap.dart';

class Events extends StatefulWidget {
  const Events({Key? key}) : super(key: key);

  @override
  _EventsState createState() => _EventsState();
}

class _EventsState extends State<Events> {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
          body: AnnotatedRegion<SystemUiOverlayStyle>(
            value: SystemUiOverlayStyle.dark,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const SizedBox(height: 60.0,),
                const Padding(
                  padding: EdgeInsets.only(left: 31.0),
                  child: Text(
                    'My page',
                    style: TextStyle(
                      fontSize: 22,
                      color: Color(0xff101010),
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                ),
                const TabBar(
                  indicatorColor: Color(0xFF101010),
                  labelColor: Color(0xFF101010),
                  unselectedLabelColor: Color(0xFF7E7E7E),
                  indicatorSize: TabBarIndicatorSize.label,
                  indicatorWeight: 1.0,
                  // labelPadding: EdgeInsets.all(0),
                  padding: EdgeInsets.only(top: 20.0, bottom: 5.0),
                  indicatorPadding: EdgeInsets.only(bottom: 8.0),
                  tabs: [
                    Tab(
                      child: Text(
                        "1",
                        style: TextStyle(
                          fontSize: 16,
                        ),
                      ),
                    ),
                    Tab(
                      child: Text(
                        "2",
                        style: TextStyle(
                          fontSize: 16,
                        ),
                      ),
                    ),
                    Tab(
                      child: Text(
                        "3",
                        style: TextStyle(
                          fontSize: 16,
                        ),
                      ),
                    ),
                  ],
                ),
                Expanded(
                  child: TabBarView(
                    children: [
                      SizedBox(
                        child: Center(
                          child: ScaleTap(
                            onPressed: () {},
                            child: Container(
                              width: 200.0,
                              height: 300.0,
                              color: Colors.red,
                            ),
                          ),
                        ),
                      ),
                      SizedBox(
                        child: Center(
                          child: ScaleTap(
                            onPressed: () {},
                            child: Container(
                              width: 200.0,
                              height: 300.0,
                              color: Colors.red,
                            ),
                          ),
                        ),
                      ),
                      SizedBox(
                        child: Center(
                          child: ScaleTap(
                            onPressed: () {},
                            child: Container(
                              width: 200.0,
                              height: 300.0,
                              color: Colors.red,
                            ),
                          ),
                        ),
                      ),
                    ]
                  ),
                ),
              ],
            ),
          ),
      ),
    );
  }
}

`

我猜测这是 ScaleTap 包的问题,但我查看了源代码,但无法理解导致此错误的原因。

flutter flutter-dependencies flutter-animation flutter-test
2个回答
1
投票

我最近也偶然发现了这个问题。 当动画仍在进行时切换选项卡时,AnimationController 将被释放。我复制了 ScaleTap 类的源代码并做了一些更改。

我添加了一个布尔值“isDispose”,默认为 false;在 dispose() 方法中,我将该 bool 设置为 false;最后,如果 isDispose 为 false,我将返回一个空的 Future,而不是调用 anim() 方法。

您可以查看下面的代码。我在添加的行旁边添加了注释。

干杯!

library flutter_scale_tap;

import 'package:flutter/physics.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

const double _DEFAULT_SCALE_MIN_VALUE = 0.95;
const double _DEFAULT_OPACITY_MIN_VALUE = 0.90;
final Curve _DEFAULT_SCALE_CURVE = CurveSpring(); // ignore: non_constant_identifier_names
const Curve _DEFAULT_OPACITY_CURVE = Curves.ease;
const Duration _DEFAULT_DURATION = Duration(milliseconds: 300);

class ScaleTapConfig {
  static double? scaleMinValue;
  static Curve? scaleCurve;
  static double? opacityMinValue;
  static Curve? opacityCurve;
  static Duration? duration;
}

class ScaleTap extends StatefulWidget {
  final Function()? onPressed;
  final Function()? onLongPress;
  final Widget? child;
  final Duration? duration;
  final double? scaleMinValue;
  final Curve? scaleCurve;
  final Curve? opacityCurve;
  final double? opacityMinValue;
  final bool enableFeedback;

  ScaleTap({
    this.enableFeedback = true,
    this.onPressed,
    this.onLongPress,
    required this.child,
    this.duration,
    this.scaleMinValue,
    this.opacityMinValue,
    this.scaleCurve,
    this.opacityCurve,
  });

  @override
  _ScaleTapState createState() => _ScaleTapState();
}

class _ScaleTapState extends State<ScaleTap> with SingleTickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _scale;
  late Animation<double> _opacity;

  bool isDisposed = false; // Added this line

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(vsync: this);
    _scale = Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
    _opacity = Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
  }

  @override
  void dispose() {
    isDisposed = true; /// Added this line
    _animationController.dispose();
    super.dispose();
  }

  Future<void> anim({double? scale, double? opacity, Duration? duration}) {
    _animationController.stop();
    _animationController.duration = duration ?? Duration.zero;

    _scale = Tween<double>(
      begin: _scale.value,
      end: scale,
    ).animate(CurvedAnimation(
      curve: widget.scaleCurve ?? ScaleTapConfig.scaleCurve ?? _DEFAULT_SCALE_CURVE,
      parent: _animationController,
    ));
    _opacity = Tween<double>(
      begin: _opacity.value,
      end: opacity,
    ).animate(CurvedAnimation(
      curve: widget.opacityCurve ?? ScaleTapConfig.opacityCurve ?? _DEFAULT_OPACITY_CURVE,
      parent: _animationController,
    ));
    _animationController.reset();
    return _animationController.forward();
  }

  Future<void> _onTapDown(_) {
    if(isDisposed) return Future<void>(() {}); // Added this line
    return anim(
      scale: widget.scaleMinValue ?? ScaleTapConfig.scaleMinValue ?? _DEFAULT_SCALE_MIN_VALUE,
      opacity: widget.opacityMinValue ?? ScaleTapConfig.opacityMinValue ?? _DEFAULT_OPACITY_MIN_VALUE,
      duration: widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
    );
  }

  Future<void> _onTapUp(_) {
    if(isDisposed) return Future<void>(() {}); // Added this line
    return anim(
      scale: 1.0,
      opacity: 1.0,
      duration: widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
    );
  }

  Future<void> _onTapCancel(_) {
    return _onTapUp(_);
  }

  @override
  Widget build(BuildContext context) {
    final bool isTapEnabled = widget.onPressed != null || widget.onLongPress != null;

    return AnimatedBuilder(
      animation: _animationController,
      builder: (_, Widget? child) {
        return Opacity(
          opacity: _opacity.value,
          child: Transform.scale(
            alignment: Alignment.center,
            scale: _scale.value,
            child: child,
          ),
        );
      },
      child: Listener(
        onPointerDown: isTapEnabled ? _onTapDown : null,
        onPointerCancel: _onTapCancel,
        onPointerUp: _onTapUp,
        child: GestureDetector(
          onTap: isTapEnabled
              ? () {
                  if (widget.enableFeedback) {
                    SystemSound.play(SystemSoundType.click);
                  }
                  widget.onPressed?.call();
                }
              : null,
          onLongPress: isTapEnabled ? widget.onLongPress : null,
          child: widget.child,
        ),
      ),
    );
  }
}

class CurveSpring extends Curve {
  final SpringSimulation sim;

  CurveSpring() : this.sim = _sim(70, 20);

  @override
  double transform(double t) => sim.x(t) + t * (1 - sim.x(1.0));
}

_sim(double stiffness, double damping) => SpringSimulation(
      SpringDescription.withDampingRatio(
        mass: 1,
        stiffness: stiffness,
        ratio: 0.7,
      ),
      0.0,
      1.0,
      0.0,
    );

0
投票

我在使用 _animationController.repeat() 时发生了这种情况。只需添加“已安装”检查即可解决数据。

class _LoadingPercentScreenState extends State<LoadingPercentScreen>
        with SingleTickerProviderStateMixin {
      late AnimationController _controller;

    @override
      void initState() {
        super.initState();
        _controller = AnimationController(
            vsync: this, duration: const Duration(milliseconds: 1000));
        if (mounted) _controller.repeat();

  }
@override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
© www.soinside.com 2019 - 2024. All rights reserved.