Compartir tecnología

Varias formas para que Flutter implemente una actualización parcial

2024-07-12

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

Tabla de contenido

Prefacio

1. La importancia de la actualización local

1. Concepto

2. Importancia

2. Varias formas de implementar una actualización parcial

1. Utilice el método setState para una actualización parcial

2. Utilice StatefulWidget y InheritedWidget para actualizar parcialmente la interfaz de usuario

3. ValueNotifier y ValueListenableBuilder

4. Constructor de transmisiones

5. Proveedor

6. Obtener X

7. Utilice la clave global


Prefacio

En Flutter, la gestión del estado se refiere a cómo gestionar y actualizar el estado de los datos en la aplicación y actualizar la interfaz de usuario de acuerdo con los cambios de estado. La gestión estatal eficaz puede ayudar a los desarrolladores a crear aplicaciones más eficientes y fáciles de mantener.

setState es el método de gestión de estado más básico en Flutter. Cuando el estado cambia, se notificará al marco para que reconstruya la interfaz de usuario. Por supuesto, sabemos que cuando llamamos al método setState, la página se volverá a dibujar. Cuando el diseño de la página es más complejo, a veces solo necesitamos actualizar una interfaz de usuario separada. En este momento, si usamos el método setState, tendrá. mayor rendimiento. Consumir para volver a dibujar la interfaz de usuario de la página actual.

Entonces, ¿qué métodos existen en Flutter para actualizar parcialmente la interfaz de usuario? Este blog enumera varias formas en que Flutter implementa la actualización parcial.

1. La importancia de la actualización local

1. Concepto

La actualización parcial se refiere a actualizar solo una parte de la interfaz, no toda la página. Esto mejora el rendimiento y la experiencia del usuario.

2. Importancia

  1. Evite redibujos innecesarios y mejore el rendimiento
  2. Proporcionar una experiencia de usuario más fluida
  3. Reducir el consumo de recursos

2. Varias formas de implementar una actualización parcial

1. Utilice el método setState para una actualización parcial

setState es el método de gestión de estado más utilizado en Flutter. Se utiliza para notificar al marco los cambios de estado, lo que lleva a la reconstrucción de la interfaz.

Cuando creamos un proyecto Flutter, el ejemplo del temporizador generado por el sistema de forma predeterminada es el ejemplo de actualización parcial de 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. }

Figura 1.Actualización parcial de setState

Cuando la página es relativamente simple, puede usar directamente el método setState para actualizar parcialmente la interfaz de usuario.

Escenarios de uso: cambios de estado simples, como recuento de clics en botones, cambio de estado, etc.

Precauciones:

  1. Las llamadas frecuentes a setState pueden causar problemas de rendimiento
  2. Evite llamar a setState en el método de compilación

2. Utilice StatefulWidget y InheritedWidget para actualizar parcialmente la interfaz de usuario

        StatefulWidget es un componente con estado,InheritedWidget Se utiliza para compartir datos dentro del árbol de componentes.

Cuando necesitamos compartir datos, podemos considerar StatefulWidget y InheritedWidget para actualizar parcialmente la interfaz de usuario.

El código completo es el siguiente:

Figura 2. Actualizar la interfaz de usuario compartiendo datos

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

Los principales escenarios de uso de este método son los siguientes: cuando se comparte el estado en el árbol de componentes, como el tema, la configuración de idioma, etc.

La ventaja es que compartir datos es conveniente y la introducción del código.

La desventaja es que es complicado de usar y el rendimiento puede verse afectado.

3. ValueNotifier y ValueListenableBuilder

        ValueNotifier Es una sencilla herramienta de gestión de estado,ValueListenableBuilder para la supervisiónValueNotifier El cambio.

El uso también es muy sencillo:

1. Crear una instancia de ValueNotifier

2. El objeto Widget a monitorear está empaquetado con ValueListenableBuilder

3. Cómo cambiar los datos activados por eventos

En comparación con los métodos anteriores, este método es muy simple y fácil de usar, y su rendimiento también es muy alto.

Desventajas: solo puede manejar cambios de estado simples

El código completo es el siguiente:

  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. Constructor de transmisiones

Stream es un objeto que se utiliza para entregar eventos asincrónicos y los eventos se pueden enviar a través de StreamController. Cuando necesite actualizar la interfaz de usuario, puede enviar un evento a Stream y luego usar StreamBuilder para escuchar Stream. Cuando se recibe un nuevo evento, StreamBuilder reconstruirá automáticamente la interfaz de usuario. Este método es adecuado para situaciones en las que es necesario monitorear múltiples eventos asincrónicos.

Cuando necesitamos procesar flujos de datos asincrónicos, como solicitudes de red, datos en tiempo real, etc., podemos considerar usar StreamBuilder. Por ejemplo, en el siguiente ejemplo, escribimos un método asincrónico que simula una solicitud de red. Cuando la solicitud de red no devuelve el resultado correcto, podemos cargar la barra de progreso.

La ventaja de este método es que puede controlar con mayor precisión las solicitudes asincrónicas, como el estado de las solicitudes de red, etc. Sin embargo, Dior es más complejo y puede requerir más código.

El código completo es el siguiente:

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

       Provider Es una solución de gestión estatal recomendada por Flutter.Consumer Se utiliza para leer y monitorear el estado.

Tomemos también el cronómetro como ejemplo.

1. Primero importamos el Proveedor.

Proveedor: ^6.1.2

2. Personalice la clase ChangeNotifier.

ChangeNotifier es una clase simple en Flutter SDK. Se utiliza para enviar notificaciones a los oyentes. En otras palabras, si se define como ChangeNotifier, puede suscribirse a sus cambios de estado. (Esto es similar al patrón familiar del observador).

En el código que queremos implementar, hay dos variables _counter1 y _counter2. El código se define de la siguiente manera:

  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. Utilice Cliente para ajustar el widget que queremos actualizar.

  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. El código completo es el siguiente:

  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. Obtener X

También podemos usar GetX para implementar una actualización parcial de la interfaz de usuario.

Primero instale GetX:

get: ^4.6.6

Luego encapsulamos las variables en 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. }

Luego use Obx para ajustar el widget que necesita mostrar lógica.

Obx(()=&gt; Text('Número del contador 1: ${controller.counter2.value}'))

El código completo es el siguiente:

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

Por supuesto, hay varias otras formas de implementar una actualización parcial en GetX. Puede consultar su documentación. Aquí hay sólo una idea de implementación.

7. Utilice la clave global

Los tres métodos de implementación anteriores se implementan a través del marco. Si no desea importar este marco, podemos usar GlobalKey para implementar la función de actualización parcial de la interfaz de usuario.

Una clave que es única en toda la aplicación GlobalKey identifica de forma única los elementos GlobalKey proporciona acceso a los elementos asociados, como por ejemplo.BuildContext .paraStatefulWidgets, GlobalKey también proporcionaStateAcceso.

En nuestra demostración del temporizador, si actualizamos parcialmente la interfaz de usuario a través de GlobalKey, primero sacamos el widget para actualizarlo parcialmente y lo encapsulamos en un componente separado.

El código completo es el siguiente. Encapsulamos el widget para que se actualice parcialmente y proporcionamos una interfaz para actualizar los datos internos, 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. }

Luego crea una instancia de GlobaKey en nuestra interfaz principal:

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

En caso de que sea necesario actualizar la interfaz de usuario, llame a la interfaz proporcionada en el paso anterior a través de GlobalKey y actualice.

El código completo es el siguiente:

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