TL;DR:不,使用InheritedWidget 将变量/数据传递给子小部件,在here 和here 中了解更多信息
为什么不呢?
在 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 不支持方法重载,这意味着有两个同名但参数不同的方法会导致编译错误。
现在,如果您像这样在 build() 方法中添加 BoxConstraints constraints
@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 并返回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 小部件存在,但它说没有脚手架?这是因为下面的行使用了Scaffold 小部件上方 提供的context 小部件(build() 方法)。
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hello')));
如果您用Builder 小部件包装FlatButton,它将工作try it.
像许多颤振小部件一样,您可以创建一个 WidgetBuilder 变体,在构建小部件时提供额外的参数,例如 FutureBuilder 的 AsyncWidgetBuilder 或 LayoutBuilder 的 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,
);
}
}
这就是你如何使用连接这两个小部件
在您的 BaseScreen 中,使用 InheritedConstraint 包装 child
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,
);
},
),
),
);
},
),
);
}
}