• Flutter实现局部刷新的几种方式


    目录

    前言

    1.局部刷新的重要性

    1.概念

    2.重要性

    2.局部刷新实现的几种方式

    1.使用setState方法进行局部刷新

    2.使用StatefulWidget和InheritedWidget局部刷新UI

    3.ValueNotifier和ValueListenableBuilder

    4.StreamBuilder

    5.Provider

    6.GetX

    7.使用GlobalKey


    前言

           在 Flutter 中,状态管理指的是如何管理和更新应用中的数据状态,并且根据状态的变化来更新 UI。有效的状态管理能够帮助开发者创建更高效、更可维护的应用。

            setState是 Flutter 中最基本的状态管理方法,当状态发生变更的时候,会通知框架重新构建UI。当然我们知道当我们调用setState方法的时候,页面会重绘,当页面布局比较复杂的时候,有时候我们仅仅需要更新某个单独的UI,这个时候如果使用setState方法,则会有比较大的性能消耗去重绘当前页面UI.

            那么Flutter中有哪些方法可以局部刷新UI呢,这篇博客列举了Flutter实现局部刷新的几种方式。

    1.局部刷新的重要性

    1.概念

            局部刷新指的是只刷新界面的一部分,而不是整个页面。这样可以提高性能和用户体验。

    2.重要性

    1. 避免不必要的重绘,提高性能
    2. 提供更流畅的用户体验
    3. 减少资源消耗

    2.局部刷新实现的几种方式

    1.通过setState局部刷新

            setState是Flutter 中最常用的状态管理方法,用于通知框架状态发生变化,导致界面重建。

            我们创建Flutter工程的时候,系统默认生成的计时器的例子,就是setState局部刷新的例子。

    1. import 'package:flutter/material.dart';
    2. class StatePartialRefreshPage extends StatefulWidget {
    3. const StatePartialRefreshPage({super.key});
    4. @override
    5. State createState() =>
    6. _StatePartialRefreshPageState();
    7. }
    8. class _StatePartialRefreshPageState extends State<StatePartialRefreshPage> {
    9. int _count = 0;
    10. @override
    11. Widget build(BuildContext context) {
    12. return Scaffold(
    13. appBar: AppBar(
    14. title: const Text(
    15. "setState局部刷新",
    16. style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
    17. ),
    18. ),
    19. body: Center(
    20. child: Column(
    21. mainAxisAlignment: MainAxisAlignment.center,
    22. children: [
    23. Text(
    24. '您点击了$_count次',
    25. style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
    26. ),
    27. const SizedBox(
    28. height: 20,
    29. ),
    30. FilledButton(
    31. onPressed: () {
    32. setState(() {
    33. _count++;
    34. });
    35. },
    36. child: const Icon(Icons.add)),
    37. ],
    38. )),
    39. );
    40. }
    41. }

            图1.setState局部刷新

             当页面比较简单的时候,可以直接使用setState方法局部刷新UI。

            使用场景:简单的状态变化,如按钮点击计数、开关状态等。

            注意事项:

    1. 频繁调用 setState 可能导致性能问题
    2. 避免在 build 方法中调用 setState 

    2.使用StatefulWidget和InheritedWidget局部刷新UI

            StatefulWidget 是具有状态的组件,InheritedWidget 用于在组件树中共享数据。

            当我们需要共享数据的时候,可以考虑StatefulWidget和InheritedWidget局部刷新UI.

            完整代码如下:

            图2.共享数据的方式刷新UI

    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();
    15. }
    16. }
    17. class InheritedWidgetPage extends StatefulWidget {
    18. final String title;
    19. const InheritedWidgetPage({super.key, required this.title});
    20. @override
    21. State 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 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发送事件。在需要刷新UI的地方,可以发送一个事件到Stream,然后使用StreamBuilder监听该Stream,当收到新的事件时,StreamBuilder会自动重新构建UI。这种方式适用于需要监听多个异步事件的情况。

            当我们需要处理异步数据流,如网络请求、实时数据等的时候,可以考虑使用StreamBuilder。例如在下面的例子中,我们写了一个模拟网络请求的异步方法,当网络请求没返回正确结果的时候,我们可以加载进度条。

            这种方式的优点就是可以对异步请求进行更加精准的控制,例如网络请求的状态等。却迪奥就是复杂度比较高,可能需要更多的代码。        

            完整的代码如下:

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

           Provider 是 Flutter 推荐的状态管理解决方案,Consumer 用于读取和监听状态。

            我们还以定时器为例。

            1.首先我们导入Provider.

    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把我们要刷新的Widget包裹起来

    1. Consumer(
    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 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: [
    34. const SizedBox(height: 10,), // 添加一些间距
    35. const Divider(),
    36. const Text('计数器实例',style: TextStyle(fontSize: 12,fontWeight: FontWeight.bold),),
    37. Consumer(
    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(
    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实现UI的局部刷新。

            首先安装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把需要显示逻辑的Widget包裹起来。

    Obx(()=> 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: [
    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来实现UI的局部刷新功能。

            在整个应用程序中是唯一的Key GlobalKey可以唯一标识元素,GlobalKey提供了对这些元素相关联的访问,比如BuildContext。对于StatefulWidgets,GlobalKey也提供对State的访问。

           在我们的计时器的demo中,如果我们通过GlobalKey的方式局部刷新UI,首先我们把要局部刷新的Widget提出来,单独封装成一个组件。

            完整代码如下,我们封装要局部刷新的Widget,并且提供一个刷新内部数据的接口,onPressed.

    1. class CounterText extends StatefulWidget {
    2. const CounterText(Key key) : super(key: key);
    3. @override
    4. State 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 textKey = GlobalKey();
    4. GlobalKey textKey2 = GlobalKey();

                在需要刷新UI的事件中,通过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 createState() => _GlobalKeyRefreshPageState();
    7. }
    8. class _GlobalKeyRefreshPageState extends State<GlobalKeyRefreshPage> {
    9. int _count = 0;
    10. int _count2 = 0;
    11. //包裹你定义的需要更新的weight
    12. GlobalKey textKey = GlobalKey();
    13. GlobalKey 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: [
    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 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. }

  • 相关阅读:
    解放双手!一键助你快速发圈、批量加好友,好用哭了!
    使用Vue + vue-i18n搭建国际化网站
    TCP的三次握手和4次挥手
    Ceph RBD 的实现原理与常规操作
    YOLOv8改进 | 注意力机制 | 添加混合局部通道注意力——MLCA【原理讲解】
    Python "爬虫"出发前的装备之二数据先行( Requests 模块)
    docker介绍及入门举例
    Linux文件查找、别名、用户组
    【Java每日一题】— —第十九题:用二维数组存放九九乘法表,并将其输出。(2023.10.03)
    6、Netty ByteBuf工作原理
  • 原文地址:https://blog.csdn.net/ZCC361571217/article/details/140239268