代码之家  ›  专栏  ›  技术社区  ›  dragonfly02

导航不会使用自定义小部件更新TabBarView主体

  •  0
  • dragonfly02  · 技术社区  · 6 年前

    我想使用TabBar和BottomNavigationBar来控制同一个TabBarView,它的主体是一个自定义小部件RankingTable列表(只在演示中放一个,并使用文本小部件作为第二个子项)。 截图1: enter image description here

    enter image description here

    TabBarView 正文,然后在点击导航栏项目时按预期更新,不确定这是否意味着问题是我的自定义 RankingTable 小装置。 此外,尽管在导航栏中点击新项目时正文不会更新,但如果我切换选项卡,例如从 Tab1 Tab2 把它调回 表1

    import 'package:flutter/material.dart';
    
    import 'package:intl/intl.dart';
    
    void main() => runApp(new Demo());
    
    class Demo extends StatefulWidget {
      @override
      _DemoState createState() => _DemoState();
    }
    
    class _DemoState extends State<Demo> with TickerProviderStateMixin {
      int _currentIndex = 0;
      Map<DateTime, List<RankingBase>> _playerDateRanking;
      TabController controller;
      List<_NavigationIconView> _navigationIconViews;
      @override
      void initState() {
        super.initState();
        controller = TabController(length: 2, vsync: this);
        _navigationIconViews = <_NavigationIconView>[
          _NavigationIconView(
            icon: Icon(Icons.calendar_view_day),
            title: 'Nav0',
            color: Colors.deepOrange,
            vsync: this,
          ),
          _NavigationIconView(
            icon: Icon(Icons.date_range),
            title: 'Nav1',
            color: Colors.deepOrange,
            vsync: this,
          ),
        ];
    
        _playerDateRanking = {
          DateTime(2018, 9, 10): [
            PlayerRanking('Tony', 7, 6, 140, 110, 80),
            PlayerRanking('John', 7, 2, 120, 130, 56),
            PlayerRanking('Mike', 8, 5, 120, 130, 70),
            PlayerRanking('Clar', 6, 2, 100, 134, 63)
          ],
          DateTime(2018, 9, 12): [
            PlayerRanking('Tony', 7, 6, 140, 110, 80),
            PlayerRanking('John', 7, 2, 120, 130, 56),
            PlayerRanking('Mike', 8, 5, 120, 130, 70),
            PlayerRanking('Clare', 6, 2, 100, 134, 63),
            PlayerRanking('Jo', 5, 1, 100, 134, 63)
          ]
        };
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              bottom: TabBar(
                controller: controller,
                tabs: <Widget>[Text('Tab1'), Text('Tab2')],
              ),
            ),
            body: TabBarView(
              controller: controller, //TabController(length: 2, vsync: this),
              children: <Widget>[
                buildRankingTable(_currentIndex),
                Text('TEst'),
              ],
            ),
            bottomNavigationBar: BottomNavigationBar(
              currentIndex: _currentIndex,
              items: _navigationIconViews.map((x) => x.item).toList(),
              onTap: (int index) {
                setState(() {
                  _currentIndex = index;
                });
              },
            ),
          ),
        );
      }
    
      Widget buildRankingTable(int currentIndex) {
        if (currentIndex == 0) {
          return RankingTable(_playerDateRanking, dateFormatter: 'yMMMEd');
        } else if (currentIndex == 1) {
          return RankingTable(_playerDateRanking,
              dateFormatter: 'MMMM'); // different date formatter here!
        }
        return Text('TODO...');
      }
    }
    
    class _NavigationIconView {
      _NavigationIconView({
        Widget icon,
        //Widget activeIcon,
        String title,
        Color color,
        TickerProvider vsync,
      })  : _icon = icon,
            _color = color,
            _title = title,
            item = new BottomNavigationBarItem(
              icon: icon,
              //   activeIcon: activeIcon,
              title: new Text(title),
              backgroundColor: color,
            ),
            controller = new AnimationController(
              duration: kThemeAnimationDuration,
              vsync: vsync,
            ) {
        _animation = new CurvedAnimation(
          parent: controller,
          curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
        );
      }
      final Widget _icon;
      final Color _color;
      final String _title;
      final BottomNavigationBarItem item;
      final AnimationController controller;
      CurvedAnimation _animation;
    }
    
    class PlayerRanking extends RankingBase {
      String name;
      PlayerRanking(this.name, played, won, pointsWon, pointsLost, duration)
          : super(played, won, pointsWon, pointsLost, duration);
    }
    
    class RankingBase {
      DateTime date;
      int won;
      int played;
      int duration;
      int pointsWon;
      int pointsLost;
      double get winRatio => won / played;
      RankingBase(
          this.played, this.won, this.pointsWon, this.pointsLost, this.duration);
    
      static int performanceSort(RankingBase rb1, RankingBase rb2) {
        if (rb1.winRatio > rb2.winRatio) return -1;
        if (rb1.winRatio < rb2.winRatio) return 1;
        if (rb1.played > rb2.played) return -1;
        if (rb2.played == rb2.played) return rb1.pointsWon.compareTo(rb2.pointsWon);
        return -1;
      }
    }
    
    // this puts a scrollable datatable and optionally a header widget into a ListView
    class RankingTable extends StatefulWidget {
      final Map<DateTime, List<RankingBase>> rankingMap;
      final bool hasHeaderWidget;
      final String dateFormatter;
      //final bool isPlayer;
      RankingTable(this.rankingMap,
          {this.dateFormatter, this.hasHeaderWidget = true});
    
      @override
      _RankingTableState createState() => _RankingTableState(this.rankingMap,
          dateFormatter: this.dateFormatter, hasHeaderWidget: this.hasHeaderWidget);
    }
    
    class _RankingTableState extends State<RankingTable> {
      Map<DateTime, List<RankingBase>> rankingMap;
      final bool hasHeaderWidget;
      final String dateFormatter;
      //final bool isPlayer;
      _RankingTableState(this.rankingMap,
          {this.dateFormatter, this.hasHeaderWidget = true});
    
      DateTime _selectedDate;
    
      @override
      initState() {
        super.initState();
        _selectedDate = rankingMap.keys.last;
      }
    
      DataTable buildRankingTable() {
        rankingMap[_selectedDate].sort(RankingBase.performanceSort);
        String nameOrPair =
            rankingMap[_selectedDate].first is PlayerRanking ? 'Name' : 'Pair';
        int rank = 1;
    
        return DataTable(
          columns: <DataColumn>[
            DataColumn(label: Text('Rank')),
            DataColumn(label: Text(nameOrPair)),
            DataColumn(label: Text('Played')),
            DataColumn(label: Text('Win Ratio')),
            DataColumn(label: Text('Points Won-Loss')),
            DataColumn(label: Text('Duration')),
          ],
          rows: rankingMap[_selectedDate].map((RankingBase pr) {
            DataCell titleCell;
            if (pr is PlayerRanking)
              titleCell = DataCell(Text('${pr.name}'));
            else {
              // var pair = pr as PairRanking;
              // titleCell = DataCell(Text('${pair.player1Name}\n${pair.player2Name}'));
            }
            return DataRow(cells: [
              DataCell(Text('${rank++}')),
              titleCell,
              DataCell(Text('${pr.played}')),
              DataCell(Text('${NumberFormat("0.##%").format(pr.won / pr.played)}')),
              DataCell(Text('${pr.pointsWon} - ${pr.pointsLost}')),
              DataCell(Text('${pr.duration}')),
            ]);
          }).toList(),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        List<Widget> childrenWidgets = [];
        if (hasHeaderWidget) {
          var dateDropdown = DropdownButton<DateTime>(
            items: rankingMap.keys
                .map((date) => DropdownMenuItem(
                      child: Text(
                          '${DateFormat(dateFormatter).format(date)}'), //yMMMEd
                      value: date,
                    ))
                .toList(),
            value: _selectedDate,
            onChanged: (value) {
              setState(() {
                _selectedDate = value;
              });
            },
          );
          childrenWidgets.add(dateDropdown);
        }
    
        childrenWidgets.add(SingleChildScrollView(
          padding: EdgeInsets.all(20.0),
          scrollDirection: Axis.horizontal,
          child: buildRankingTable(),
        ));
    
        return ListView(
          padding: EdgeInsets.all(10.0),
          children: childrenWidgets,
        );
      }
    }
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   tomwyr    6 年前

    RankingTable 不改变是因为 建造 代码中的方法使用存储在状态中的字段(rankingMap、dateFormatter等)。 什么时候? StatefulWidget State 对象,然后在 建造 方法。每次你打电话 设置状态 方法,颤振 再现 Widget 正在持久化对象

    小部件是临时对象,用于构建应用程序当前状态的表示。另一方面,状态对象在调用build()之间是持久的,允许它们记住信息。

    这意味着,每次 分级表 建造 中的方法 _RankingTableState 使用在构造函数中传递/在中赋值的相同值 初始状态 (即使widget对象包含更新的字段值)。另一方面,当您使用 TabBar 日期格式化程序 -这就是为什么在这个场景中更新表。

    要使其按预期工作,您应该从 状态 小装置 要获取所有必需的值:

    class RankingTable extends StatefulWidget {
      final Map<DateTime, List<RankingBase>> rankingMap;
      final bool hasHeaderWidget;
      final String dateFormatter;
      RankingTable(this.rankingMap,
          {this.dateFormatter, this.hasHeaderWidget = true});
    
      @override
      _RankingTableState createState() => _RankingTableState();
    }
    
    class _RankingTableState extends State<RankingTable> {
      DateTime _selectedDate;
    
      @override
      initState() {
        super.initState();
        _selectedDate = widget.rankingMap.keys.last;
      }
    
      DataTable buildRankingTable() {
        widget.rankingMap[_selectedDate].sort(RankingBase.performanceSort);
        String nameOrPair =
            widget.rankingMap[_selectedDate].first is PlayerRanking ? 'Name' : 'Pair';
        int rank = 1;
    
        return DataTable(
          columns: <DataColumn>[
            DataColumn(label: Text('Rank')),
            DataColumn(label: Text(nameOrPair)),
            DataColumn(label: Text('Played')),
            DataColumn(label: Text('Win Ratio')),
            DataColumn(label: Text('Points Won-Loss')),
            DataColumn(label: Text('Duration')),
          ],
          rows: widget.rankingMap[_selectedDate].map((RankingBase pr) {
            DataCell titleCell;
            if (pr is PlayerRanking)
              titleCell = DataCell(Text('${pr.name}'));
            else {
              // var pair = pr as PairRanking;
              // titleCell = DataCell(Text('${pair.player1Name}\n${pair.player2Name}'));
            }
            return DataRow(cells: [
              DataCell(Text('${rank++}')),
              titleCell,
              DataCell(Text('${pr.played}')),
              DataCell(Text('${NumberFormat("0.##%").format(pr.won / pr.played)}')),
              DataCell(Text('${pr.pointsWon} - ${pr.pointsLost}')),
              DataCell(Text('${pr.duration}')),
            ]);
          }).toList(),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        List<Widget> childrenWidgets = [];
        if (widget.hasHeaderWidget) {
          var dateDropdown = DropdownButton<DateTime>(
            items: widget.rankingMap.keys
                .map((date) => DropdownMenuItem(
                      child: Text(
                          '${DateFormat(widget.dateFormatter).format(date)}'), //yMMMEd
                      value: date,
                    ))
                .toList(),
            value: _selectedDate,
            onChanged: (value) {
              setState(() {
                _selectedDate = value;
              });
            },
          );
          childrenWidgets.add(dateDropdown);
        }
    
        childrenWidgets.add(SingleChildScrollView(
          padding: EdgeInsets.all(20.0),
          scrollDirection: Axis.horizontal,
          child: buildRankingTable(),
        ));
    
        return ListView(
          padding: EdgeInsets.all(10.0),
          children: childrenWidgets,
        );
      }
    }
    
        2
  •  2
  •   Norbert    6 年前

    问题出在你自己身上 State .

    widget.youFinalField

    问题是:当你返回 new RankingTable 潜在的 state 不是重新创建而是重建 build 以及 didUpdateWidget 方法)。 因为您在构造中传递了变量(这只是第一次使用),所以格式化程序不会更新。

    只需通过小部件访问变量。

    工作代码状态代码:

        // this puts a scrollable datatable and optionally a header widget into a ListView
    class RankingTable extends StatefulWidget {
      final Map<DateTime, List<RankingBase>> rankingMap;
      final bool hasHeaderWidget;
      final String dateFormatter;
      //final bool isPlayer;
      RankingTable(this.rankingMap,
          {this.dateFormatter, this.hasHeaderWidget = true});
    
      @override
      _RankingTableState createState() => _RankingTableState();
    }
    
    class _RankingTableState extends State<RankingTable> {
    
    
      DateTime _selectedDate;
    
    
      @override
      void initState() {
        super.initState();
        _selectedDate = widget.rankingMap.keys.last;
      }
    
    
    
      DataTable buildRankingTable() {
        widget.rankingMap[_selectedDate].sort(RankingBase.performanceSort);
        String nameOrPair =
        widget.rankingMap[_selectedDate].first is PlayerRanking ? 'Name' : 'Pair';
        int rank = 1;
    
        return DataTable(
          columns: <DataColumn>[
            DataColumn(label: Text('Rank')),
            DataColumn(label: Text(nameOrPair)),
            DataColumn(label: Text('Played')),
            DataColumn(label: Text('Win Ratio')),
            DataColumn(label: Text('Points Won-Loss')),
            DataColumn(label: Text('Duration')),
          ],
          rows: widget.rankingMap[_selectedDate].map((RankingBase pr) {
            DataCell titleCell;
            if (pr is PlayerRanking)
              titleCell = DataCell(Text('${pr.name}'));
            else {
              // var pair = pr as PairRanking;
              // titleCell = DataCell(Text('${pair.player1Name}\n${pair.player2Name}'));
            }
            return DataRow(cells: [
              DataCell(Text('${rank++}')),
              titleCell,
              DataCell(Text('${pr.played}')),
              DataCell(Text('')),
              DataCell(Text('${pr.pointsWon} - ${pr.pointsLost}')),
              DataCell(Text('${pr.duration}')),
            ]);
          }).toList(),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        List<Widget> childrenWidgets = [];
        if (widget.hasHeaderWidget) {
          var dateDropdown = DropdownButton<DateTime>(
            items: widget.rankingMap.keys
                .map((date) => DropdownMenuItem(
              child: Text(
                  '$date ${widget.dateFormatter}'), //yMMMEd
              value: date,
            ))
                .toList(),
            value: _selectedDate,
            onChanged: (value) {
              setState(() {
                _selectedDate = value;
              });
            },
          );
          childrenWidgets.add(dateDropdown);
        }
    
        childrenWidgets.add(SingleChildScrollView(
          padding: EdgeInsets.all(20.0),
          scrollDirection: Axis.horizontal,
          child: buildRankingTable(),
        ));
    
        return ListView(
          padding: EdgeInsets.all(10.0),
          children: childrenWidgets,
        );
      }
    }