Flutter - calling setState() from other widget

2020-03-01 20:05发布

问题:

Is it possible to call setState() of particular widget (embedded in other widgets) from other widgets onPressed() method so only that widget is redrawn?

I want to click on the button and see the state of "MyTextWidget" to change. The rest of the layout is same, nothing changes there so it should not be rewritten.

This is my code:

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Timer',
      theme: new ThemeData(
        primaryColor: Colors.grey.shade800,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  int _seconds = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          MyTextWidget(), //just update this widget
          Divider(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.add_circle),
                onPressed: _addPressed,
                iconSize: 150.0,
              ),
              IconButton(
                icon: Icon(Icons.remove_circle),
                onPressed: ()=> print("to be implemented"),
                iconSize: 150.0,
              ),
            ],
          )
        ],
      ),
    );
  }

  void _addPressed() {
    //somehow call _updateSeconds()
  }
}

And this is statefull MyTextWidget which I want to update.

class MyTextWidget extends StatefulWidget{
  @override
  _MyTextWidgetState createState() => _MyTextWidgetState();
}

class _MyTextWidgetState extends State<MyTextWidget> {

  int secondsToDisplay = 0;

  void _updateSeconds(int newSeconds) {
    setState(() {
      secondsToDisplay = newSeconds;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      secondsToDisplay.toString(),
      textScaleFactor: 5.0,
    );
  }
}

It seems like something quite simple what I want to achieve but I'm not able to figure it out. Imagine if "MyTextWidget" was buried in huge layout tree and every time I want to update it I would need to redraw whole tree again.

回答1:

This is a possible solution using streams:

import 'dart:async';

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Timer',
      theme: new ThemeData(
        primaryColor: Colors.grey.shade800,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  StreamController<int> _controller = StreamController<int>();

  int _seconds = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          MyTextWidget(stream: _controller.stream), //just update this widget
          Divider(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.add_circle),
                onPressed: _addPressed,
                iconSize: 150.0,
              ),
              IconButton(
                icon: Icon(Icons.remove_circle),
                onPressed: ()=> _controller.add(_seconds++),
                iconSize: 150.0,
              ),
            ],
          )
        ],
      ),
    );
  }

  void _addPressed() {
    //somehow call _updateSeconds()
  }
}

class MyTextWidget extends StatefulWidget{

  final Stream<int> stream;

  MyTextWidget({this.stream});

  @override
  _MyTextWidgetState createState() => _MyTextWidgetState();
}

class _MyTextWidgetState extends State<MyTextWidget> {

  int secondsToDisplay = 0;

  void _updateSeconds(int newSeconds) {
    setState(() {
      secondsToDisplay = newSeconds;
    });
  }

  @override
  void initState() {
    super.initState();
    widget.stream.listen((seconds) {
      _updateSeconds(seconds);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      secondsToDisplay.toString(),
      textScaleFactor: 5.0,
    );
  }
}


回答2:

There are different ways on how to achieve this. The really simple one is with InheritedWidget widget, like that:

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Timer',
      theme: new ThemeData(
        primaryColor: Colors.grey.shade800,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  MyHomePageState createState() {
    return new MyHomePageState();
  }
}

class MyHomePageState extends State<MyHomePage> {
  int _seconds = 1;

  @override
  Widget build(BuildContext context) {
    return new MyInheritedWidget(
      secondsToDisplay: _seconds,
      child: Scaffold(

        appBar: AppBar(
          title: Text("title"),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            MyTextWidget(), //just update this widget
            Divider(),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                IconButton(
                  icon: Icon(Icons.add_circle),
                  onPressed: _addPressed,
                  iconSize: 150.0,
                ),
                IconButton(
                  icon: Icon(Icons.remove_circle),
                  onPressed: () => print("to be implemented"),
                  iconSize: 150.0,
                ),
              ],
            )
          ],
        ),
      ),
    );
  }

  void _addPressed() {
    setState(() {
      _seconds++;
    });
  }
}

class MyTextWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final MyInheritedWidget inheritedWidget = MyInheritedWidget.of(context);
    return Text(inheritedWidget.secondsToDisplay.toString(),
      textScaleFactor: 5.0,
    );
  }
}

class MyInheritedWidget extends InheritedWidget {
  final int secondsToDisplay;

  MyInheritedWidget({
    Key key,
    @required this.secondsToDisplay,
    @required Widget child,
  }) : super(key: key, child: child);

  static MyInheritedWidget of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(MyInheritedWidget);
  }

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) =>
      secondsToDisplay != oldWidget.secondsToDisplay;
}


回答3:

I'm a beginner of flutter.

This is my modified version, not sure if there is anything wrong via this way, but it works.

  1. Add key value in class MyTextWidget
MyTextWidget({Key key}):super(key:key);
  1. Declare a GlobalKey in class MyHomePage
GlobalKey<_MyTextWidgetState> textGlobalKey = new GlobalKey<_MyTextWidgetState>();
  1. Add MyTextWidget widget
MyTextWidget(key: textGlobalKey);
  1. Call _updateSeconds() in _addPressed()
void _addPressed() {
  //somehow call _updateSeconds()
  textGlobalKey.currentState._updateSeconds(_seconds++);
}

All code:

import 'package:flutter/material.dart';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Timer',
      theme: new ThemeData(
        primaryColor: Colors.grey.shade800,
      ),
      home: new MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {

  GlobalKey<_MyTextWidgetState> textGlobalKey = new GlobalKey<_MyTextWidgetState>();

  int _seconds = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("title"),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          MyTextWidget(key: textGlobalKey), //just update this widget
          Divider(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              IconButton(
                icon: Icon(Icons.add_circle),
                onPressed: _addPressed,
                iconSize: 150.0,
              ),
              IconButton(
                icon: Icon(Icons.remove_circle),
                onPressed: ()=> print("to be implemented"),
                iconSize: 150.0,
              ),
            ],
          )
        ],
      ),
    );
  }

  void _addPressed() {
    //somehow call _updateSeconds()
    textGlobalKey.currentState._updateSeconds(_seconds++);
  }
}

class MyTextWidget extends StatefulWidget{

  MyTextWidget({Key key}):super(key:key);

  @override
  _MyTextWidgetState createState() => _MyTextWidgetState();
}

class _MyTextWidgetState extends State<MyTextWidget> {

  int secondsToDisplay = 0;

  void _updateSeconds(int newSeconds) {
    setState(() {
      secondsToDisplay = newSeconds;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      secondsToDisplay.toString(),
      textScaleFactor: 5.0,
    );
  }
}

Hope it helps :)



标签: dart flutter