【问题标题】:Flutter - Custom Widget - How to obtain a value fromFlutter - 自定义小部件 - 如何从
【发布时间】:2019-09-26 07:41:22
【问题描述】:

这是我遇到的问题的一个简单示例。给定以下示例,如何从类外部获取“计数器”的值?

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int counter = 0;
  void increaseCount() {
    setState(() => this.counter++);
    print("New count = $counter");
  }

  Widget build(context) {
    return new RaisedButton(
      onPressed: increaseCount,
      child: new Text('Tap To Add'),
    );
  }
}

【问题讨论】:

    标签: flutter widget


    【解决方案1】:

    这是对我之前答案的更新,以更好地说明我想要实现的目标。我目前在我的代码中使用这个概念,使用 Checkbox 来存储它自己的值。

    这只是一个示例(概念验证),用于展示我想要实现的目标。我只是想让 Widget 存储它的值并在我需要时将其提供给我(即更新数据)。另一种方法是让主程序存储值,但我认为这并不理想。

    以下似乎有效。为了简单地获取计数器的值,我似乎需要做很多事情。我希望有一个更简单的方法。代码如下:

    import 'package:flutter/material.dart';
    import 'counterWithState.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      final String sTitle = 'Flutter Counter-With-State Demo';
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: sTitle,
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: sTitle),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      CounterWithState _counterWithState;
      FloatingActionButton _fab;
      int _iCounterOriginal = 99;
      RaisedButton _raisedButton;
      SizedBox _sizedBox;
      final _scaffoldKey = GlobalKey<ScaffoldState>();
    
      @override
      void initState() {
        super.initState();
        _fab = FloatingActionButton(
          onPressed: _showSnackbar,
          tooltip: 'Press to show Counter',
          child: Icon(Icons.info),
        );
        _raisedButton = RaisedButton(
            child: const Text('Update'),
            color: Theme.of(context).accentColor,
            elevation: 4.0,
            splashColor: Colors.blueGrey,
            onPressed: () {
              _iCounterOriginal = _counterWithState.iCounter;
              _counterWithState = null;
              _getCounterWithState(context);
              setState(() {});
            });
        _sizedBox = SizedBox(height: _raisedButton.height);
      }
    
      fnCounterChanged(int iCounter) {
        setState(() {});
      }
    
      _showSnackbar() {
        _scaffoldKey.currentState.showSnackBar(SnackBar(
            backgroundColor: Colors.blue,
            content:
                Text("Current value of Counter is ${_counterWithState.iCounter}")));
      }
    
      @override
      Widget build(BuildContext context) {
        _counterWithState = _counterWithState != null
            ? _counterWithState
            : _getCounterWithState(context);
        return Scaffold(
          key: _scaffoldKey,
          appBar: AppBar(
            title: Text(widget.title),
            centerTitle: true,
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                _counterWithState.getCounterWidget(),
                _getUpdateButton(context),
              ],
            ),
          ),
          floatingActionButton: _fab,
        );
      }
    
      Widget _getUpdateButton(BuildContext context) {
        return _counterWithState == null ||
                _counterWithState.iCounter == _iCounterOriginal
            ? _sizedBox
            : _raisedButton;
      }
    
      CounterWithState _getCounterWithState(context) {
        if (_counterWithState == null)
          _counterWithState = CounterWithState(
              iCounter: _iCounterOriginal,
              isAllowedChange: true,
              fnNotifyChange: fnCounterChanged);
        return _counterWithState;
      }
    }
    
    import 'package:flutter/material.dart';
    
    class CounterWithState {
      int _iCounter;
      final Function fnNotifyChange;
      CounterWithStateInternal _counterWithStateInternal;
    
      fnDataChanged(int iNewCounter) {
        _iCounter = iNewCounter;
        if (fnNotifyChange != null) fnNotifyChange(iNewCounter);
      }
    
      CounterWithStateInternal getCounterWidget() {
        return _counterWithStateInternal;
      }
    
      CounterWithState(
          {@required iCounter,
          @required bool isAllowedChange,
          this.fnNotifyChange}) {
        _iCounter = iCounter;
        _counterWithStateInternal = CounterWithStateInternal(
            GlobalKey(), this._iCounter, isAllowedChange, fnDataChanged);
      }
      get iCounter => _iCounter;
    }
    
    class CounterWithStateInternal extends StatefulWidget {
      final int iCounter;
      final bool tfAllowChange;
      final Function fnDataChanged;
      CounterWithStateInternal(
          Key key, this.iCounter, this.tfAllowChange, this.fnDataChanged)
          : super(key: key);
      @override
      CounterWithStateMain createState() => CounterWithStateMain();
    }
    
    class CounterWithStateMain extends State<CounterWithStateInternal> {
      int _iCounter;
      int _iOriginalCounter;
    
      @override
      initState() {
        super.initState();
        _iCounter = widget.iCounter;
        _iOriginalCounter = widget.iCounter;
      }
    
      void incrementCounter(int iValue) {
        if (widget.tfAllowChange) {
          setState(() => this._iCounter += iValue);
          widget.fnDataChanged(_iCounter);
        }
      }
    
      Widget build(context) {
        return Column(children: <Widget>[
          Text("Value of original counter = $_iOriginalCounter"),
          _getSizedBox(),
          Text("Value of counter = $_iCounter"),
          _getSizedBox(),
          RaisedButton(
            onPressed: (() => incrementCounter(1)),
            child: Text('Tap To Add'),
          ),
          _getSizedBox(),
          RaisedButton(
            onPressed: (() => incrementCounter(-1)),
            child: Text('Tap To Subtract'),
          ),
          _getSizedBox(),
        ]);
      }
    
      SizedBox _getSizedBox() {
        return SizedBox(height: 20.0);
      }
    }
    

    【讨论】:

      【解决方案2】:

      这只是一个示例(概念验证)来展示我想要实现的目标。我只是希望 Widget 存储它的值并在我需要时将其提供给我(即更新数据)。另一种方法是让主程序存储值,但我认为这并不理想。

      以下似乎有效。为了简单地获取计数器的值,我似乎需要做很多事情。我希望有一个更简单的方法。代码如下:

      import 'package:flutter/material.dart';
      import 'counterWithState.dart';
      
      void main() => runApp(MyApp());
      
      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: MyHomePage(title: 'Flutter Demo Home Page'),
          );
        }
      }
      
      class MyHomePage extends StatefulWidget {
        MyHomePage({Key key, this.title}) : super(key: key);
        final String title;
      
        @override
        _MyHomePageState createState() => _MyHomePageState();
      }
      
      class _MyHomePageState extends State<MyHomePage> {
        CounterWithState _counterWithState =
            CounterWithState(iCounter: 0, isAllowedChange: true);
        FloatingActionButton _fab;
        final _scaffoldKey = GlobalKey<ScaffoldState>();
        @override
        void initState() {
          super.initState();
          _fab = FloatingActionButton(
            onPressed: _showSnackbar,
            tooltip: 'Press to show Counter',
            child: Icon(Icons.info),
          );
        }
      
        _showSnackbar() {
          _scaffoldKey.currentState.showSnackBar(SnackBar(
              backgroundColor: Colors.blue,
              content:
                  Text("Current value of Counter is ${_counterWithState.iCounter}")));
        }
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            key: _scaffoldKey,
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  _counterWithState.getCounterWidget(),
                ],
              ),
            ),
            floatingActionButton: _fab,
          );
        }
      }
      
      import 'package:flutter/material.dart';
      
      class CounterWithState {
        int _iCounter;
        CounterWithStateInternal _counterWithStateInternal;
      
        fnDataChanged(int iNewCounter) {
          _iCounter = iNewCounter;
          debugPrint("CounterWithState: New value = $_iCounter");
        }
      
        CounterWithStateInternal getCounterWidget() {
          return _counterWithStateInternal;
        }
      
        CounterWithState({@required iCounter, @required bool isAllowedChange}) {
          _iCounter = iCounter;
          _counterWithStateInternal = CounterWithStateInternal(
              GlobalKey(), this._iCounter, isAllowedChange, fnDataChanged);
        }
        get iCounter => _iCounter;
      }
      
      class CounterWithStateInternal extends StatefulWidget {
        final int iCounter;
        final bool tfAllowChange;
        final Function fnDataChanged;
        CounterWithStateInternal(
            Key key, this.iCounter, this.tfAllowChange, this.fnDataChanged)
            : super(key: key);
        @override
        CounterWithStateMain createState() => CounterWithStateMain();
      }
      
      class CounterWithStateMain extends State<CounterWithStateInternal> {
        int _iCounter = 0;
      
        @override
        initState() {
          super.initState();
          _iCounter = widget.iCounter;
        }
      
        void increaseCount() {
          if (widget.tfAllowChange) {
            setState(() => this._iCounter++);
            widget.fnDataChanged(_iCounter);
            print("CounterWithStateMain: New count = $_iCounter");
          }
        }
      
        Widget build(context) {
          return Column(children: <Widget>[
            Text("Value of counter = $_iCounter"),
            SizedBox(
              height: 20.0,
            ),
            RaisedButton(
              onPressed: increaseCount,
              child: Text('Tap To Add'),
            )
          ]);
        }
      }
      
      

      【讨论】:

      • 我认为这不是一个好方法,因为这样做有太多其他解决方案。你的问题的标题具有误导性。正如您在 OWN 代码中看到的那样,您想要操纵状态 (CounterWithState),而不仅仅是获取值。
      • @Hosar。看不出你是怎么得出结论的。我希望小部件具有状态并在我想要它时(更新时)获取值。请详细说明,因为您的评论对我来说毫无意义。如果还有其他方法,请告诉我。我不想操纵任何东西,我只想能够获得价值(CounterWithState)。 “其他解决方案太多”???
      【解决方案3】:

      您可以像这样使用 GlobalKey:

      class Counter extends StatefulWidget {
        @override
        final globalKey = GlobalKey<_CounterState>();
      
        @override
        _CounterState createState() => _CounterState();
      }
      
      class _CounterState extends State<Counter> {
        int counter = 0;
        void increaseCount() {
          setState(() => counter++);
          print("New count = $counter");
        }
      
        @override
        Widget build(context) {
          return RaisedButton(
            onPressed: increaseCount,
            child: Text('Tap To Add'),
          );
        }
      }
      

      并像这样访问计数器:

      Counter counter = Counter();
      int count = counter.globalKey.currentState.counter;
      

      请注意:建议这样做。

      您正在从一个简单的包含状态过渡到一个在多个小部件之间共享的状态。有几种更好的方法可以解决这个问题。如需更多信息和解决问题的更好方法,请访问https://flutter.dev/docs/development/data-and-backend/state-mgmt

      【讨论】:

      • 如果您愿意,请查看我的回答和评论。如果有更简单的方法来实现,我想知道。我认为,如果不推荐您的解决方案,那是有原因的。
      • Hosar 的回答是最适合您用例的解决方案。这就是所谓的“提升状态”。 flutter.dev-Link(尤其是这个网站:flutter.dev/docs/development/data-and-backend/state-mgmt/simple)向您展示了如何做到这一点以及有哪些替代方案。
      • 我尝试了 Hosar 的方法,在我看来,它达到了我的预期。它提供了一个回调,该回调在它更改时提供该值。我不想要那个。我想要的是 Widget 有状态并在我要求时给我价值。这样我就不必在我的程序中使用非结构化变量来存储小部件的值。在更新数据时,我可以从 Widget 中获取值(唯一的真相来源)。我并不是说我所做的是最好的方法,但它确实实现了我想要的 - 一个具有自己状态的小部件。 Widget 在任何情况下都有价值。
      • 我将查看 scoped_model 以了解它的作用。正如我所看到的,TextEditingController 目前有自己的状态。这意味着我不必将文本的值存储在我的程序中。我需要做的就是检查数据是否已更改。如果它已更改,我允许更新并从 TextEditingController 获取值。我以与我编写的 CounterWithState 类似的方式将相同的概念应用于 Checkbox。
      【解决方案4】:

      在 Flutter 中你通常做的是传递一个回调函数,在那个函数中你可以传递你需要的值,例如

      class Counter extends StatefulWidget {
        // you can use a callback function
        final ValueSetter<int> callback;
      
        Counter({this.callback});
      
        @override
        _CounterState createState() => _CounterState();
      }
      
      class _CounterState extends State<Counter> {
        int counter = 0;
        void increaseCount() {
          setState(() => this.counter++);
          print("New count = $counter");
          // Here you can pass the value
          widget.callback(this.counter);
        }
      
        Widget build(context) {
          return new RaisedButton(
            onPressed: increaseCount,
            child: new Text('Tap To Add'),
          );
        }
      }
      

      当调用您的小部件时,您可以执行以下操作:

      Counter(callback: (counter){
            // Here you can take some actions on counter
          });
      

      这是我所知道的最简单的方法,或者您可以使用其他一些模式,例如 bloc 或其他东西。
      希望对您有所帮助。

      【讨论】:

      • 谢谢,我会研究一下,但我想要的是让 Widget 拥有自己的状态,并在我需要的时候得到它。
      猜你喜欢
      • 2019-04-01
      • 2018-10-01
      • 1970-01-01
      • 2020-12-07
      • 2019-11-25
      • 2021-04-30
      • 1970-01-01
      • 2020-12-12
      • 1970-01-01
      相关资源
      最近更新 更多