Флаттер — как динамически поддерживать состояние виджетов внутри нижней панели навигации

#flutter #dart

Вопрос:

У меня есть 2 pages (страница A и страница B), по которым вы можете перемещаться с и на через a bottom navigation bar .

У меня есть специальная банка для использования, где:

Страница А : должно быть « keptAlive » большую часть времени, только 1 сценарий, в котором он должен быть восстановлен

Страница Б: должно быть rebuilt , каждый раз, когда мы переключаемся на нее.

У меня работают эти модели поведения, кроме одной, Dynamic keepAlive для страницы А.

Прежде всего, код:

Главная страница, на которой размещена навигация:

 class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  int _currentIndex = 0;
  PageController? _pageController;
  List<Widget> _pages = [];

  @override
  void initState() {
    super.initState();

    _currentIndex = 0;
    _pages = [PageA(), PageB()];

    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text('example')),
      body: PageView(
      controller: _pageController,
      physics: NeverScrollableScrollPhysics(),
      children: _pages,
    ),
      bottomNavigationBar: AnimatedBottomNavigationBar.builder(
        itemCount: _iconList.length,
        activeIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            _pageController!.jumpToPage(index);
            });
        },
      ),
    );
  }
 

Теперь эта часть работает нормально, на странице B мне не нужно делать ничего особенного, так как по умолчанию при просмотре страниц каждый раз, когда я переключаюсь на эту страницу, она перестраивается.

Теперь, для страницы A, я хочу, чтобы большую часть времени она НЕ перестраивалась, если переходить со страницы B

И я достигаю этого с помощью: AutomaticKeepAliveClientMixin и wantKeepAlive :

Страница виджета:

 class PageA extends StatefulWidget {
  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA> with AutomaticKeepAliveClientMixin<PageA> {
 bool shouldKeepAlive = true;

  @override
  void initState() {
    super.initState();
  }

  @override
  bool get wantKeepAlive => shouldKeepAlive;

@override
  Widget build(BuildContext context) {
    super.build(context);
    /// ..... 
 

Так что это отлично работает, страница A не перестраивается, но у меня есть особый случай,
когда, если на странице A пользователь заполняет a textField любым значением, а затем переключается на страницу B, я хочу, чтобы, если пользователь вернется на страницу A, теперь она была сброшена и перестроена!

Я пробовал разные подходы, но я не могу изменить значение wantKeepAlive после того, как страница A была создана в первый раз. Я пробовал использовать updateKeepAlive() , но это не помогает, так что у кого-нибудь есть идея, как я могу динамически настроить wantKeepAlive страницу A, чтобы, если я перейду на страницу B при некоторых особых условиях, при возвращении на страницу A она могла быть сброшена, а могла и не быть сброшена.

Ответ №1:

Используя метод «updateKeepAlive» в классе «AutomaticKeepAliveClientMixin»,
вы можете изменить условие сохранения.

Если пользователь вводит текст в «Текстовое поле» на странице,
установите значение «shouldKeepAlive» в значение false и вызовите метод «updateKeepAlive», чтобы обновить параметр «Сохранить жизнь».

Я подтвердил, что все работает хорошо, отправив сообщение журнала в метод build (), чтобы подтвердить, когда вызывается build ().

——Модификация—-
Я точно не знаю, почему «updateKeepAlive» адаптируется после того, как фокус удален из виджета текстового поля.
Я изменил некоторый код, чтобы отключить фокус от текстового поля после исчезновения клавиатуры, используя кнопку «стрелка вниз» или «галочка».
По этой причине я использовал один пакет, чтобы проверить, отображается или скрывается клавиатура. https://pub.dev/packages/keyboard_visibility

введите описание изображения здесь

 import 'package:animated_bottom_navigation_bar/animated_bottom_navigation_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyboard_visibility/keyboard_visibility.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Example(),
    );
  }
}

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  int _currentIndex = 0;
  PageController _pageController;
  List<Widget> _pages = [];

  final iconList = <IconData>[
    Icons.brightness_5,
    Icons.brightness_4,
  ];

  @override
  void initState() {
    super.initState();

    _currentIndex = 0;
    _pages = [PageA(), PageB()];

    _pageController = PageController(initialPage: _currentIndex);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('example')),
      body: PageView(
        controller: _pageController,
        physics: NeverScrollableScrollPhysics(),
        children: _pages,
      ),
      bottomNavigationBar: AnimatedBottomNavigationBar.builder(
        itemCount: 2,
        activeIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
            _pageController.jumpToPage(index);
          });
        },
        tabBuilder: (int index, bool isActive) {
          final color = isActive ? Color(0XFFFFA400) : Colors.grey;
          return Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                iconList[index],
                size: 24,
                color: color,
              ),
              const SizedBox(height: 4),
              Padding(
                padding: const EdgeInsets.symmetric(horizontal: 8),
                child: Text(
                  "Page $index",
                  maxLines: 1,
                  style: TextStyle(color: color),
                ),
              )
            ],
          );
        },
      ),
    );
  }
}

class PageA extends StatefulWidget {
  @override
  _PageAState createState() => _PageAState();
}

class _PageAState extends State<PageA>
    with AutomaticKeepAliveClientMixin<PageA> {
  bool shouldKeepAlive = true;
  String input;
  
  @override
  void initState() {
    super.initState();
    KeyboardVisibilityNotification().addNewListener(
      onChange: (bool visible) {
        if (!visible) {
          FocusManager.instance.primaryFocus.unfocus();
          print('keyboard disappeared');
          if (!shouldKeepAlive amp;amp; input.isEmpty) {
            shouldKeepAlive = true;
            updateKeepAlive();
          } else if (shouldKeepAlive amp;amp; input.isNotEmpty) {
            shouldKeepAlive = false;
            updateKeepAlive();
          }
        }
      },
    );
  }

  @override
  bool get wantKeepAlive => shouldKeepAlive;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    print('PageA is builded');

    return TextField(
      onSubmitted: (String value) {
        print('onSumitted: $value');
      },
      onChanged: (String value) {
        input = value;
        print('onChanged: $value');
      },
    );
  }
}

class PageB extends StatefulWidget {
  @override
  _PageBState createState() => _PageBState();
}

class _PageBState extends State<PageB> {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    print('PageB is builded');
    return TextField();
  }
}

 

Комментарии:

1. Это был мой первый подход после того, как я нашел метод «updateKeepAlive ()», но он не сработал, поэтому я разместил здесь, в основном у меня точно такой же код, у меня есть несколько отпечатков в методе «OnChanged» текстового поля, и значение ShouldKeepAlive обновляется правильно, но даже если значение «shouldKeepAlive» установлено в значение false, если я переключусь на страницу, когда я вернусь на страницу, я нахожусь в том же самом состоянии, в текстовом поле есть то, что я написал перед переключением страниц, и страница не сбрасывается…что-то не так отсутствует, похоже, что, когда я возвращаюсь назад, он не запускает повторное создание виджета PageA

2. и я просто запускаю ваш код, и действительно, если вы заполните текстовое поле на странице, а затем переключитесь на страницу, когда вернетесь на страницу, даже в вашем примере текстовое поле все еще имеет value…so это не сбросило страницу

3. @AJ989 Правда? Я много раз подтверждал, работает ли это так, как вы хотите.

4. @AJ989 Я прикрепил gif, который я тестировал, к контенту.

5. хорошо, я выяснил, почему это не работает на моей стороне, я закрывал клавиатуру с помощью «стрелки вниз» вместо того, чтобы нажимать «значок галочки», чтобы закрыть клавиатуру.. Это подводит меня к проблеме, как я могу убедиться, что даже если пользователь закрывает клавиатуру кнопкой «вниз» и переключает страницу, у меня такое же поведение? Спасибо за помощь