我试图创建小部件来构建视图,而不必每次都键入相同的代码。这是我想出的:
/// Common class for all views
class GerenciadorTelaLogada<C extends StatefulWidget> extends State<C> {
/// View title
final String titulo;
// Child widgets
final Widget child;
GerenciadorTelaLogada(
{@required this.titulo, this.child, this.floatingActButton});
final Widget floatingActButton;
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(this.titulo),
centerTitle: true,
),
// Side Menu
drawer: GMenu(),
floatingActionButton: this.floatingActButton,
// View body
body: SafeArea(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: Container(
color: Color(0xff5b71b3),
padding: EdgeInsets.only(top: 10),
child: this.child,
),
),
),
);
}
}
然后,我创建了另一个类,为GerenciadorTelaLogada
类添加了一些功能:
// Standard class to all views that as a Form
class GerenciadorTelaLogadaCadastro<C extends StatefulWidget>
extends GerenciadorTelaLogada<C> {
/// Executed when clicked in save button
final VoidCallback onSalvar;
/// Construtor
GerenciadorTelaLogadaCadastro(
{@required String titulo, Widget child, @required this.onSalvar})
: super(titulo: titulo, child: child);
// Adds new widgets
@override
Widget build(BuildContext context) {
FloatingActionButton salvarFloatButton = FloatingActionButton(
child: Icon(
Icons.save,
color: Colors.white,
),
backgroundColor: Colors.green,
onPressed: this.onSalvar,
);
/// Return the base view with additional widgets
return GerenciadorTelaLogada(
titulo: this.titulo,
child: this.child,
floatingActButton: salvarFloatButton,
).build(context);
}
}
现在,我可以使用该类创建多个视图,如下所示:
class CBandeira extends StatefulWidget {
@override
_CBandeiraState createState() => _CBandeiraState();
}
class _CBandeiraState extends GerenciadorTelaLogadaCadastro<CBandeira>
with SingleTickerProviderStateMixin {
final _formKey = GlobalKey<FormState>();
var formInputs = <GTextBox>[];
@override
Widget build(BuildContext context) {
return GerenciadorTelaLogadaCadastro(
onSalvar: () {
if (_formKey.currentState.validate()) {
Scaffold.of(context).showSnackBar(new SnackBar(
content: Text("Test"),
));
}
},
titulo: "Cadastro de Bandeira",
child: Form(
key: _formKey,
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
leading: Icon(
formInputs[index].icon,
color: Colors.white,
),
title: formInputs[index],
);
},
separatorBuilder: (context, index) {
return SizedBox(height: 10);
},
itemCount: formInputs.length),
),
).build(context);
}
}
一切正常,除了当我尝试显示快餐栏时,_CBandeiraState, onSalvar
函数返回此错误:
I/flutter (20636): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter (20636): The following assertion was thrown while handling a gesture:
I/flutter (20636): Scaffold.of() called with a context that does not contain a Scaffold.
I/flutter (20636): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
I/flutter (20636): usually happens when the context provided is from the same StatefulWidget as that whose build
I/flutter (20636): function actually creates the Scaffold widget being sought.
I/flutter (20636): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that
I/flutter (20636): is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
I/flutter (20636): https://api.flutter.dev/flutter/material/Scaffold/of.html
I/flutter (20636): A more efficient solution is to split your build function into several widgets. This introduces a
I/flutter (20636): new context from which you can obtain the Scaffold. In this solution, you would have an outer widget
I/flutter (20636): that creates the Scaffold populated by instances of your new inner widgets, and then in these inner
I/flutter (20636): widgets you would use Scaffold.of().
I/flutter (20636): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the
I/flutter (20636): key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
I/flutter (20636): The context used was:
I/flutter (20636): CBandeira
I/flutter (20636):
I/flutter (20636): When the exception was thrown, this was the stack:
I/flutter (20636): #0 Scaffold.of (package:flutter/src/material/scaffold.dart:1456:5)
I/flutter (20636): #1 _CBandeiraState.build.<anonymous closure> (package:gerenciador/views/core/cartao/c_bandeira.dart:43:20)
I/flutter (20636): #2 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:779:19)
I/flutter (20636): #3 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:862:36)
I/flutter (20636): #4 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
I/flutter (20636): #5 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:504:11)
I/flutter (20636): #6 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:282:5)
I/flutter (20636): #7 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:217:7)
I/flutter (20636): #8 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:475:9)
I/flutter (20636): #9 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:76:12)
I/flutter (20636): #10 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:122:9)
I/flutter (20636): #11 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:379:8)
I/flutter (20636): #12 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:120:18)
I/flutter (20636): #13 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:106:7)
I/flutter (20636): #14 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
I/flutter (20636): #15 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
I/flutter (20636): #16 GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
I/flutter (20636): #17 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
I/flutter (20636): #18 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
I/flutter (20636): #22 _invoke1 (dart:ui/hooks.dart:275:10)
I/flutter (20636): #23 _dispatchPointerDataPacket (dart:ui/hooks.dart:184:5)
I/flutter (20636): (elided 3 frames from dart:async)
I/flutter (20636):
I/flutter (20636): Handler: "onTap"
I/flutter (20636): Recognizer:
I/flutter (20636): TapGestureRecognizer#033d5
I/flutter (20636): ════════════════════════════════════════════════════════════════════════════════════════════════════
我是否缺少某些东西来完成我想做的事情?我几天前才开始发抖,所以如果我做错了方向,我也不会感到惊讶。
答案 0 :(得分:0)
问题是您正在创建CBandeira> GerenciadorTelaLogadaCadastro> GerenciadorTelaLogada,而不是让StatefulWidget做并在需要时调用每个构建方法,而是在使用{{1}时在其他构建方法中调用它们},这样做是要告诉该类使用为其提供的上下文(这就是错误的原因,因为您使用的是相同的上下文,所以它无法在树中找到支架),而不是让StatefulWidget创建其在树下拥有。以GMenu()为例,它是您在其他地方创建的小部件,因此可以随时重用。
您需要从顶部小部件(包裹其他类的父类)到底部进行构建,因此首先需要构建Scaffold,然后在内部要包装另一个类的主体,并按照这种逻辑直到找到树中所需的最小的类或小部件为止。
.build(context)
然后,您可以将其与其他任何类一起使用
//If you're not going to use setState or doens't need to update the widget then just use a StatelessWidget
class GerenciadorTelaLogada extends StatelessWidget {
final String titulo;
final Widget child;
final Widget floatingActButton;
GerenciadorTelaLogada(
{@required this.titulo, this.child, this.floatingActButton});
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text(this.titulo),
centerTitle: true,
),
drawer: GMenu(),
floatingActionButton: this.floatingActButton,
body: SafeArea(
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(new FocusNode());
},
child: Container(
color: Color(0xff5b71b3),
padding: EdgeInsets.only(top: 10),
child: this.child ?? const SizedBox(),
),
),
),
);
}
}
更新
我在创建窗口小部件之前使用了脚手架,这就是为什么我没有看到错误的原因,现在我看到了它,在这种情况下,您要做的是将要使用class CBandeira extends StatefulWidget {
@override
_CBandeiraState createState() => _CBandeiraState();
}
class _CBandeiraState extends State<CBandeira> {
final _formKey = GlobalKey<FormState>();
var formInputs = <GTextBox>[];
@override
Widget build(BuildContext context) {
// Now you can use it in other's widget build method and create your own child, title and fab
return GerenciadorTelaLogada(
titulo: "Cadastro de Bandeira",
floatingActButton: FloatingActionButton(
child: Icon(
Icons.save,
color: Colors.white,
),
backgroundColor: Colors.green,
onPressed: () {
if (_formKey.currentState.validate()) {
Scaffold.of(context).showSnackBar(new SnackBar(
content: Text("Test"),
));
}
},
),
child: Form(
key: _formKey,
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
leading: Icon(
formInputs[index].icon,
color: Colors.white,
),
title: formInputs[index],
);
},
separatorBuilder: (context, index) {
return SizedBox(height: 10);
},
itemCount: formInputs.length),
),
);
}
}
的窗口小部件包装起来一个Builder,它允许您创建一个新的上下文,然后它可以在树中的上方检查支架。在此之前,两者都处于同一环境中,这就是为什么它找不到它的原因
Scaffold.of(context)
使用全局密钥更新
class CBandeira extends StatefulWidget {
@override
_CBandeiraState createState() => _CBandeiraState();
}
class _CBandeiraState extends State<CBandeira> {
final _formKey = GlobalKey<FormState>();
var formInputs = <GTextBox>[
GTextBox(
label: "Código",
tipo: TextBoxTipo.numerico,
icon: Icons.person,
),
GTextBox(
label: "Nome",
tipo: TextBoxTipo.tudo,
icon: Icons.person,
validator: (valor) {
if (valor.length < 3) {
return 'Nome muito curto';
}
return null;
},
),
GTextBox(
label: "Descrição",
tipo: TextBoxTipo.tudo,
icon: Icons.details,
),
];
@override
Widget build(BuildContext context) {
// Now you can use it in other's widget build method and create your own child, title and fab
return GerenciadorTelaLogada(
titulo: "Cadastro de Bandeira",
floatingActButton: Builder( //with this It can now check for a scaffold above the tree
builder: (context){
return FloatingActionButton(
child: Icon(
Icons.save,
color: Colors.white,
),
backgroundColor: Colors.green,
onPressed: () {
if (_formKey.currentState.validate()) {
Scaffold.of(context).showSnackBar(new SnackBar(
content: Text("Test"),
));
}
},
);
},
),
child: Form(
key: _formKey,
child: ListView.separated(
itemBuilder: (context, index) {
return ListTile(
leading: Icon(
formInputs[index].icon,
color: Colors.white,
),
title: formInputs[index],
);
},
separatorBuilder: (context, index) {
return SizedBox(height: 10);
},
itemCount: formInputs.length),
),
);
}
}