技術共有

Flutter が部分リフレッシュを実装するためのいくつかの方法

2024-07-12

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

目次

序文

1. ローカルリフレッシュの重要性

1.コンセプト

2. 重要性

2. 部分リフレッシュを実装するいくつかの方法

1. setState メソッドを使用して部分更新を行う

2. StatefulWidget と InheritedWidget を使用して UI を部分的に更新します

3.ValueNotifierとValueListenableBuilder

4.ストリームビルダー

5.プロバイダー

6.Xを取得する

7. GlobalKey を使用する


序文

Flutter における状態管理とは、アプリケーション内のデータの状態を管理および更新し、状態の変化に応じて UI を更新する方法を指します。効果的な状態管理は、開発者がより効率的で保守しやすいアプリケーションを作成するのに役立ちます。

setState は、Flutter の最も基本的な状態管理メソッドです。状態が変化すると、フレームワークに UI を再構築するように通知されます。もちろん、setState メソッドを呼び出すとページが再描画されることはわかっていますが、ページ レイアウトがより複雑な場合、現時点では setState メソッドを使用する場合、別の UI を更新するだけで済みます。現在のページ 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 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. 監視対象の 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<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 を更新する必要がある場合は、ストリームにイベントを送信し、StreamBuilder を使用してストリームをリッスンすることができます。新しいイベントを受信すると、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 という 2 つの変数があります。コードは次のように定義されています。

  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.Xを取得する

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 で部分更新を実装する方法は他にもいくつかあります。ドキュメントを参照してください。ここで紹介するのは実装アイデアの 1 つです。

7. GlobalKey を使用する

上記の 3 つの実装方法はすべてフレームワークを通じて実装されます。このフレームワークをインポートしたくない場合は、GlobalKey を使用して UI の部分更新機能を実装できます。

アプリケーション全体で一意のキー GlobalKey は、関連する要素へのアクセスを提供します。BuildContext 。のためにStatefulWidgets、GlobalKey も提供しますStateアクセス。

タイマーのデモでは、GlobalKey を通じて UI を部分的に更新する場合、まず部分的に更新するウィジェットを取り出し、それを別のコンポーネントにカプセル化します。

完全なコードは次のとおりです。部分的に更新されるウィジェットをカプセル化し、内部データを更新するためのインターフェイス 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. }

次に、メイン インターフェイスで 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. }