Flutter设置子窗口小部件的状态,而不重建父窗口小部件



我有一个包含listViewfloatingActionButton的父级。当用户开始滚动时,我想隐藏floatingActionButton。我已经在父级小部件中做到了这一点,但这需要每次重新生成列表。

我已经将floatingActionButton移到了一个单独的类中,这样我就可以更新状态,只重建该小部件。我遇到的问题是将数据从父类中的ScrollController传递给子类。通过导航进行操作很简单,但如果不重建父类,则会更尴尬!

当父窗口小部件中的值发生更改时,只重建子窗口小部件的一个好方法是使用ValueNotifier和ValueListenableBuilder。将ValueNotifier的实例添加到父级的状态类中,并将要重建的小部件包装在ValueListenableBuilder中。

如果要更改该值,请使用通知程序进行更改,而不调用setState,并且子窗口小部件将使用新值进行重建。

import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
@override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
ValueNotifier<bool> _notifier = ValueNotifier(false);
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(onPressed: () => _notifier.value = !_notifier.value, child: Text('toggle')),
ValueListenableBuilder(
valueListenable: _notifier,
builder: (BuildContext context, bool val, Widget? child) {
return Text(val.toString());
}),
],
);
}
@override
void dispose() {
_notifier.dispose();
super.dispose();
}
}

为了获得最佳性能,您可以围绕Scaffold创建自己的包装器,将body作为参数。当在HideFabOnScrollScaffoldState中调用setState时,将不会重建body小部件。

这是一种常见的模式,也可以在核心小部件(如AnimationBuilder(中找到。

import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: MyHomePage()));
class MyHomePage extends StatefulWidget {
@override
State<StatefulWidget> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
return HideFabOnScrollScaffold(
body: ListView.builder(
controller: controller,
itemBuilder: (context, i) => ListTile(title: Text('item $i')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
controller: controller,
);
}
}
class HideFabOnScrollScaffold extends StatefulWidget {
const HideFabOnScrollScaffold({
Key key,
this.body,
this.floatingActionButton,
this.controller,
}) : super(key: key);
final Widget body;
final Widget floatingActionButton;
final ScrollController controller;
@override
State<StatefulWidget> createState() => HideFabOnScrollScaffoldState();
}
class HideFabOnScrollScaffoldState extends State<HideFabOnScrollScaffold> {
bool _fabVisible = true;
@override
void initState() {
super.initState();
widget.controller.addListener(_updateFabVisible);
}
@override
void dispose() {
widget.controller.removeListener(_updateFabVisible);
super.dispose();
}
void _updateFabVisible() {
final newFabVisible = (widget.controller.offset == 0.0);
if (_fabVisible != newFabVisible) {
setState(() {
_fabVisible = newFabVisible;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: widget.body,
floatingActionButton: _fabVisible ? widget.floatingActionButton : null,
);
}
}

或者,您也可以为FloatingActionButton创建一个包装器,但这可能会破坏转换。

我认为使用流更简单,也很容易。

您只需要在事件到达时发布到流,然后使用流生成器来响应这些更改。

在这里,我显示/隐藏了一个基于小部件层次结构中的小部件焦点的组件。

我在这里使用了rxdart包,但我认为你不需要。而且你可能无论如何都想使用,因为大多数人无论如何都会使用BloC模式。

import 'dart:async';
import 'package:rxdart/rxdart.dart';
class _PageState extends State<Page> {
final _focusNode = FocusNode();
final _focusStreamSubject = PublishSubject<bool>();
Stream<bool> get _focusStream => _focusStreamSubject.stream;
@override
void initState() {
super.initState();
_focusNode.addListener(() {
_focusStreamSubject.add(_focusNode.hasFocus);
});
}
@override
void dispose() {
_focusNode.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
_buildVeryLargeComponent(),
StreamBuilder(
stream: _focusStream,
builder: ((context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData && snapshot.data) {
return Text("keyboard has focus")
}
return Container();
}),
)
],
),
);
}
}

您可以使用StatefulBuilder并使用其setState函数在其下构建小部件。

示例:

import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// put widget here that you do not want to update using _setState of StatefulBuilder
Container(
child: Text("I am static"),
),
StatefulBuilder(builder: (_context, _setState) {
// put widges here that you want to update using _setState
return Column(
children: [
Container(
child: Text("I am updated for $count times"),
),
RaisedButton(
child: Text('Update'),
onPressed: () {
// Following only updates widgets under StatefulBuilder as we are using _setState
// that belong to StatefulBuilder
_setState(() {
count++;
});
})
],
);
}),
],
);
}
}

最新更新