我正在尝试在Flutter中创建一个登录页面,并希望在进入下一页之前进行动画处理。一切正常,除非这样做(登录时出现错误信息,所以它调用“ failedLoginAnimation”函数,当我在表单中输入正确的信息后)开始动画,我的应用崩溃并显示给我,但是当我进入正确的信息,而在一切正常之前您没有犯错。 PS:我是一个新手,大约要动一个月的动画,甚至更多。
══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (11398): The following assertion was thrown while notifying listeners for AnimationController:
I/flutter (11398): Looking up a deactivated widget's ancestor is unsafe.
I/flutter (11398): At this point the state of the widget's element tree is no longer stable. To safely refer to a
I/flutter (11398): widget's ancestor in its dispose() method, save a reference to the ancestor by calling
I/flutter (11398): inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
I/flutter (11398):
I/flutter (11398): When the exception was thrown, this was the stack:
I/flutter (11398): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3232:9)
I/flutter (11398): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3241:6)
I/flutter (11398): #2 Element.ancestorStateOfType (package:flutter/src/widgets/framework.dart:3289:12)
I/flutter (11398): #3 Navigator.of (package:flutter/src/widgets/navigator.dart:1271:19)
I/flutter (11398): #4 Navigator.popAndPushNamed (package:flutter/src/widgets/navigator.dart:825:22)
I/flutter (11398): #5 StaggerAnimationSignIn.build.<anonymous closure> (package:test/premium/pages/login/loginAnimation.dart:146:25)
I/flutter (11398): #6 _AnimationController&Animation&AnimationEagerListenerMixin&AnimationLocalListenersMixin.notifyListeners (package:flutter/src/animation/listener_helpers.dart:124:19)
I/flutter (11398): #7 AnimationController._tick (package:flutter/src/animation/animation_controller.dart:693:5)
I/flutter (11398): #8 Ticker._tick (package:flutter/src/scheduler/ticker.dart:228:5)
I/flutter (11398): #9 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter (11398): #10 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleBeginFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:906:11)
I/flutter (11398): #11 __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.forEach (dart:collection/runtime/libcompact_hash.dart:370:8)
I/flutter (11398): #12 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleBeginFrame (package:flutter/src/scheduler/binding.dart:904:17)
I/flutter (11398): #13 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleBeginFrame (package:flutter/src/scheduler/binding.dart:834:5)
I/flutter (11398): #14 _invoke1 (dart:ui/hooks.dart:159:13)
I/flutter (11398): #15 _beginFrame (dart:ui/hooks.dart:129:3)
I/flutter (11398):
I/flutter (11398): The AnimationController notifying listeners was:
I/flutter (11398): AnimationController#97d39(⏭ 1.000; paused)
I/flutter (11398): ════════════════════════════════════════════════════════════════════════════════════════════════════
我的登录页面
import 'package:flutter/material.dart';
import 'loginAnimation.dart';
import 'freeAccessAnimation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/animation.dart';
import 'package:test/premium/tools/jsonTools.dart';
import 'dart:async';
import 'package:test/premium/tools/design/color/designColors.dart';
import './Components/PremiumLink.dart';
import './Components/ForgotPasswordLink.dart';
import './Components/Logo.dart';
import './Components/SignInButton.dart';
import './Components/FreeAccessButton.dart';
import 'package:flutter/services.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:test/premium/tools/internet/checkInternet.dart';
import 'package:test/premium/pages/tools/alerts.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:test/premium/variables/variablesApp.dart';
import 'package:test/premium/tools/internet/sendHTTPRequest.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key key}) : super(key: key);
@override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
static bool hasFinishedLogin;
static bool hasFailedLogin;
static String userLoginToken ;
static String tokenFileExist ;
static String backToLogin;
static bool isFirstCallAction;
static bool hasFinishLogAnim;
AnimationController _loginButtonController;
AnimationController _freeButtonController;
static final usernameController = TextEditingController();
static final passwordController = TextEditingController();
static var animationStatusLogin = 0;
static var animationStatusFree = 0;
Future pause(Duration d) => new Future.delayed(d);
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void tokensManagement(){
if(userLoginToken == null && tokenFileExist == null && backToLogin == "true") {
JTools.deleteFileToken().then((e){
if(e == "Token has been deleted"){
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
}
});
}else{
JTools.readDataToken().then((e){
String _token = e;
pause(new Duration(milliseconds: 500)).then((e){
JTools.tokenFileExist().then((value){
tokenFileExist = value.toString();
userLoginToken = _token;
}).then((b){
if(_token != null){
if(userLoginToken.isNotEmpty && tokenFileExist == 'true'){
//TODO: on loading
APIToken.token = _token;
CheckInternet().checkInternet().then((e){
if(e == true){
PostHTTP().sendRequestAPI(APIToken.token, 'membership').then((value){
if(value['status'] == "200"){
String membership = value['membership'];
print(membership);
if(membership == "138" || membership == "136"){
print('User is premium');
Navigator.of(context).pushNamed('/main');
}else {
print("User isn't premium ");
JTools.deleteFileToken().then((e) {
if (e == "Token has been deleted") {
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertNotPremium();
});
}
});
}
}else{
if(value['status'] == "404"){
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertErrorConnectionServer();
});
}
if(value['status'] == "401" || value['status'] == "403")
{print("User isn't premium ");
JTools.deleteFileToken().then((e) {
if (e == "Token has been deleted") {
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertNotPremium();
});
}
});}
}
});
}else{
Alerts(context: context).alertInternet();
failedLoginAnimation();
}
});
}
else{
setState(() {
});
}
}else{
setState(() {
});
}
});
});
});
}
}
@override
void initState() {
super.initState();
_loginButtonController = new AnimationController(
duration: new Duration(milliseconds: 3000), vsync: this);
_freeButtonController = new AnimationController(
duration: new Duration(milliseconds: 3000), vsync: this);
tokensManagement();
animationStatusLogin = 0;
animationStatusFree = 0;
print('init LOGIN');
}
@override
void dispose() {
super.dispose();
_loginButtonController.dispose();
_freeButtonController.dispose();
}
Future<Null> _playAnimationLogin() async {
try {
await _loginButtonController.forward();
} on TickerCanceled {}
}
Future<Null> _playAnimationFree() async {
try {
await _freeButtonController.forward();
await _freeButtonController.reverse();
} on TickerCanceled {}
}
Widget formLogin(){
return new Container(
margin: new EdgeInsets.symmetric(horizontal: 20.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Form(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: DesignColors.animatedLoginColor,
),
),
),
child: new TextFormField(
controller: usernameController,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(
color: Colors.white,
),
decoration: new InputDecoration(
icon: new Icon(
Icons.person_outline,
color: Colors.white,
),
border: InputBorder.none,
hintText: "Email/Username",
hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
contentPadding: const EdgeInsets.only(
top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
),
),
),
new Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: DesignColors.animatedLoginColor,
),
),
),
child: new TextFormField(
controller: passwordController,
obscureText: true,
style: const TextStyle(
color: Colors.white,
),
decoration: new InputDecoration(
icon: new Icon(
Icons.lock_outline,
color: Colors.white,
),
border: InputBorder.none,
hintText: "Password",
hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
contentPadding: const EdgeInsets.only(
top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
),
),
),
],
)),
],
));
}
void failedLoginAnimation(){
setState(() {
hasFailedLogin = true;
_playAnimationLogin();
animationStatusLogin = 0;
});
}
Widget logPage(){
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
backgroundColor: DesignColors.backgroundColor,
body: Center(
child: Theme(
data: ThemeData(splashColor: Colors.transparent, textSelectionHandleColor: Colors.blue, highlightColor: Colors.transparent),
child:new ListView(
shrinkWrap: true,
children: <Widget>[
new Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Logo(image: DecorationImage(image: ExactAssetImage('assets/images/AppLogo.png'), fit: BoxFit.cover),),
formLogin(),
new PremiumLink(action: (){_launchURL("someurl");}),
new ForgotPasswordLink(action: (){_launchURL("someurl");}),
],
),
animationStatusLogin == 0
? new Padding(
padding: const EdgeInsets.only(bottom: 160.0),
child: new InkWell(
onTap: () {
setState(() {
animationStatusLogin = 1;
hasFinishedLogin = false;
hasFinishLogAnim = false;
hasFailedLogin = false;
isFirstCallAction = true;
});
_playAnimationLogin();
},
child: new SignIn()),
)
: new StaggerAnimationSignIn( action: (){
CheckInternet().checkInternet().then((e){
if(e == true){
PostHTTP().sendLogin({'username': usernameController.text, 'password': passwordController.text}).then((e){
String responseStatus = e['status'];
if(responseStatus != "200"){
Alerts(context: context).alertFalseInfosLogin();
failedLoginAnimation();
}else{
String token = e['token'];
PostHTTP().sendRequestAPI(token, 'membership').then((value){
if(value['status'] == "200"){
String membership = value['membership'];
print(membership);
if(membership == "138" || membership == "136"){
APIToken.token = token;
JTools.saveLogin(token);
print('User is premium ');
FocusScope.of(context).requestFocus(new FocusNode());
hasFinishedLogin = true;
hasFinishLogAnim = false;
hasFailedLogin = false;
_playAnimationLogin();
}else{
print("User isn't premium ");
Alerts(context: context).alertNotPremium();
failedLoginAnimation();
}
}else{
if(value['status'] == "404"){Alerts(context: context).alertErrorConnectionServer(); failedLoginAnimation();}
if(value['status'] == "401" || value['status'] == "403"){Alerts(context: context).alertNotPremium(); failedLoginAnimation();}
}
});
}
});
}else{
Alerts(context: context).alertInternet();
failedLoginAnimation();
}
});
//Navigator.of(context).popAndPushNamed('/main');
},
buttonController:
_loginButtonController.view
),
animationStatusFree == 0
? new Padding(
padding: const EdgeInsets.only(bottom: 85.0),
child: new InkWell(
onTap: () {
setState(() {
animationStatusFree = 1;
});
_playAnimationFree();
},
child: new FreeAccess()),
)
: new StaggerAnimationFree(
buttonController:
_freeButtonController.view),
],
),
],
),
),
)
)
);
}
Widget waitingPage(){
return WillPopScope(child: Scaffold(backgroundColor: DesignColors.backgroundColor.withOpacity(1), body: Center(child: new CircularProgressIndicator(
value: null,
strokeWidth: 1.0,
valueColor: new AlwaysStoppedAnimation<Color>(
DesignColors.robot1Theme),
)),), onWillPop:() async => false);
}
@override
Widget build(BuildContext context) {
timeDilation = 0.4;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
if(userLoginToken == null && tokenFileExist == 'false'){
return logPage();
}
if(userLoginToken == null && tokenFileExist == null && backToLogin == "true"){
//TODO: on loading
backToLogin ="false";
tokensManagement();
return logPage();
}else if(userLoginToken == null && tokenFileExist == null){
//TODO: on loading
return waitingPage();
}
if(userLoginToken.isNotEmpty && tokenFileExist == null){
//TODO: on loading
return waitingPage();
}
if(userLoginToken.isEmpty && tokenFileExist == 'true'){
return logPage();
}
return logPage();
}
}
我的动画课
import 'package:flutter/material.dart';
import 'package:test/premium/tools/design/color/designColors.dart';
import 'login.dart';
class StaggerAnimationSignIn extends StatelessWidget {
final action;
StaggerAnimationSignIn({Key key, this.buttonController, @required this.action})
: buttonSqueezeanimation = new Tween(
begin: 320.0,
end: 70.0,
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.0,
0.175,
),
),
),
buttomZoomOut = new Tween(
begin: 70.0,
end: 5000.0,
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.55,
0.999,
curve: Curves.bounceOut,
),
),
),
containerCircleAnimation = new EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 160.0),
end: const EdgeInsets.only(bottom: 0.0),
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.500,
0.800,
curve: Curves.ease,
),
),
),
super(key: key);
final AnimationController buttonController;
final Animation<EdgeInsets> containerCircleAnimation;
final Animation buttonSqueezeanimation;
final Animation buttomZoomOut;
Widget _buildAnimation(BuildContext context, Widget child) {
return new Padding(
padding: buttomZoomOut.value == 80
? const EdgeInsets.only(bottom: 160.0)
: containerCircleAnimation.value,
child: new InkWell(
onTap: () {
},
child: new Hero(
tag: "fadeLogin",
child: buttomZoomOut.value <= 300
? new Container(
width: buttomZoomOut.value == 70
? buttonSqueezeanimation.value
: buttomZoomOut.value,
height:
buttomZoomOut.value == 70 ? 60.0 : buttomZoomOut.value,
alignment: FractionalOffset.center,
decoration: new BoxDecoration(
color: DesignColors.animatedLoginColor,
borderRadius: buttomZoomOut.value < 400
? new BorderRadius.all(const Radius.circular(30.0))
: new BorderRadius.all(const Radius.circular(0.0)),
),
child: buttonSqueezeanimation.value > 75.0
? new Text(
"Sign In",
style: new TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w300,
letterSpacing: 0.3,
),
)
: buttomZoomOut.value < 300.0
? new CircularProgressIndicator(
value: null,
strokeWidth: 1.0,
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.white),
)
: null)
: new Container(
width: buttomZoomOut.value,
height: buttomZoomOut.value,
decoration: new BoxDecoration(
shape: buttomZoomOut.value < 500
? BoxShape.circle
: BoxShape.rectangle,
color: DesignColors.animatedLoginColor,
),
),
)),
);
}
@override
Widget build(BuildContext context) {
if(LoginScreenState.isFirstCallAction){
buttonController.addListener(() {
if (LoginScreenState.isFirstCallAction) {
print("1");
LoginScreenState.isFirstCallAction = false;
action();
}
if (LoginScreenState.hasFailedLogin) {
print("3");
LoginScreenState.hasFailedLogin = false;
buttonController.reverse();
LoginScreenState.animationStatusLogin = 0;
}
if (buttonController.value > 0.2 &&
LoginScreenState.hasFinishedLogin == false &&
LoginScreenState.hasFailedLogin == false &&
LoginScreenState.hasFinishLogAnim == false) {
print("2");
buttonController.stop();
}
if (LoginScreenState.hasFinishedLogin && !LoginScreenState.hasFailedLogin && !LoginScreenState.hasFinishLogAnim && buttonController.value == 1.0) {
print("4");
LoginScreenState.hasFinishedLogin = false;
LoginScreenState.hasFailedLogin = false;
LoginScreenState.hasFinishLogAnim = true;
LoginScreenState.animationStatusLogin = 0;
Navigator.popAndPushNamed(context, "/main");
return;
}
});
}
return new AnimatedBuilder(
builder: _buildAnimation,
animation: buttonController,
);
}
}
更新 :
HI解决了我认为的问题,我仍然需要做一些测试,但是似乎在AnimationClass中调用Navigator.popAndPushNamed(context, "/main");
并不是一个好主意,我只是以一种动画结束的方式更改了Login类我在登录课程中致电Navigator.popAndPushNamed(context, "/main");
。我将在未来几天内继续更新此帖子。
我的构建登录类的新起点
@override
Widget build(BuildContext context) {
_loginButtonController.addListener((){
if(_loginButtonController.isCompleted){
FocusScope.of(context).requestFocus(new FocusNode());
Navigator.popAndPushNamed(context, "/main");
}
});
并在“我的动画”结束通话中删除了Navigator.popAndPushNamed(context, "/main");
和return;
答案 0 :(得分:0)
虽然我无法运行提供的最小重现,但通过日志,您似乎正在尝试从已弹出或处理的内容中访问上下文。
如果您能够复制您在 minimal repro 上报告的行为,则更容易确定原因。这是我正在使用的完整示例代码。
import 'package:flutter/material.dart';
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({Key key, this.controller})
:
// Each animation defined here transforms its value during the subset
// of the controller's duration defined by the animation's interval.
// For example the opacity animation transforms its value during
// the first 10% of the controller's duration.
opacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.100,
curve: Curves.ease,
),
),
),
width = Tween<double>(
begin: 50.0,
end: 150.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.125,
0.250,
curve: Curves.ease,
),
),
),
height = Tween<double>(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.250,
0.375,
curve: Curves.ease,
),
),
),
padding = EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 16.0),
end: const EdgeInsets.only(bottom: 75.0),
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.250,
0.375,
curve: Curves.ease,
),
),
),
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4.0),
end: BorderRadius.circular(75.0),
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.375,
0.500,
curve: Curves.ease,
),
),
),
color = ColorTween(
begin: Colors.indigo[100],
end: Colors.orange[400],
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.500,
0.750,
curve: Curves.ease,
),
),
),
super(key: key);
final Animation<double> controller;
final Animation<double> opacity;
final Animation<double> width;
final Animation<double> height;
final Animation<EdgeInsets> padding;
final Animation<BorderRadius> borderRadius;
final Animation<Color> color;
// This function is called each time the controller "ticks" a new frame.
// When it runs, all of the animation's values will have been
// updated to reflect the controller's current value.
Widget _buildAnimation(BuildContext context, Widget child) {
return Container(
padding: padding.value,
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
border: Border.all(
color: Colors.indigo[300],
width: 3.0,
),
borderRadius: borderRadius.value,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: controller,
);
}
}
class StaggerDemo extends StatefulWidget {
@override
_StaggerDemoState createState() => _StaggerDemoState();
}
class _StaggerDemoState extends State<StaggerDemo>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _playAnimation() async {
try {
await _controller.forward().orCancel;
await _controller.reverse().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because we were disposed
}
}
@override
Widget build(BuildContext context) {
var timeDilation = 10.0; // 1.0 is normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Staggered Animation'),
),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.1),
border: Border.all(
color: Colors.black.withOpacity(0.5),
),
),
child: StaggerAnimation(controller: _controller.view),
),
ElevatedButton(
child: Text('Open route'),
onPressed: () {
// Navigate to second route when tapped.
Navigator.popAndPushNamed(context, '/second');
},
),
],
),
),
),
);
}
}
void main() {
runApp(StaggerDemoApp());
}
class StaggerDemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => StaggerDemo(),
// When navigating to the "/second" route, build the SecondScreen widget.
'/second': (context) => MyHomePage(title: 'Second Page',),
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
据我观察,在动画中弹出屏幕并推送新屏幕似乎不会导致任何错误。正如日志所提到的,在 AnimationControllers 上设置的触发侦听器是导致问题的原因。