Flutter setState of child widget without rebuilding parent

我的未来我决定 提交于 2021-02-17 15:43:10

问题


I have a parent that contain a listView and a floatingActionButton i would like to hide the floatingActionButton when the user starts scrolling i have managed to do this within the parent widget but this requires the list to be rebuilt each time.

I have moved the floatingActionButton to a separate class so i can update the state and only rebuild that widget the problem i am having is passing the data from the ScrollController in the parent class to the child this is simple when doing it through navigation but seams a but more awkward without rebuilding the parent!


回答1:


For optimal performance, you can create your own wrapper around Scaffold that gets the body as a parameter. The body widget will not be rebuilt when setState is called in HideFabOnScrollScaffoldState.

This is a common pattern that can also be found in core widgets such as 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,
    );
  }
}

Alternatively you could also create a wrapper for FloatingActionButton, but that will probably break the transition.




回答2:


I think using a stream is more simpler and also pretty easy.

You just need to post to the stream when your event arrives and then use a stream builder to respond to those changes.

Here I am showing/hiding a component based on the focus of a widget in the widget hierarchy.

I've used the rxdart package here but I don't believe you need to. also you may want to anyway because most people will be using the BloC pattern anyway.

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();
            }),
          )
        ],
      ),
    );
  }
}



回答3:


You can use StatefulBuilder and use its setState function to build widgets under it.

Example:

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++;
                    });
                  })
            ],
          );
        }),
      ],
    );
  }
}


来源:https://stackoverflow.com/questions/51725031/flutter-setstate-of-child-widget-without-rebuilding-parent

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!