TL;DR:不,用于将变量/数据传递给子小部件,在此处和此处InheritedWidget
阅读有关它的更多信息
为什么不?
在 Dart 语言中,只能将可选/命名的非冲突参数添加到被覆盖的方法。
例如:
class SuperClass {
void someMethod(String parameter1) {}
}
class SubClass1 extends SuperClass {
// adding optional parameter
@override
void someMethod(String paremeter1, [String paremter2]) {}
}
class SubClass2 extends SuperClass {
// adding optional named parameter
@override
void someMethod(String paremeter1, {String paremter2}) {}
}
注意:Dart 不支持方法重载,这意味着有两个同名但参数不同的方法会导致编译错误。
现在如果你像这样添加BoxConstraints constraints
你的build()
方法
@override
Widget build(BuildContext context, [BoxConstraints constraint]){
/// Your code
}
它会编译,但谁会给你那个 [constraint] 参数?
作为开发人员,我们从不build()
自己调用该方法,Flutter 框架会为我们调用该方法。
原因:我们自己调用该build()
方法会很困难,因为它需要context
,并且提供正确的上下文值是只有 Flutter 框架才能正确完成的事情。大多数新开发人员都会传递context
变量,但不能保证它是否总是有效,因为小部件在小部件树中的位置决定了该小部件的正确context
值是什么。并且在编写代码期间,没有简单的方法可以确定小部件在小部件树中的确切位置。即使我们能以某种方式找出这个地方,context
那个地方的价值是什么?因为 Flutter 提供了那个价值,所以这个价值是如何创造的,就另当别论了。
解决方案
Flutter 中有两种简单且非常常见的解决方案,用于将数据/变量传递给子小部件,
- 使用
WidgetBuilder
变体
- 使用
InheritedWidget
(推荐)
解决方案 1. 使用WidgetBuilder
变体
WidgetBuilder
是一个接受BuildContext
并返回 a的函数Widget
,听起来很熟悉吗?它是build()
方法的类型定义。但是我们已经有了build()
可用的方法,那有什么意义WidgetBuilder
呢?最常见的用例是为BuildContext
.
例如:如果您单击“显示快餐栏”,它将不起作用,而是抛出错误并显示“使用不包含 Scaffold 的上下文调用的 Scaffold.of()”。
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () {
/// This will not work
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text('Hello')));
},
child: Text('Show snackbar'),
),
)
);
}
你可能会想,显然有一个Scaffold
小部件存在,但它说没有脚手架?这是因为下面的行正在使用context
小部件上方的Scaffold
小部件(build() 方法)提供。
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hello')));
如果你FlatButton
用Builder
小部件包装它,它会起作用。
像许多颤振小部件一样,您可以创建一个 WidgetBuilder 变体,在构建类似FutureBuilder
'sAsyncWidgetBuilder
或 like LayoutBuilder
's的小部件时提供额外参数LayoutWidgetBuilder
例如:
class BaseScreen extends StatelessWidget {
/// Instead of [child], a builder is used here
final LayoutWidgetBuilder builder;
const BaseScreen({this.builder});
@override
Widget build(BuildContext context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
/// Here we forward the [constraint] to [builder],
/// so that it can forward it to child widget
child: builder(context, constraint),
),
),
);
},
),
);
}
}
这就是你使用它的方式(就像 LayoutBuilder 一样,但是孩子获得了父窗口小部件的 LayoutBuilder 的约束,并且只需要一个 LayoutBuilder
@override
Widget build(BuildContext context) {
return BaseScreen(
builder: (context, constraint) {
// TODO: use the constraints as you wish
return Container(
color: Colors.blue,
height: constraint.minHeight,
);
},
);
}
解决方案 2. 使用InheritedWidget
(推荐)
样本InheritedWidget
/// [InheritedWidget]s are very efficient, in fact they are used throughout
/// flutter's source code. Even the `MediaQuery.of(context)` and `Theme.of(context)`
/// is actually an [InheritedWidget]
class InheritedConstraint extends InheritedWidget {
const InheritedConstraint({
Key key,
@required this.constraint,
@required Widget child,
}) : assert(constraint != null),
assert(child != null),
super(key: key, child: child);
final BoxConstraints constraint;
static InheritedConstraint of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<InheritedConstraint>();
}
@override
bool updateShouldNotify(covariant InheritedConstraint old) =>
constraint != old.constraint;
}
extension $InheritedConstraint on BuildContext {
/// Get the constraints provided by parent widget
BoxConstraints get constraints => InheritedConstraint.of(this).constraint;
}
您的子小部件可以BoxConstraints
像这样访问此继承的小部件提供的
class ChildUsingInheritedWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// Get the constrains provided by parent widget
final constraint = context.constraints;
// TODO: use the constraints as you wish
return Container(
color: Colors.green,
height: constraint.minHeight,
);
}
}
这就是你如何使用连接这两个小部件
在你的 BaseScreenchild
中用 InheritedConstraint包裹
class BaseScreen extends StatelessWidget {
final Widget child;
const BaseScreen({this.child});
@override
Widget build(BuildContext context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
child:
InheritedConstraint(constraint: constraint, child: child),
),
),
);
},
),
);
}
}
你可以在任何你喜欢的地方使用 BaseScreen 例如:
@override
Widget build(BuildContext context) {
return BaseScreen(child: ChildUsingInheritedWidget());
}
请参阅此工作 DartPad 示例:https ://dartpad.dev/9e35ba5c2dd938a267f0a1a0daf814a7
注意:我在您的示例代码中注意到这一行:
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
如果您尝试获取SafeArea()
小部件提供的填充,则该行不会为您提供正确的填充,因为它使用错误context
,它应该使用下面 SafeArea()
的上下文来执行此操作,请使用Builder
小部件。
例子:
class BaseScreen extends StatelessWidget {
final Widget child;
const BaseScreen({this.child});
@override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: SafeArea(
child: Builder(
builder: (context) {
var safePadding = MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom;
return ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraint.maxHeight - safePadding,
),
child: child,
);
},
),
),
);
},
),
);
}
}