Flutter: низкая производительность при просмотре страниц при обновлении текущего индекса в нижней панели навигации (но без задержки, если я не обновляю currentIndex)

#flutter #dart #lag

#колебание #дротик #задержка

Вопрос:

Я создаю приложение с каркасом, который содержит:

  • A FutureBuilder в теле, которое создает a PageView в качестве дочернего элемента при загрузке данных.
  • BottomNavigationBar Который синхронизируется с PageView для более интуитивной навигации.

С точки зрения функциональности все работает нормально. Я могу проводить пальцем влево и вправо между страницами, и currentIndex они будут правильно обновляться в BottomNavigationBar , и если я нажму на BottomNavigationBar элементы, PageView анимация на правильной странице, как и ожидалось.

Однако… производительность действительно плохая при переключении между страницами, даже в режиме профиля.

После долгих исследований я подтвердил, что задержка присутствует только при обновлении currentIndex BottomNavigationBar .

Если я не обновляю BottomNavigationBar , анимации остаются очень плавными при переключении между страницами, как при пролистывании по PageView , так и при касании самих BottomNavigationBar элементов.

Я также могу подтвердить, что это происходит точно так же при использовании setState и при использовании Provider . Я действительно надеялся, что это просто setState неэффективный метод… но не повезло : (

Для setState реализации, это то, что я делаю:

На PageView :

 onPageChanged: (page) {
  setState(() {
    _selectedIndex = page;
  });
}
  

В нижней навигационной панели:

 onTap: _onTappedBar,
currentIndex: _selectedIndex
  

и ниже:

 void _onTappedBar(int value) {
  _pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
  setState(() {
    _selectedIndex = value;
  });
}
  

Если я закомментирую оба setState метода, приложение снова станет гладким, как масло, и я также смогу использовать BottomNavigationBar корректно — он просто не обновляет выбранный элемент.

Достаточно интересно, что если я ТОЛЬКО закомментирую строку внутри обоих setState методов ( _selectedIndex = page; и _selectedIndex = value; ), но оставлю методы там, приложение все равно все равно отстает, даже если setState методы полностью пусты и ничего не обновляют …??

И это Provider версия:

На PageView :

 onPageChanged: (page) {
  Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
}
  

На BottomBarNavigation :

 onTap: _onTappedBar,
currentIndex: Provider.of<BottomNavigationBarProvider>(context).currentIndex,
  

и ниже:

 void _onTappedBar(int value) {
  Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
  pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
  

Как уже было сказано, такая же низкая, как и setState версия : (

Есть идеи о том, что вызывает это отставание и как это исправить? Я действительно не знаю, что еще попробовать.

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

1. Кажется, что есть виджет, вызывающий проблему с задержкой при перестроении. Глупый метод: удаляйте один за другим виджет, чтобы увидеть, какой из них вызывает эту проблему, или создайте версию профиля и используйте инструмент разработки, чтобы посмотреть на временную шкалу. Использовали ли вы MediaQuery.of (контекст)? В прошлый раз я столкнулся с проблемой задержки из-за этого.

Ответ №1:

Хорошо, я думаю, что мне удалось решить эту проблему, а также извлечь ценный урок о Flutter!

Я был на правильном пути с дилеммой setState / Provider — вам действительно нужно использовать Provider (или другое решение для управления состоянием), если вы хотите избежать перестройки всей страницы.

Однако этого недостаточно.

Для того, чтобы использовать модульность этой реализации, вам также необходимо извлечь соответствующий виджет (в данном случае весь BottomNavigationBar ) за пределы основного виджета. Если вы этого не сделаете, кажется, что все на главной странице все равно будет восстановлено, даже если только маленький виджет прослушивает Provider уведомления.

Итак, теперь это структура моего root_screen метода build (упрощенное содержимое тела для удобства чтения):

 Widget build(BuildContext context) {
  return Scaffold(
    body: PageView(
      controller: _pageController,
        children: <Widget>[
          HomeScreen(),
          PerformanceScreen(),
          SettingsScreen(),
        ],
      onPageChanged: (page) {
        Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = page;
      },
    );
    bottomNavigationBar: MyBottomNavigationBar(onTapped: _onTappedBar),
  );
}
  

Обратите внимание, что bottomNavigationBar: параметр больше не определен в этом root_screen . Вместо этого я создал новый класс (a StatelessWidget ) в отдельном файле Dart, который принимает onTapped функцию в качестве параметра, и я создаю его экземпляр отсюда.

Указанная _onTappedBar функция определена прямо здесь, на root_screen , чуть ниже build метода:

 void _onTappedBar(int value) {
  Provider.of<BottomNavigationBarProvider>(context, listen: false).currentIndex = value;
  _pageController.animateToPage(value, duration: Duration(milliseconds: 500), curve: Curves.ease);
}
  

И это отдельный файл Dart, содержащий новый MyBottomNavigationBar класс:

 class MyBottomNavigationBar extends StatelessWidget {
  @override
  const MyBottomNavigationBar({
    Key key,
    @required this.onTapped,
  }) : super(key: key);
  final Function onTapped;

  Widget build(BuildContext context) {
    return BottomNavigationBar(
      items: [
        BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
        BottomNavigationBarItem(
            icon: Icon(Icons.trending_up), title: Text('Performance')),
        BottomNavigationBarItem(
            icon: Icon(Icons.settings), title: Text('Settings')),
      ],
      onTap: onTapped,
      currentIndex:
          Provider.of<BottomNavigationBarProvider>(context).currentIndex,
    );
  }
}
  

Также для полноты картины (и потому, что мне абсолютно необходимо было знать), я попробовал снова использовать setState подход, сохранив BottomNavigationBar его в новом отдельном файле. Я хотел понять, достаточно ли простого извлечения виджетов для достижения цели, или вам все еще нужно использовать решение для управления состоянием, несмотря ни на что.

Оказывается … этого было недостаточно! Производительность при использовании setState снова была ужасной, даже несмотря на то, что виджет BottomNavigationBar был извлечен из его собственного файла класса.

Итак, в итоге, чтобы сохранить эффективность вашего приложения и плавность анимации, не забудьте извлечь виджеты и максимально модулировать код Flutter, а также использовать решение для управления состоянием вместо setState . Кажется, это единственный способ избежать ненужных перерисовок (и ваш код, очевидно, будет намного чище и его будет легче отлаживать).