是否可以扩展在其构建方法上提供额外参数的 StatefulWidget?
Is it possible to extend a StatefulWidget that provides an extra parameter on its build method?
我想创建一个像这样的 BaseScreen 小部件以在我的应用程序中重复使用:
class BaseScreen extends StatelessWidget {
final Widget child;
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: IntrinsicHeight(
child: child,
),
),
),
);
},
),
);
}
}
但是我看到的问题是我还想重用 LayoutBuilder
在这个 class 的 child 中提供的 constraint
属性 .
目前,我需要在 child 中创建一个新的 LayoutBuilder,这听起来像是对引擎的更多处理,以及更多样板代码。
如果我能以某种方式扩展这个小部件,以便在 child 中我可以拥有这个:
@override
Widget build(BuildContext context, BoxConstraints constraints) {
}
那就太好了。我知道 Flutter 也鼓励组合而不是继承,所以如果我能用另一种方式解决它,我也会很感激。
谢谢!
当然可以。它看起来像这样
abstract class BoxConstraintsWidget extends StatelessWidget {
Widget build(BuildContext context, BoxConstraints constraints);
@override
Widget build(BuildContext context) {
return build(context, BoxConstraints());
}
}
然后像
一样覆盖它
class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
@override
Widget build(BuildContext context, BoxConstraints constraints) {
return someStuff;
}
}
虽然有一个小问题 - Widget build(BuildContext context)
是一种内部框架方法,您不能强制使用多个参数调用它(当然,如果您不想自己重写整个 flutter ).问题是你可以使用上面的方法,但是在你的基础 class 中添加这个 BoxConstraints constraints
作为一些 getter
和默认实现,如果你想在它的 child 中覆盖它.它看起来像这样:
abstract class BoxConstraintsWidget extends StatelessWidget {
BoxConstraints get constraints => BoxConstraints();
Widget build(BuildContext context, BoxConstraints constraints);
@override
Widget build(BuildContext context) {
return build(context, constraints);
}
}
并按原样使用它或覆盖它
class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
@override
BoxConstraints get constraints => MyBoxConstraints();
@override
Widget build(BuildContext context, BoxConstraints constraints) {
//here you will have you copy of constraints that = MyBoxConstraints()
//without overriding method you would have had a parent constraints that = BoxConstraints()
return someStuff;
}
}
这只是一种方法,可能有点多余,但您可以尝试一下。您可以在没有继承的情况下使用它。
您也可以为您的小部件试验自定义 Builders
,其工作方式类似于 ListView.builder()
、LayoutBuilder()
或 FutureBuilder()
。我建议您研究一下它们是如何工作的。
您还可以为 child 小部件创建一个自定义构造函数,它接收 BoxConstraints
作为参数并将其存储在 State
或 [= 中作为用户的小部件23=]建设者。
还有很多方法可以做到这一点,其中大部分都是简单组合的不同实现,所以是的......实验))
希望对您有所帮助。
TL;DR :不,使用 InheritedWidget
将 variables/data 传递给子部件,在 here and here[= 中阅读更多相关信息64=]
为什么不呢?
在 Dart 语言中,只能将 optional/named 个不冲突的参数添加到重写的方法中。
例如:
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
,并且提供正确的上下文值只会让人颤抖框架正确。大多数新开发人员都会传递 context
变量,但不能保证它是否始终有效,因为小部件在小部件树中的位置决定了该小部件的正确 context
值。并且在编写代码期间,没有简单的方法来确定小部件在小部件树中的确切位置。即使我们能以某种方式找出那个地方,那个地方的 context
的值是多少?因为 flutter 提供了该值,所以该值的创建方式是另一个 post.
解决方案
flutter 中有两种简单且非常常见的解决方案,用于将 data/variables 传递给子部件,
- 使用
WidgetBuilder
个变体
- 使用
InheritedWidget
(推荐)
解决方案 1. 使用 WidgetBuilder
变体
WidgetBuilder
是一个函数,它接受BuildContext
和returns一个Widget
,听起来很熟悉吧?,它是build()
方法的类型定义。但是我们已经有可用的 build()
方法,WidgetBuilder
有什么意义呢?。最常见的用例是 BuildContext
.
的范围
例如:
如果你点击 "Show snackbar" 它将不起作用,而是抛出错误 "Scaffold.of() called with a context that does not contain a Scaffold."
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
widget,却说没有脚手架?这是因为以下行使用 context
由 上面的 小部件提供 Scaffold
小部件(build() 方法)。
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hello')));
如果用 Builder
widget, it will work try it.
包裹 FlatButton
像许多 flutter 小部件一样,您可以创建一个 WidgetBuilder 变体,它在构建小部件时提供额外的参数,例如 FutureBuilder
's AsyncWidgetBuilder
or 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]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,
);
}
}
这就是您使用 connect 这两个小部件的方式
在你的 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,
);
},
),
),
);
},
),
);
}
}
我想创建一个像这样的 BaseScreen 小部件以在我的应用程序中重复使用:
class BaseScreen extends StatelessWidget {
final Widget child;
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: IntrinsicHeight(
child: child,
),
),
),
);
},
),
);
}
}
但是我看到的问题是我还想重用 LayoutBuilder
在这个 class 的 child 中提供的 constraint
属性 .
目前,我需要在 child 中创建一个新的 LayoutBuilder,这听起来像是对引擎的更多处理,以及更多样板代码。
如果我能以某种方式扩展这个小部件,以便在 child 中我可以拥有这个:
@override
Widget build(BuildContext context, BoxConstraints constraints) {
}
那就太好了。我知道 Flutter 也鼓励组合而不是继承,所以如果我能用另一种方式解决它,我也会很感激。
谢谢!
当然可以。它看起来像这样
abstract class BoxConstraintsWidget extends StatelessWidget {
Widget build(BuildContext context, BoxConstraints constraints);
@override
Widget build(BuildContext context) {
return build(context, BoxConstraints());
}
}
然后像
一样覆盖它class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
@override
Widget build(BuildContext context, BoxConstraints constraints) {
return someStuff;
}
}
虽然有一个小问题 - Widget build(BuildContext context)
是一种内部框架方法,您不能强制使用多个参数调用它(当然,如果您不想自己重写整个 flutter ).问题是你可以使用上面的方法,但是在你的基础 class 中添加这个 BoxConstraints constraints
作为一些 getter
和默认实现,如果你想在它的 child 中覆盖它.它看起来像这样:
abstract class BoxConstraintsWidget extends StatelessWidget {
BoxConstraints get constraints => BoxConstraints();
Widget build(BuildContext context, BoxConstraints constraints);
@override
Widget build(BuildContext context) {
return build(context, constraints);
}
}
并按原样使用它或覆盖它
class BoxConstraintsWidgetChild extends BoxConstraintsWidget{
@override
BoxConstraints get constraints => MyBoxConstraints();
@override
Widget build(BuildContext context, BoxConstraints constraints) {
//here you will have you copy of constraints that = MyBoxConstraints()
//without overriding method you would have had a parent constraints that = BoxConstraints()
return someStuff;
}
}
这只是一种方法,可能有点多余,但您可以尝试一下。您可以在没有继承的情况下使用它。
您也可以为您的小部件试验自定义 Builders
,其工作方式类似于 ListView.builder()
、LayoutBuilder()
或 FutureBuilder()
。我建议您研究一下它们是如何工作的。
您还可以为 child 小部件创建一个自定义构造函数,它接收 BoxConstraints
作为参数并将其存储在 State
或 [= 中作为用户的小部件23=]建设者。
还有很多方法可以做到这一点,其中大部分都是简单组合的不同实现,所以是的......实验))
希望对您有所帮助。
TL;DR :不,使用 InheritedWidget
将 variables/data 传递给子部件,在 here and here[= 中阅读更多相关信息64=]
为什么不呢?
在 Dart 语言中,只能将 optional/named 个不冲突的参数添加到重写的方法中。
例如:
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
,并且提供正确的上下文值只会让人颤抖框架正确。大多数新开发人员都会传递 context
变量,但不能保证它是否始终有效,因为小部件在小部件树中的位置决定了该小部件的正确 context
值。并且在编写代码期间,没有简单的方法来确定小部件在小部件树中的确切位置。即使我们能以某种方式找出那个地方,那个地方的 context
的值是多少?因为 flutter 提供了该值,所以该值的创建方式是另一个 post.
解决方案
flutter 中有两种简单且非常常见的解决方案,用于将 data/variables 传递给子部件,
- 使用
WidgetBuilder
个变体 - 使用
InheritedWidget
(推荐)
解决方案 1. 使用 WidgetBuilder
变体
WidgetBuilder
是一个函数,它接受BuildContext
和returns一个Widget
,听起来很熟悉吧?,它是build()
方法的类型定义。但是我们已经有可用的 build()
方法,WidgetBuilder
有什么意义呢?。最常见的用例是 BuildContext
.
例如: 如果你点击 "Show snackbar" 它将不起作用,而是抛出错误 "Scaffold.of() called with a context that does not contain a Scaffold."
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
widget,却说没有脚手架?这是因为以下行使用 context
由 上面的 小部件提供 Scaffold
小部件(build() 方法)。
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Hello')));
如果用 Builder
widget, it will work try it.
FlatButton
像许多 flutter 小部件一样,您可以创建一个 WidgetBuilder 变体,它在构建小部件时提供额外的参数,例如 FutureBuilder
's AsyncWidgetBuilder
or 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]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,
);
}
}
这就是您使用 connect 这两个小部件的方式
在你的 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,
);
},
),
),
);
},
),
);
}
}