Flutter: управление состояниями нескольких дочерних виджетов из одного родительского виджета - PullRequest
2 голосов
/ 14 июля 2020

У меня есть виджет с отслеживанием состояния Menu, который является родительским виджетом для нескольких экземпляров дочернего виджета MenuIcon, который возвращает контейнер. Пользователь может касаться виджетов MenuIcon индивидуально, чтобы они выделялись, когда active bool истинно, и не выделялись, когда это не так. Прямо сейчас все это контролируется в классе MenuIcon, что, судя по чтению здесь , не лучший подход, учитывая, что состояние каждой из иконок будет играть важную роль в приложении, и поэтому Я хочу иметь возможность управлять им в родительских виджетах. Но я не совсем уверен, как это сделать, в приведенном ранее руководстве просто один родительский виджет -> один дочерний виджет, поэтому очень легко управлять состоянием из родительского виджета. Но в этом случае, поскольку родительский виджет содержит несколько экземпляров дочерних виджетов, каждый из которых имеет собственное активное / неактивное состояние, я не могу придумать простой способ сделать это.

В настоящее время моя иерархия выглядит так: Я предполагаю, что MenuIcons будет просто управлять своей собственной анимацией, класс Menu - для управления состояниями каждого MenuIcon, а HomePage - для хранения списка всех MenuIcon, активных в данный момент.

Код для классов Menu и MenuIcon следующий:

//----------------- Menu ------------------------------------------
//These classes control the scrollable menu that appears when the
//dropdown is pressed

class Menu extends StatefulWidget {
  final String _category;

  Menu(this._category);

  @override
  _MenuState createState() => _MenuState(category: this._category);

}

class _MenuState extends State<Menu> {
  List<Offer> _offersList = createOffers();
  String category;

  _MenuState({@required this.category});

  //build method
  Widget build(BuildContext context) {
    final _menuItems = List<Container>();
    //builds an item widget if the category is correct.
    for (int i = 0; i < _offersList.length; i++) {
      if (this.category == _offersList[i].category) {
        // adds a container containing the MenuIcon object, with the offer
        // in question passed through _offersList[i]
        _menuItems.add(Container(
            child: MenuIcon(
                offer: _offersList[i],
        )));
      }
    }

    //This particular widget tree allows to have a horizontal scrolling
    //menu within a fixed widget
    if (_menuItems.length > 0) {
      return SizedBox(
        child: Row(
          children: [
            Expanded(
                child: ListView(
                  children: _menuItems,
                  scrollDirection: Axis.horizontal,
            )),
          ],
        ),
        height: 154,
     );
   } else {
      return Row(
        children: [
          Container(
            child: Text(
              'Sorry! There are no offers available for this category',
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: 14.0,
              ),
            ),
            padding: EdgeInsets.only(left: 12),
          ),
        ],
      );
    }
  }
}

//------------------- MenuIcon class -----------------------------

class MenuIcon extends StatefulWidget {
  Offer offer;

  MenuIcon({@required this.offer});

  @override
  _MenuIconState createState() => _MenuIconState(this.offer);
}

class _MenuIconState extends State<MenuIcon> {
  Offer _offer;
  bool active;

  _MenuIconState(this._offer) {
    this.active = false;
  }

  void _handleTap() {
    setState(() {
      active = !active;
    });
  }

  Widget build(BuildContext context) {
    //print('icon rebuilt with active = $active');
    var label = _offer.discount.toString();
    return Container(
      child: GestureDetector(
        child: Column(
          children: [
            _offer.image,
            Text(
               '$label% off',
               style: TextStyle(
                  color: Colors.red,
                  fontSize: 14.0,
               ),
            ),
          ],
          mainAxisAlignment: MainAxisAlignment.center,
        ),
        onTap: _handleTap,
      ),
      //changes the colour if the icon is selected or not
      color: active ? Colors.yellow : Colors.white,
    );
  }
}

Ответы [ 3 ]

2 голосов
/ 22 июля 2020

Вы можете проверить пакет поставщик , с его помощью легко управлять пользовательским интерфейсом на каждой странице,

Добавление некоторых статей

  1. https://www.raywenderlich.com/6373413-state-management-with-provider
  2. https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd
  3. https://medium.com/flutterpub/provider-state-management-in-flutter-d453e73537c5
1 голос
/ 23 июля 2020

Другой жизнеспособный вариант - это пакет Async_Redux, доступный на pub.dev

Он имеет гораздо меньше шаблонного кода и позволяет очень легко реализовать состояние. Вы можете настроить ReduxActions для управления состоянием из ваших действий. Все приложение реагирует на манипуляции с государством. Затем используются ReduxSelectors для отображения определенных c изменений на каждом экране.

Изучите:

https://pub.dev/packages/async_redux

https://pub.dev/packages/provider_for_redux

Доступна обширная документация.

https://www.fireship.io также содержит интересное содержание, касающееся управления состоянием.

0 голосов
/ 18 июля 2020

Вы можете использовать пакет blo c для обработки состояния поддерева (в данном случае с Menu как root)

Вот пример:

class MenuBloc extends Bloc<int, int> {
  MenuBloc() : super(0);

  @override
  Stream<int> mapEventToState(int event) async* {
    yield event;
}


class Menu extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider<MenuBloc>(
      create: (context) => MenuBloc(),
      child: Column(
        children: [
          MenuIcon(menuIndex 0),
          MenuIcon(menuIndex: 1),
          MenuIcon(menuIndex: 2),
        ],
      ),
    );
  }
}

class MenuIcon extends StatelessWidget {
  final int menuIndex;

  const MenuIcon({Key key, this.menuIndex}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => _updateState(context),
      child:
          BlocBuilder<MenuBloc, int>(builder: (context, state) {
            final isActive = menuIndex == state;
        return Container();
      }),
    );
  }

  void _updateState(BuildContext context) {
    BlocProvider.of<MenuBloc>(context).add(menuIndex);
  }
}

Если вам нужно сделать что-то в любом месте поддерева при нажатии на MenuIcon, вы можете использовать BlocListener, который запускается каждый раз при изменении состояния

...