Навигация не обновляет тело TabBarView с пользовательским виджетом - PullRequest
0 голосов
/ 20 сентября 2018

Я хочу использовать TabBar и BottomNavigationBar для управления тем же TabBarView, тело которого является списком настраиваемого виджета RankingTable (поместите только один в демо и используя виджет Text в качестве 2-го дочернего элемента).Моя проблема заключается в том, что при нажатии на панель навигации, он не обновляет тело TabBarView, например, RankingTable не обновляется;в приведенном ниже демонстрационном коде я поместил те же данные в таблицу, но выпадающий список над таблицей должен быть отформатирован по-разному, поскольку при каждом касании панели навигации я передаю разные форматеры в каждый элемент данных.А именно, на втором снимке экрана в Nav1 он все еще показывает тот же формат даты раскрывающегося списка, что и на первом снимке экрана, который находится на Nav0.Снимок экрана 1: enter image description here

Снимок экрана 2: enter image description here

Если я помещу простой виджет, такой как Текст, в тело TabBarView,затем он обновляется, как и ожидалось, при нажатии на элементы панели навигации, не уверен, подразумевает ли это, что проблема заключается в моем пользовательском RankingTable виджете.Кроме того, несмотря на то, что тело не обновляется при нажатии на новый элемент на панели навигации, если я переключаю вкладку, например, с Tab1 на Tab2 и переключаю ее обратно на Tab1, то тело обновляется правильно, в соответствии ссоответствующий элемент панели навигации.Такое ощущение, что данные тела обновлялись при нажатии на навигацию, но они просто не отображались.

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 ]

0 голосов
/ 21 сентября 2018

Причина, по которой RankingTable не изменяется, заключается в том, что метод build в коде использует поля (rankMap, dateFormatter и т. Д.), Хранящиеся в вашем состоянии.Когда StatefulWidget создается впервые, он также создает соответствующий объект State, который затем создает ваш виджет в методе build .Каждый раз, когда вы вызываете метод setState , Flutter воссоздает Widget с нуля, тогда как объект State сохраняется .

Виджеты являются временными объектами,используется для создания презентации приложения в его текущем состоянии.С другой стороны, объекты состояния постоянны между вызовами build (), что позволяет им запоминать информацию.

Это означает, что каждый раз, когда создается RankingTable виджет, ваш build Метод в _RankingTableState использует те же значения, которые передаются в конструкторе / присваиваются в initState (даже если объект виджета содержит обновленные значения полей).С другой стороны, когда вы перемещаетесь назад и вперед с помощью TabBar, объект состояния воссоздается с текущим dateFormatter - поэтому таблица обновляется в этом сценарии.

Для созданияон работает как задумано, вы должны удалить все последние поля из вашего state объекта и обратиться непосредственно к его widget , чтобы получить все необходимые значения:

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,
    );
  }
}
0 голосов
/ 21 сентября 2018

Проблема заключается в вашем State.

Во-первых, вам не нужен конструктор в вашем состоянии.Вы можете получить доступ к переменным вашего виджета, вызвав widget.youFinalField.

. Проблема в том, что когда вы возвращаете new RankingTable, базовый state не воссоздается, а восстанавливается (build и didUpdateWidget методы называются).Поскольку вы передали переменные в конструкцию (которая используется только в первый раз), ваш форматтер не обновляется.

Решение довольно простое, вместо использования конструктора в вашем state просто доступ кпеременные через виджет.

Рабочий код состояния кода:

    // 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,
    );
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...