【问题标题】:Adding to widgets a to Listview using the builder doesn't keep state of the widget使用构建器将小部件添加到 Listview 不会保留小部件的状态
【发布时间】:2021-09-14 17:49:49
【问题描述】:

我有一个 ListView,您可以使用按钮和 ListView.builder 添加更多小部件。我添加的小部件是有状态的,称为Timer。如果我编辑 Timer 的文本,然后尝试添加另一个小部件,则 ListView 中所有小部件的状态都会重置。

如何使小部件在添加新小部件时保持其状态?如果我在 _builderTimer 方法中打印索引,它似乎是在重建 ListView 中的每个小部件,而不是添加一个新小部件。


import 'package:flutter/material.dart';
import 'package:flutter/cupertino.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: Scaffold(
          appBar: AppBar(
            title: Text("Rest and Reps"),
          ),
          body: TimerList(),
          drawer: Drawer(
            child: ListView(
              padding: EdgeInsets.zero,
              children: <Widget>[DrawerHeader(child: Text('Timers'))],
            ),
          ),
        ));
  }
}

class TimerList extends StatefulWidget {
  TimerList({Key? key}) : super(key: key);

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

class _TimerListState extends State<TimerList> {
  int timers = 1;

  _addTimer() {
    setState(() {
      timers += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Expanded(
        child: ListView.builder(
            itemCount: this.timers,
            itemBuilder: (context, index) => this._buildTimer(index)),
      ),
      ElevatedButton(onPressed: _addTimer, child: Text("Add Timer")),
      ElevatedButton(onPressed: () {}, child: Text("Start Workout")),
      ElevatedButton(onPressed: () {}, child: Text("Pause Timer")),
      ElevatedButton(onPressed: () {}, child: Text("Reset Current Timer"))
    ]);
  }

  _buildTimer(int timerIndex) {
    //Each row show be a widget. Should have Gif/image of Rxcercise, Excercise name and timer length
    return Timer(
        timerIndex: timerIndex,
        excerciseName: "New Excercise",
        excerciseDuration: new Duration(minutes: 0, seconds: 0));
  }
}

class Timer extends StatefulWidget {
  Timer(
      {Key? key,
      required this.timerIndex,
      required this.excerciseName,
      required this.excerciseDuration})
      : super(key: key);

  int timerIndex;
  String excerciseName;
  Duration excerciseDuration;

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

class _TimerState extends State<Timer> {
  //Needs to be stateful so that user can pause timer.
  @override
  Widget build(BuildContext context) {
    Future<void> setExcerciseName(passedInName) async {
      print(passedInName);
      setState(() {
        widget.excerciseName = passedInName;
      });
    }

    return ListTile(
      isThreeLine: true,
      title: Text((widget.timerIndex + 1).toString() +
          " " +
          widget.excerciseName.toString()),
      subtitle: Text('Time left'),
      onTap: () {
        setState(() {
          widget.excerciseName = "mememe";
        });
        _popDialog(context, widget.excerciseName,
            setExcerciseName); //When creating a new timer, have Exercise name as default
      },
    );
  }
}

_popDialog(BuildContext context, String excerciseName,
    Function setExcerciseNameCallback) {
  TextEditingController _excerciseNameController;

  _excerciseNameController =
      TextEditingController.fromValue(TextEditingValue(text: excerciseName));
  showDialog(
      context: context,
      builder: (BuildContext context) {
        return new SimpleDialog(
            title: TextField(
              controller: _excerciseNameController,
            ),
            children: [
              Container(
                  width: 280,
                  height: 280,
                  child: CupertinoTimerPicker(
                      mode: CupertinoTimerPickerMode.ms,
                      onTimerDurationChanged: (newTime) {
                        //newTime is what will set the new duration. newTime is typeof Duration.
                        print(newTime);
                      })),
              new TextButton(
                child: new Text('Save'),
                onPressed: () {
                  //SetState when clicking saved
                  setExcerciseNameCallback(_excerciseNameController.text);
                  Navigator.of(context).pop();
                },
              ),
            ]);
      });
}

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    您的示例存在多个问题。

    1. 每次您在 TimerList 小部件上调用 setState 时,它都会被重建。由于您在 ListView.builder 的 builder Function 中创建了 Timers,因此 Timer 的新实例将替换以前的实例。
    2. 为了完成这项工作,您必须将实例保持在TimerList 的状态,并让构建器在重新构建小部件时重新使用这些实例。您可以为此使用 List&lt;Timer&gt;
    3. Timer 是一个有状态的小部件,但您不会将数据(持续时间、练习)保存在 TimerState 类中,而是保存在 Widget 类中,其中数据应该是不可变的。
    4. TextEditingController 不应驻留在对话框函数中,而应处于Timer 的状态。当Timer 被处置时,它也应该被处置。因此,用于显示对话框的函数应该移动到Timer 小部件内,并直接操作Timer 的状态。
    5. 您在Timerbuild 方法中声明对话框回调函数。应该移到外面,变成Timer的方法。 build 函数应该只返回它构建的小部件,不应实现任何副作用。

    我已经修正了这个例子。

    import 'package:flutter/material.dart';
    import 'package:flutter/cupertino.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: Scaffold(
            appBar: AppBar(
              title: Text("Rest and Reps"),
            ),
            body: TimerList(),
            drawer: Drawer(
              child: ListView(
                padding: EdgeInsets.zero,
                children: <Widget>[
                  DrawerHeader(child: Text('Timers')),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class TimerList extends StatefulWidget {
      TimerList({Key? key}) : super(key: key);
    
      @override
      _TimerListState createState() => _TimerListState();
    }
    
    class _TimerListState extends State<TimerList> {
      List<Timer> timers = [];
    
      _addTimer() {
        final newIndex = timers.length;
        final newTimer = Timer(
          key: ValueKey(newIndex),
          timerIndex: newIndex,
          excerciseName: "New Excercise",
          excerciseDuration: new Duration(minutes: 0, seconds: 0),
        );
    
        setState(() {
          timers.add(newTimer);
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Column(children: [
          Expanded(
            child: ListView.builder(
                itemCount: timers.length,
                itemBuilder: (context, index) => this._buildTimer(index)),
          ),
          ElevatedButton(onPressed: _addTimer, child: Text("Add Timer")),
          ElevatedButton(onPressed: () {}, child: Text("Start Workout")),
          ElevatedButton(onPressed: () {}, child: Text("Pause Timer")),
          ElevatedButton(onPressed: () {}, child: Text("Reset Current Timer"))
        ]);
      }
    
      _buildTimer(int timerIndex) {
        return timers[timerIndex];
      }
    }
    
    class Timer extends StatefulWidget {
      Timer({
        Key? key,
        required this.timerIndex,
        required this.excerciseName,
        required this.excerciseDuration,
      }) : super(key: key);
    
      final int timerIndex;
      final Duration excerciseDuration;
      final String excerciseName;
    
      @override
      _TimerState createState() => _TimerState();
    }
    
    class _TimerState extends State<Timer> {
      late Duration excerciseDuration;
      late String excerciseName;
      late TextEditingController _excerciseNameController;
    
      @override
      void initState() {
        excerciseDuration = widget.excerciseDuration;
        excerciseName = widget.excerciseName;
        _excerciseNameController = TextEditingController(text: excerciseName);
        super.initState();
      }
    
      @override
      void dispose() {
        _excerciseNameController.dispose();
        super.dispose();
      }
    
      Future<void> updateExcercise() async {
        // call set sate to rebuild the widget
        setState(() {
          excerciseName = _excerciseNameController.text;
        });
      }
    
      //Needs to be stateful so that user can pause timer.
      @override
      Widget build(BuildContext context) {
        return ListTile(
          isThreeLine: true,
          title: Text(
            (widget.timerIndex + 1).toString() + " " + excerciseName,
          ),
          subtitle: Text('Time left ${excerciseDuration.inSeconds} seconds'),
          onTap: () {
            _showDialog(
              context,
              updateExcercise,
            ); //When creating a new timer, have Exercise name as default
          },
        );
      }
    
      _showDialog(
        BuildContext context,
        Function updateExcerciseCallback,
      ) {
        showDialog(
          context: context,
          barrierDismissible: false,
          builder: (BuildContext context) {
            return new SimpleDialog(
              title: TextField(
                controller: _excerciseNameController,
              ),
              children: [
                Container(
                  width: 280,
                  height: 280,
                  child: CupertinoTimerPicker(
                    mode: CupertinoTimerPickerMode.ms,
                    onTimerDurationChanged: (newTime) {
                      excerciseDuration = newTime;
                    },
                  ),
                ),
                new TextButton(
                  child: new Text('Save'),
                  onPressed: () {
                    //SetState when clicking saved
                    updateExcerciseCallback();
                    Navigator.of(context).pop();
                  },
                ),
              ],
            );
          },
        );
      }
    }
    

    【讨论】:

    • 非常感谢。这是我第一次玩 Flutter,所以您的所有反馈和解释对我很有帮助!
    猜你喜欢
    • 2023-02-05
    • 1970-01-01
    • 2021-10-29
    • 2022-01-21
    • 1970-01-01
    • 2016-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多