Compartilhamento de tecnologia

Várias maneiras de o Flutter implementar atualização parcial

2024-07-12

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

Índice

Prefácio

1. A importância da atualização local

1. Conceito

2. Importância

2. Várias maneiras de implementar atualização parcial

1. Use o método setState para atualização parcial

2. Use StatefulWidget e InheritedWidget para atualizar parcialmente a IU

3. ValueNotifier e ValueListenableBuilder

4. Construtor de fluxo

5.Provedor

6.Obtenha X

7. Use GlobalKey


Prefácio

No Flutter, o gerenciamento de estado refere-se a como gerenciar e atualizar o estado dos dados no aplicativo e atualizar a IU de acordo com as mudanças no estado. O gerenciamento de estado eficaz pode ajudar os desenvolvedores a criar aplicativos mais eficientes e de fácil manutenção.

setState é o método de gerenciamento de estado mais básico no Flutter. Quando o estado muda, a estrutura será notificada para reconstruir a IU. Claro que sabemos que quando chamamos o método setState, a página será redesenhada. Quando o layout da página for mais complexo, às vezes só precisaremos atualizar uma UI separada. Neste momento, se usarmos o método setState, ela terá. maior desempenho. Consumir para redesenhar a IU da página atual.

Então, quais métodos existem no Flutter para atualizar parcialmente a IU. Este blog lista várias maneiras de o Flutter implementar a atualização parcial.

1. A importância da atualização local

1. Conceito

A atualização parcial refere-se à atualização apenas de parte da interface, não da página inteira. Isso melhora o desempenho e a experiência do usuário.

2. Importância

  1. Evite redesenhos desnecessários e melhore o desempenho
  2. Fornece uma experiência de usuário mais tranquila
  3. Reduza o consumo de recursos

2. Várias maneiras de implementar atualização parcial

1. Use o método setState para atualização parcial

setState é o método de gerenciamento de estado mais comumente usado no Flutter. Ele é usado para notificar a estrutura sobre mudanças de estado, levando à reconstrução da interface.

Quando criamos um projeto Flutter, o exemplo do timer gerado pelo sistema por padrão é o exemplo da atualização parcial 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. atualização parcial setState

Quando a página é relativamente simples, você pode usar diretamente o método setState para atualizar parcialmente a IU.

Cenários de uso: mudanças simples de estado, como contagem de cliques em botões, estado de troca, etc.

Precauções:

  1. Chamadas frequentes para setState podem causar problemas de desempenho
  2. Evite chamar setState no método build

2. Use StatefulWidget e InheritedWidget para atualizar parcialmente a IU

        StatefulWidget é um componente com estado,InheritedWidget Usado para compartilhar dados na árvore de componentes.

Quando precisarmos compartilhar dados, podemos considerar StatefulWidget e InheritedWidget para atualizar parcialmente a IU.

O código completo é o seguinte:

Figura 2. Atualize a IU compartilhando dados

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

Os principais cenários de uso deste método são os seguintes: ao compartilhar estado na árvore de componentes, como tema, configurações de idioma, etc.

A vantagem é que o compartilhamento de dados é conveniente e a introdução do código

A desvantagem é que é complicado de usar e o desempenho pode ser afetado.

3. ValueNotifier e ValueListenableBuilder

        ValueNotifier É uma ferramenta simples de gerenciamento de status,ValueListenableBuilder para monitorarValueNotifier O troco.

O uso também é muito simples:

1. Instanciar ValueNotifier

2. O objeto Widget a ser monitorado é empacotado com ValueListenableBuilder

3. Como alterar dados acionados por eventos

Comparado com os métodos anteriores, este método é muito simples e fácil de usar e seu desempenho também é muito alto.

Desvantagens: só pode lidar com mudanças simples de estado

O código completo é o seguinte:

  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. Construtor de fluxo

Stream é um objeto usado para entregar eventos assíncronos e os eventos podem ser enviados por meio de StreamController. Quando precisar atualizar a IU, você pode enviar um evento para o Stream e, em seguida, usar o StreamBuilder para ouvir o Stream. Quando um novo evento for recebido, o StreamBuilder reconstruirá automaticamente a IU. Este método é adequado para situações em que vários eventos assíncronos precisam ser monitorados.

Quando precisamos processar fluxos de dados assíncronos, como solicitações de rede, dados em tempo real, etc., podemos considerar o uso do StreamBuilder. Por exemplo, no exemplo a seguir, escrevemos um método assíncrono que simula uma solicitação de rede. Quando a solicitação de rede não retorna o resultado correto, podemos carregar a barra de progresso.

A vantagem deste método é que ele pode controlar solicitações assíncronas com mais precisão, como o status das solicitações de rede, etc. No entanto, Dior é mais complexo e pode exigir mais código.

O código completo é o seguinte:

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

       Provider É uma solução de gerenciamento de estado recomendada pela Flutter.Consumer Usado para ler e monitorar o status.

Tomemos também o cronômetro como exemplo.

1. Primeiro importamos o Provedor.

provedor: ^6.1.2

2. Personalize a classe ChangeNotifier.

ChangeNotifier é uma classe simples no Flutter SDK. É usado para enviar notificações aos ouvintes. Em outras palavras, se definido como ChangeNotifier, você poderá assinar suas alterações de estado. (Isto é semelhante ao padrão de observador familiar).

No código que queremos implementar, existem duas variáveis ​​_counter1 e _counter2. O código é definido da seguinte forma:

  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. Use o Cliente para agrupar o widget que queremos atualizar

  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. O código completo é o seguinte:

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

Também podemos usar GetX para implementar a atualização parcial da IU.

Primeiro instale o GetX:

get: ^4.6.6

Em seguida, encapsulamos as variáveis ​​em 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. }

Em seguida, use Obx para agrupar o widget que precisa exibir a lógica.

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

O código completo é o seguinte:

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

Claro, existem várias outras maneiras de implementar a atualização parcial no GetX. Você pode dar uma olhada em sua documentação. Aqui está apenas uma ideia de implementação.

7. Use GlobalKey

Os três métodos de implementação acima são todos implementados por meio da estrutura. Se você não quiser importar esta estrutura, podemos usar GlobalKey para implementar a função de atualização parcial da IU.

Uma chave exclusiva em todo o aplicativo GlobalKey identifica exclusivamente os elementos. GlobalKey fornece acesso aos elementos associados, como.BuildContext .paraStatefulWidgets, a GlobalKey também forneceStateAcesso.

Em nossa demonstração do cronômetro, se atualizarmos parcialmente a IU por meio do GlobalKey, primeiro retiramos o Widget para ser parcialmente atualizado e o encapsulamos em um componente separado.

O código completo é o seguinte. Encapsulamos o widget para ser parcialmente atualizado e fornecemos uma interface para atualização de dados 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. }

Em seguida, instancie o GlobaKey em nossa interface principal:

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

Caso a IU precise ser atualizada, chame a interface fornecida na etapa anterior por meio de GlobalKey e atualize.

O código completo é o seguinte:

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