기술나눔

Flutter가 부분 새로 고침을 구현하는 여러 가지 방법

2024-07-12

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

목차

머리말

1. 로컬 리프레시(Local Refresh)의 중요성

1. 컨셉

2. 중요성

2. 부분 새로 고침을 구현하는 여러 가지 방법

1. 부분 새로 고침을 위해 setState 메소드를 사용하십시오.

2. StatefulWidget 및 InheritedWidget을 사용하여 UI를 부분적으로 새로 고침

3.ValueNotifier와 ValueListenableBuilder

4.스트림빌더

5. 제공자

6.GetX

7. 글로벌키 사용


머리말

Flutter에서 상태 관리는 애플리케이션의 데이터 상태를 관리 및 업데이트하고 상태 변경에 따라 UI를 업데이트하는 방법을 나타냅니다. 효과적인 상태 관리는 개발자가 보다 효율적이고 유지 관리가 가능한 애플리케이션을 만드는 데 도움이 됩니다.

setState는 Flutter의 가장 기본적인 상태 관리 방법으로, 상태가 변경되면 프레임워크에 UI를 다시 빌드하라는 알림이 전달됩니다. 물론 우리는 setState 메소드를 호출하면 페이지가 다시 그려질 것이라는 것을 알고 있습니다. 페이지 레이아웃이 더 복잡해지면 때때로 별도의 UI만 업데이트해야 할 수도 있습니다. 현재 페이지 UI를 다시 그리는 데 더 많은 성능이 사용됩니다.

그렇다면 Flutter에는 UI를 부분적으로 새로 고치는 방법이 무엇입니까? 이 블로그에는 Flutter가 부분 새로 고침을 구현하는 여러 가지 방법이 나열되어 있습니다.

1. 로컬 리프레시(Local Refresh)의 중요성

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 메소드를 직접 사용하여 UI를 부분적으로 새로 고칠 수 있습니다.

사용 시나리오: 버튼 클릭 횟수, 스위치 상태 등과 같은 간단한 상태 변경

지침:

  1. setState를 자주 호출하면 성능 문제가 발생할 수 있음
  2. 빌드 메소드에서 setState를 호출하지 마세요.

2. StatefulWidget 및 InheritedWidget을 사용하여 UI를 부분적으로 새로 고침

        StatefulWidget 상태가 있는 구성요소입니다.InheritedWidget 구성 요소 트리 내에서 데이터를 공유하는 데 사용됩니다.

데이터를 공유해야 할 경우 UI를 부분적으로 새로 고치기 위해 StatefulWidget 및 InheritedWidget을 고려할 수 있습니다.

전체 코드는 다음과 같습니다.

그림 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<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. 모니터링할 위젯 개체는 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.스트림빌더

Stream은 비동기 이벤트를 전달하는 데 사용되는 객체이며 StreamController를 통해 이벤트를 보낼 수 있습니다. UI를 새로 고쳐야 하는 경우 Stream으로 이벤트를 보낸 다음 StreamBuilder를 사용하여 Stream을 수신할 수 있습니다. 새 이벤트가 수신되면 StreamBuilder가 자동으로 UI를 다시 빌드합니다. 이 방법은 여러 비동기 이벤트를 모니터링해야 하는 상황에 적합합니다.

네트워크 요청, 실시간 데이터 등과 같은 비동기 데이터 스트림을 처리해야 하는 경우 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. 먼저 공급자를 가져옵니다.

제공자: ^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를 사용하여 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를 사용하여 로직을 표시해야 하는 위젯을 래핑합니다.

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를 사용하여 UI의 부분 새로 고침 기능을 구현할 수 있습니다.

애플리케이션 전체에서 고유한 키 GlobalKey는 다음과 같은 관련 요소에 대한 액세스를 제공합니다.BuildContext .~을 위한StatefulWidgets, GlobalKey는 또한 다음을 제공합니다.State입장.

타이머 데모에서 GlobalKey를 통해 UI를 부분적으로 새로 고치면 먼저 부분적으로 새로 고쳐질 위젯을 꺼내 별도의 구성 요소로 캡슐화합니다.

전체 코드는 다음과 같습니다. 부분적으로 새로 고쳐질 위젯을 캡슐화하고 내부 데이터를 새로 고치는 인터페이스를 제공합니다.

  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();

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