Partage de technologie

Plusieurs façons pour Flutter d'implémenter une actualisation partielle

2024-07-12

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

Table des matières

Préface

1. L’importance du rafraîchissement local

1. Concept

2. Importance

2. Plusieurs façons de mettre en œuvre un rafraîchissement partiel

1. Utilisez la méthode setState pour une actualisation partielle

2. Utilisez StatefulWidget et InheritedWidget pour actualiser partiellement l'interface utilisateur

3.ValueNotifier et ValueListenableBuilder

4.StreamBuilder

5. Fournisseur

6.GetX

7. Utilisez GlobalKey


Préface

Dans Flutter, la gestion de l'état fait référence à la manière de gérer et de mettre à jour l'état des données dans l'application, ainsi que de mettre à jour l'interface utilisateur en fonction des changements d'état. Une gestion efficace de l'état peut aider les développeurs à créer des applications plus efficaces et plus maintenables.

setState est la méthode de gestion d'état la plus basique dans Flutter. Lorsque l'état change, le framework sera informé de la reconstruction de l'interface utilisateur. Bien sûr, nous savons que lorsque nous appelons la méthode setState, la page sera redessinée. Lorsque la mise en page est plus complexe, il suffit parfois de mettre à jour une interface utilisateur distincte. À ce stade, si nous utilisons la méthode setState, elle aura. de meilleures performances. Consommer pour redessiner l'interface utilisateur de la page actuelle.

Alors, quelles méthodes existe-t-il dans Flutter pour actualiser partiellement l'interface utilisateur ? Ce blog répertorie plusieurs façons permettant à Flutter d'implémenter une actualisation partielle.

1. L’importance du rafraîchissement local

1. Concept

L'actualisation partielle fait référence à l'actualisation d'une seule partie de l'interface, et non de la page entière. Cela améliore les performances et l’expérience utilisateur.

2. Importance

  1. Évitez les redessins inutiles et améliorez les performances
  2. Offrir une expérience utilisateur plus fluide
  3. Réduire la consommation de ressources

2. Plusieurs façons de mettre en œuvre un rafraîchissement partiel

1. Utilisez la méthode setState pour une actualisation partielle

setState est la méthode de gestion d'état la plus couramment utilisée dans Flutter. Elle est utilisée pour notifier le framework des changements d'état, conduisant à la reconstruction de l'interface.

Lorsque nous créons un projet Flutter, l'exemple du timer généré par le système par défaut est l'exemple du rafraîchissement partiel 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. }

Figure 1. Actualisation partielle setState

Lorsque la page est relativement simple, vous pouvez directement utiliser la méthode setState pour actualiser partiellement l'interface utilisateur.

Scénarios d'utilisation : changements d'état simples, tels que le nombre de clics sur les boutons, l'état du commutateur, etc.

Précautions:

  1. Des appels fréquents à setState peuvent entraîner des problèmes de performances
  2. Évitez d'appeler setState dans la méthode build

2. Utilisez StatefulWidget et InheritedWidget pour actualiser partiellement l'interface utilisateur

        StatefulWidget est un composant avec état,InheritedWidget Utilisé pour partager des données dans l'arborescence des composants.

Lorsque nous avons besoin de partager des données, nous pouvons envisager StatefulWidget et InheritedWidget pour actualiser partiellement l'interface utilisateur.

Le code complet est le suivant :

Figure 2. Actualiser l'interface utilisateur en partageant des données

  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. }

Les principaux scénarios d'utilisation de cette méthode sont les suivants : lors du partage de l'état dans l'arborescence des composants, comme le thème, les paramètres de langue, etc.

L'avantage est que le partage des données est pratique et que l'introduction du code

L’inconvénient est qu’il est compliqué à utiliser et que les performances peuvent être affectées.

3.ValueNotifier et ValueListenableBuilder

        ValueNotifier C'est un outil simple de gestion de statut,ValueListenableBuilder pour la surveillanceValueNotifier Le changement.

L'utilisation est également très simple :

1. Instancier ValueNotifier

2. L'objet Widget à surveiller est encapsulé avec ValueListenableBuilder

3. Comment modifier les données déclenchées par un événement

Par rapport aux méthodes précédentes, cette méthode est très simple et facile à utiliser, et ses performances sont également très élevées.

Inconvénients : ne peut gérer que de simples changements d’état

Le code complet est le suivant :

  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 est un objet utilisé pour diffuser des événements asynchrones, et les événements peuvent être envoyés via StreamController. Lorsque vous devez actualiser l'interface utilisateur, vous pouvez envoyer un événement au Stream, puis utiliser StreamBuilder pour écouter le Stream. Lorsqu'un nouvel événement est reçu, StreamBuilder reconstruira automatiquement l'interface utilisateur. Cette méthode convient aux situations dans lesquelles plusieurs événements asynchrones doivent être surveillés.

Lorsque nous devons traiter des flux de données asynchrones, tels que des requêtes réseau, des données en temps réel, etc., nous pouvons envisager d'utiliser StreamBuilder. Par exemple, dans l'exemple suivant, nous avons écrit une méthode asynchrone qui simule une requête réseau. Lorsque la requête réseau ne renvoie pas le résultat correct, nous pouvons charger la barre de progression.

L'avantage de cette méthode est qu'elle permet de contrôler plus précisément les requêtes asynchrones, comme l'état des requêtes réseau, etc. Cependant, Dior est plus complexe et peut nécessiter plus de code.

Le code complet est le suivant :

  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. Fournisseur

       Provider Il s'agit d'une solution de gestion d'état recommandée par Flutter.Consumer Utilisé pour lire et surveiller l’état.

Prenons également la minuterie comme exemple.

1. Nous importons d’abord le fournisseur.

fournisseur: ^6.1.2

2. Personnalisez la classe ChangeNotifier.

ChangeNotifier est une classe simple du SDK Flutter. Il est utilisé pour envoyer des notifications aux auditeurs. En d’autres termes, s’il est défini comme ChangeNotifier, vous pouvez vous abonner à ses changements d’état. (Ceci est similaire au modèle d'observateur familier).

Dans le code que nous voulons implémenter, il y a deux variables _counter1 et _counter2. Le code est défini comme suit :

  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. Utilisez Customer pour envelopper le widget que nous souhaitons actualiser

  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. Le code complet est le suivant :

  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

Nous pouvons également utiliser GetX pour implémenter une actualisation partielle de l'interface utilisateur.

Installez d'abord GetX :

get: ^4.6.6

Ensuite, nous encapsulons les variables dans 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. }

Utilisez ensuite Obx pour envelopper le widget qui doit afficher la logique.

Obx(()=&gt; Text('Numéro du compteur 1 : ${controller.counter2.value}'))

Le code complet est le suivant :

  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. }

Bien sûr, il existe plusieurs autres façons d'implémenter une actualisation partielle dans GetX. Vous pouvez consulter sa documentation. Voici juste une idée de mise en œuvre.

7. Utilisez GlobalKey

Les trois méthodes d'implémentation ci-dessus sont toutes implémentées via le framework. Si vous ne souhaitez pas importer ce framework, nous pouvons utiliser GlobalKey pour implémenter la fonction d'actualisation partielle de l'interface utilisateur.

Une clé unique dans toute l'application GlobalKey identifie de manière unique les éléments GlobalKey permet d'accéder aux éléments associés, tels que.BuildContext .pourStatefulWidgets, GlobalKey fournit égalementStateAccéder.

Dans notre démo de minuterie, si nous actualisons partiellement l'interface utilisateur via GlobalKey, nous retirons d'abord le widget à actualiser partiellement et l'encapsulons dans un composant distinct.

Le code complet est le suivant. Nous encapsulons le widget pour qu'il soit partiellement actualisé et fournissons une interface pour actualiser les données internes, 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. }

Instanciez ensuite GlobaKey dans notre interface principale :

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

Dans le cas où l'interface utilisateur doit être actualisée, appelez l'interface fournie à l'étape précédente via GlobalKey et actualisez.

Le code complet est le suivant :

  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. }