我有一个 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 包的问题,但我查看了源代码,但无法理解导致此错误的原因。
我最近也偶然发现了这个问题。 当动画仍在进行时切换选项卡时,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,
);
我在使用 _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();
}