Обмен технологиями

Несколько способов Flutter реализовать частичное обновление

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Оглавление

Предисловие

1. Важность локального обновления

1. Концепция

2. Важность

2. Несколько способов реализовать частичное обновление

1. Используйте метод setState для частичного обновления.

2. Используйте StatefulWidget и InheritedWidget, чтобы частично обновить пользовательский интерфейс.

3.ValueNotifier и ValueListenableBuilder

4.StreamBuilder

5.Поставщик

6.GetX

7. Используйте глобальный ключ


Предисловие

Во Flutter управление состоянием означает управление и обновление состояния данных в приложении, а также обновление пользовательского интерфейса в соответствии с изменениями состояния. Эффективное управление состоянием может помочь разработчикам создавать более эффективные и удобные в обслуживании приложения.

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

Итак, какие методы существуют во Flutter для частичного обновления пользовательского интерфейса. В этом блоге перечислено несколько способов реализации частичного обновления во Flutter.

1. Важность локального обновления

1. Концепция

Частичное обновление означает обновление только части интерфейса, а не всей страницы. Это улучшает производительность и удобство использования.

2. Важность

  1. Избегайте ненужных перерисовок и повышайте производительность
  2. Обеспечьте более плавный пользовательский опыт
  3. Уменьшить потребление ресурсов

2. Несколько способов реализовать частичное обновление

1. Используйте метод setState для частичного обновления.

setState — наиболее часто используемый метод управления состоянием во Flutter. Он используется для уведомления платформы об изменениях состояния, что приводит к реконструкции интерфейса.

Когда мы создаем проект Flutter, примером таймера, генерируемого системой по умолчанию, является пример частичного обновления setState.

  1. import 'package:flutter/material.dart';
  2. class SetStateMainPage extends StatefulWidget {
  3. final String title;
  4. const SetStateMainPage({super.key, required this.title});
  5. @override
  6. State<SetStateMainPage> createState() => _SetStateMainPageMainPageState();
  7. }
  8. class _SetStateMainPageMainPageState extends State<SetStateMainPage> {
  9. int _count = 0;
  10. @override
  11. Widget build(BuildContext context) {
  12. return Scaffold(
  13. appBar: AppBar(
  14. title: Text(widget.title),
  15. ),
  16. body: Center(
  17. child: Text(
  18. '您点击了$_count次',
  19. style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
  20. ),
  21. ),
  22. floatingActionButton: FloatingActionButton(
  23. child: const Icon(Icons.add),
  24. onPressed: () {
  25. setState(() {
  26. _count++;
  27. });
  28. },
  29. )
  30. );
  31. }
  32. }

Рисунок 1. Частичное обновление setState

Если страница относительно простая, вы можете напрямую использовать метод setState, чтобы частично обновить пользовательский интерфейс.

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

Меры предосторожности:

  1. Частые вызовы setState могут вызвать проблемы с производительностью.
  2. Избегайте вызова setState в методе сборки.

2. Используйте StatefulWidget и InheritedWidget, чтобы частично обновить пользовательский интерфейс.

        StatefulWidget является компонентом с состоянием,InheritedWidget Используется для обмена данными в дереве компонентов.

Когда нам нужно поделиться данными, мы можем использовать StatefulWidget и InheritedWidget для частичного обновления пользовательского интерфейса.

Полный код выглядит следующим образом:

Рисунок 2. Обновление пользовательского интерфейса путем обмена данными

  1. import 'package:flutter/material.dart';
  2. class MyInheritedWidget extends InheritedWidget {
  3. final int counter;
  4. const MyInheritedWidget({
  5. super.key,
  6. required this.counter,
  7. required super.child,
  8. });
  9. @override
  10. bool updateShouldNotify(covariant InheritedWidget oldWidget) {
  11. return true;
  12. }
  13. static MyInheritedWidget? of(BuildContext context) {
  14. return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  15. }
  16. }
  17. class InheritedWidgetPage extends StatefulWidget {
  18. final String title;
  19. const InheritedWidgetPage({super.key, required this.title});
  20. @override
  21. State<InheritedWidgetPage> createState() => _InheritedWidgetPageState();
  22. }
  23. class _InheritedWidgetPageState extends State<InheritedWidgetPage> {
  24. int counter = 0;
  25. void _incrementCounter() {
  26. setState(() {
  27. counter++;
  28. });
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. return MyInheritedWidget(
  33. counter: counter,
  34. child: Scaffold(
  35. appBar: AppBar(
  36. title: Text(widget.title),
  37. ),
  38. body: Center(child: Column(
  39. children: [
  40. const Divider(),
  41. const CounterDisplay(),
  42. const SizedBox(height: 20),
  43. ElevatedButton(
  44. onPressed: _incrementCounter,
  45. child: const Text('add'),
  46. ),
  47. ],
  48. ),),
  49. ),
  50. );
  51. }
  52. }
  53. class CounterDisplay extends StatelessWidget {
  54. const CounterDisplay({super.key});
  55. @override
  56. Widget build(BuildContext context) {
  57. final inheritedWidget = MyInheritedWidget.of(context);
  58. return Text('点击次数: ${inheritedWidget?.counter}');
  59. }
  60. }

Основные сценарии использования этого метода следующие: при совместном использовании состояний в дереве компонентов, таких как тема, настройки языка и т. д.

Преимущество в том, что обмен данными удобен, а введение кода

Недостатком является то, что его сложно использовать и это может повлиять на производительность.

3.ValueNotifier и ValueListenableBuilder

        ValueNotifier Это простой инструмент управления статусом,ValueListenableBuilder для мониторингаValueNotifier Изменение.

Использование также очень простое:

1. Создать экземпляр ValueNotifier

2. Объект Widget, который нужно отслеживать, обернут ValueListenableBuilder.

3. Как изменить данные, инициированные событием

По сравнению с предыдущими методами этот метод очень прост и удобен в использовании, а его производительность также очень высока.

Недостатки: Может обрабатывать только простые изменения состояния.

Полный код выглядит следующим образом:

  1. import 'package:flutter/material.dart';
  2. class ValueNotifierPage extends StatefulWidget {
  3. final String title;
  4. const ValueNotifierPage({super.key, required this.title});
  5. @override
  6. State<ValueNotifierPage> createState() => _ValueNotifierPageState();
  7. }
  8. class _ValueNotifierPageState extends State<ValueNotifierPage> {
  9. final ValueNotifier<int> _counter = ValueNotifier<int>(0);
  10. @override
  11. Widget build(BuildContext context) {
  12. return Scaffold(
  13. appBar: AppBar(
  14. title: Text(widget.title),
  15. ),
  16. body: Center(
  17. child: ValueListenableBuilder<int>(
  18. valueListenable: _counter,
  19. builder: (context, value, child) {
  20. return Text(
  21. '您点击了$value次',
  22. style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
  23. );
  24. },
  25. )
  26. ),
  27. floatingActionButton: FloatingActionButton(
  28. child: const Icon(Icons.add),
  29. onPressed: () {
  30. _counter.value ++;
  31. },
  32. )
  33. );
  34. }
  35. }

4.StreamBuilder

Stream — это объект, используемый для доставки асинхронных событий, и события можно отправлять через StreamController. Если вам необходимо обновить пользовательский интерфейс, вы можете отправить событие в поток, а затем использовать StreamBuilder для прослушивания потока. При получении нового события StreamBuilder автоматически перестроит пользовательский интерфейс. Этот метод подходит для ситуаций, когда необходимо отслеживать несколько асинхронных событий.

Когда нам нужно обрабатывать асинхронные потоки данных, такие как сетевые запросы, данные в реальном времени и т. д., мы можем рассмотреть возможность использования StreamBuilder. Например, в следующем примере мы написали асинхронный метод, имитирующий сетевой запрос. Если сетевой запрос не возвращает правильный результат, мы можем загрузить индикатор выполнения.

Преимущество этого метода в том, что он позволяет более точно контролировать асинхронные запросы, например состояние сетевых запросов и т. д. Однако Dior более сложен и может потребовать больше кода.

Полный код выглядит следующим образом:

  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. class StreamBuilderRefreshUIPage extends StatefulWidget {
  4. final String title;
  5. const StreamBuilderRefreshUIPage({super.key, required this.title});
  6. @override
  7. State<StreamBuilderRefreshUIPage> createState() =>
  8. _StreamBuilderRefreshUIPageState();
  9. }
  10. class _StreamBuilderRefreshUIPageState extends State<StreamBuilderRefreshUIPage> {
  11. late Future<String> _data;
  12. Future<String> fetchData() async {
  13. // 模拟网络请求延迟
  14. await Future.delayed(const Duration(seconds: 2));
  15. // 返回模拟数据
  16. return 'Hello, Flutter!';
  17. }
  18. @override
  19. void initState() {
  20. // TODO: implement initState
  21. super.initState();
  22. _data = fetchData();
  23. }
  24. @override
  25. Widget build(BuildContext context) {
  26. return Scaffold(
  27. appBar: AppBar(
  28. title: Text(widget.title),
  29. ),
  30. body: Center(
  31. child: FutureBuilder<String>(
  32. future: _data,
  33. builder: (context, snapshot) {
  34. if (snapshot.connectionState == ConnectionState.waiting) {
  35. return const CircularProgressIndicator();
  36. } else if (snapshot.hasError) {
  37. return Text('Error: ${snapshot.error}');
  38. } else {
  39. return Text('Data: ${snapshot.data}');
  40. }
  41. },
  42. ),
  43. ),
  44. floatingActionButton: FloatingActionButton(
  45. onPressed: fetchData,
  46. tooltip: 'Increment',
  47. child: const Icon(Icons.add),
  48. ),
  49. );
  50. }
  51. }

5.Поставщик

       Provider Это решение для управления состоянием, рекомендованное Flutter.Consumer Используется для чтения и мониторинга состояния.

Давайте также возьмем таймер в качестве примера.

1. Сначала импортируем Provider.

провайдер: ^6.1.2

2. Настройте класс ChangeNotifier.

ChangeNotifier — это простой класс в Flutter SDK. Он используется для отправки уведомлений слушателям. Другими словами, если он определен как ChangeNotifier, вы можете подписаться на изменения его состояния. (Это похоже на знакомый шаблон наблюдателя).

В коде, который мы хотим реализовать, есть две переменные _counter1 и _counter2. Код определяется следующим образом:

  1. class CounterModel extends ChangeNotifier {
  2. int _counter1 = 0;
  3. int _counter2 = 0;
  4. void addCounter1(){
  5. debugPrint('counter:$_counter1');
  6. _counter1 += 1;
  7. notifyListeners();
  8. }
  9. void addCounter2(){
  10. debugPrint('counter:$_counter2');
  11. _counter2 += 1;
  12. notifyListeners();
  13. }
  14. }

3. Используйте Customer, чтобы обернуть виджет, который мы хотим обновить.

  1. Consumer<CounterModel>(
  2. builder: (context, counterModel, child) {
  3. return Row(
  4. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  5. children: [
  6. Text('计数器1个数: ${counterModel._counter1}'),
  7. ElevatedButton(onPressed: (){
  8. counterModel.addCounter1();
  9. }, child: const Icon(Icons.add),),
  10. ],
  11. );
  12. },
  13. ),

4. Полный код выглядит следующим образом:

  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. class ProviderRefreshPage extends StatefulWidget {
  4. final String title;
  5. const ProviderRefreshPage({super.key, required this.title});
  6. @override
  7. State<ProviderRefreshPage> createState() => _ProviderRefreshPageState();
  8. }
  9. class CounterModel extends ChangeNotifier {
  10. int _counter1 = 0;
  11. int _counter2 = 0;
  12. void addCounter1(){
  13. debugPrint('counter:$_counter1');
  14. _counter1 += 1;
  15. notifyListeners();
  16. }
  17. void addCounter2(){
  18. debugPrint('counter:$_counter2');
  19. _counter2 += 1;
  20. notifyListeners();
  21. }
  22. }
  23. class _ProviderRefreshPageState extends State<ProviderRefreshPage> {
  24. @override
  25. Widget build(BuildContext context) {
  26. return Scaffold(
  27. appBar: AppBar(
  28. title: Text(widget.title),
  29. ),
  30. body: Center(
  31. child: Column(
  32. mainAxisAlignment: MainAxisAlignment.start,
  33. children: <Widget>[
  34. const SizedBox(height: 10,), // 添加一些间距
  35. const Divider(),
  36. const Text('计数器实例',style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold),),
  37. Consumer<CounterModel>(
  38. builder: (context, counterModel, child) {
  39. return Row(
  40. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  41. children: [
  42. Text('计数器1个数: ${counterModel._counter1}'),
  43. ElevatedButton(onPressed: (){
  44. counterModel.addCounter1();
  45. }, child: const Icon(Icons.add),),
  46. ],
  47. );
  48. },
  49. ),
  50. Consumer<CounterModel>(
  51. builder: (context, counterModel, child) {
  52. return Row(
  53. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  54. children: [
  55. Text('计数器1个数: ${counterModel._counter2}'),
  56. ElevatedButton(onPressed: (){
  57. counterModel.addCounter2();
  58. }, child: const Icon(Icons.add),),
  59. ],
  60. );
  61. },
  62. ),
  63. const Divider(height: 20,),
  64. ],
  65. ),
  66. ),
  67. );
  68. }
  69. }

6.GetX

Мы также можем использовать GetX для реализации частичного обновления пользовательского интерфейса.

Сначала установите GetX:

get: ^4.6.6

Затем мы инкапсулируем переменные в GetxController.

  1. class CounterManagerController extends GetxController {
  2. final counter1 = 0.obs;
  3. final counter2 = 0.obs;
  4. void incrementCount1() {
  5. counter1.value++;
  6. }
  7. void incrementCount2() {
  8. counter2.value++;
  9. }
  10. }

Затем используйте Obx, чтобы обернуть виджет, который должен отображать логику.

Obx(()=&gt; Text('Номер счетчика 1: ${controller.counter2.value}'))

Полный код выглядит следующим образом:

  1. import 'package:flutter/material.dart';
  2. import 'package:get/get.dart';
  3. class CounterManagerController extends GetxController {
  4. final counter1 = 0.obs;
  5. final counter2 = 0.obs;
  6. void incrementCount1() {
  7. counter1.value++;
  8. }
  9. void incrementCount2() {
  10. counter2.value++;
  11. }
  12. }
  13. class GetXRefreshUIPage extends StatelessWidget {
  14. final String title;
  15. const GetXRefreshUIPage({super.key, required this.title});
  16. @override
  17. Widget build(BuildContext context) {
  18. final CounterManagerController controller = Get.put(CounterManagerController());
  19. return Scaffold(
  20. appBar: AppBar(
  21. title: Text(title),
  22. ),
  23. body: Center(
  24. child: Column(
  25. mainAxisAlignment: MainAxisAlignment.start,
  26. children: <Widget>[
  27. const SizedBox(
  28. height: 10,
  29. ), // 添加一些间距
  30. const Divider(),
  31. const Text(
  32. '计数器实例',
  33. style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
  34. ),
  35. Row(
  36. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  37. children: [
  38. Obx(()=> Text('计数器1个数: ${controller.counter1.value}')),
  39. ElevatedButton(
  40. onPressed: () {
  41. controller.incrementCount1();
  42. },
  43. child: const Icon(Icons.add),
  44. ),
  45. ],
  46. ),
  47. Row(
  48. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  49. children: [
  50. Obx(()=> Text('计数器1个数: ${controller.counter2.value}')),
  51. ElevatedButton(
  52. onPressed: () {
  53. controller.incrementCount2();
  54. },
  55. child: const Icon(Icons.add),
  56. ),
  57. ],
  58. ),
  59. const Divider(
  60. height: 20,
  61. ),
  62. ],
  63. ),
  64. ),
  65. );
  66. }
  67. }

Конечно, есть несколько других способов реализовать частичное обновление в GetX. Вы можете посмотреть его документацию. Вот лишь одна идея реализации.

7. Используйте глобальный ключ

Все три вышеуказанных метода реализации реализованы через платформу. Если вы не хотите импортировать эту платформу, мы можем использовать GlobalKey для реализации функции частичного обновления пользовательского интерфейса.

Ключ, который уникален во всем приложении. GlobalKey уникально идентифицирует элементы. GlobalKey обеспечивает доступ к связанным элементам, например:BuildContext .дляStatefulWidgets, GlobalKey также предоставляетStateДоступ.

В нашей демонстрации таймера, если мы частично обновляем пользовательский интерфейс через GlobalKey, сначала мы извлекаем виджет, который нужно частично обновить, и инкапсулируем его в отдельный компонент.

Полный код выглядит следующим образом. Мы инкапсулируем виджет для частичного обновления и предоставляем интерфейс для обновления внутренних данных onPressed.

  1. class CounterText extends StatefulWidget {
  2. const CounterText(Key key) : super(key: key);
  3. @override
  4. State<StatefulWidget> createState() {
  5. return CounterTextState();
  6. }
  7. }
  8. class CounterTextState extends State<CounterText> {
  9. String _text="0";
  10. @override
  11. Widget build(BuildContext context) {
  12. return Center(
  13. child: Text(_text,style: const TextStyle(fontSize: 20),),
  14. );
  15. }
  16. void onPressed(int count) {
  17. setState(() {
  18. _text = count.toString();
  19. });
  20. }
  21. }

Затем создайте экземпляр GlobaKey в нашем основном интерфейсе:

  1. int _count = 0;
  2. int _count2 = 0;
  3. GlobalKey<CounterTextState> textKey = GlobalKey();
  4. GlobalKey<CounterTextState> textKey2 = GlobalKey();

В случае необходимости обновления пользовательского интерфейса вызовите интерфейс, предоставленный на предыдущем шаге, через GlobalKey и обновите его.

Полный код выглядит следующим образом:

  1. import 'package:flutter/material.dart';
  2. class GlobalKeyRefreshPage extends StatefulWidget {
  3. final String title;
  4. const GlobalKeyRefreshPage({super.key, required this.title});
  5. @override
  6. State<GlobalKeyRefreshPage> createState() => _GlobalKeyRefreshPageState();
  7. }
  8. class _GlobalKeyRefreshPageState extends State<GlobalKeyRefreshPage> {
  9. int _count = 0;
  10. int _count2 = 0;
  11. //包裹你定义的需要更新的weight
  12. GlobalKey<CounterTextState> textKey = GlobalKey();
  13. GlobalKey<CounterTextState> textKey2 = GlobalKey();
  14. @override
  15. Widget build(BuildContext context) {
  16. return Scaffold(
  17. appBar: AppBar(
  18. title: Text(widget.title),
  19. ),
  20. body: Center(
  21. child: Column(
  22. mainAxisAlignment: MainAxisAlignment.start,
  23. children: <Widget>[
  24. const SizedBox(
  25. height: 10,
  26. ), // 添加一些间距
  27. const Divider(),
  28. const Text(
  29. '计数器实例',
  30. style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
  31. ),
  32. Row(
  33. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  34. children: [
  35. CounterText(textKey),
  36. ElevatedButton(
  37. onPressed: () {
  38. _count++;
  39. textKey.currentState?.onPressed(_count);
  40. },
  41. child: const Icon(Icons.add),
  42. ),
  43. ],
  44. ),
  45. Row(
  46. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  47. children: [
  48. CounterText(textKey2),
  49. ElevatedButton(
  50. onPressed: () {
  51. _count2++;
  52. textKey2.currentState?.onPressed(_count2);
  53. },
  54. child: const Icon(Icons.add),
  55. ),
  56. ],
  57. ),
  58. const Divider(
  59. height: 20,
  60. ),
  61. ],
  62. ),
  63. ),
  64. );
  65. }
  66. }
  67. class CounterText extends StatefulWidget {
  68. const CounterText(Key key) : super(key: key);
  69. @override
  70. State<StatefulWidget> createState() {
  71. return CounterTextState();
  72. }
  73. }
  74. class CounterTextState extends State<CounterText> {
  75. String _text="0";
  76. @override
  77. Widget build(BuildContext context) {
  78. return Center(
  79. child: Text(_text,style: const TextStyle(fontSize: 20),),
  80. );
  81. }
  82. void onPressed(int count) {
  83. setState(() {
  84. _text = count.toString();
  85. });
  86. }
  87. }