2023-11-16 flutter从provider看flutter页面刷新的两种机制

1,调用setState方法刷新界面

使用 StatefulWidget 作为页面,StatefulWidget基类实现为如下:

abstract class StatefulWidget extends Widget {
  
  @override
  StatefulElement createElement() => StatefulElement(this);

 
  @protected
  @factory
  State createState();  
}

通过createState方法创建state,调用state的build方法创建视图:

//使用 StatefulElement element类型
class StatefulElement extends ComponentElement {
  // state
  State<StatefulWidget> get state => _state!;
  //调用state的build方法创建视图
  Widget build() => state.build(this);
}

调用state的setState方法刷新界面:

 class State<T extends StatefulWidget> .. {
//..
 
  void setState(VoidCallback fn) {
    
    _element!.markNeedsBuild();
  }
///
}

我们看到就是调用 element的markNeedsBuild方法刷新界面
element:

abstract class Element extends DiagnosticableTree implements BuildContext {

//标记Element 为dirty
 void markNeedsBuild() {
   
    if (dirty) {
      return;
    }
    _dirty = true;
    owner!.scheduleBuildFor(this);
  }

}

owner核心实现如下:

class BuildOwner {
 //...
//
 void scheduleBuildFor(Element element) {
    
    _dirtyElements.add(element);
    element._inDirtyList = true;
 
  }
}

了解了这种调用原理,我们就可以在咱们实现的statefullwidget的state中,手动调用下面方法刷新界面:

  (context as Element).owner!.scheduleBuildFor(this);

效果是跟调用setState一样的。

2,provider简单实例

一个使用provider的简单页面代码如下:


class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知观察者状态已更改
  }
}

class ProvdiderTestPage1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Counter(), // 创建Counter实例并提供给整个应用程序
      child: Scaffold(body: ProvdierPage1()),
    );
  }
}
class ProvdierPage1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 使用Provider.of获取Counter实例
    final counter = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Provider 示例'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '计数器值:',
              style: TextStyle(fontSize: 20),
            ),
            Text(
              '${counter.count}',
              style: TextStyle(fontSize: 40),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 调用Counter实例的increment方法
          counter.increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

通过上面页面,我们发现provider的调用为如下:

  • ChangeNotifierProvider
  • Provider.of<Counter>(context);
  • counter.increment();
  • notifyListeners(); // 通知观察者状态已更改

3,使用provider刷新界面

下面我们看一下provider原理

provider没有使用StatefulWidget 和State
核心区别就是没有使用State类作为管理数据变化。

class ProvdiderTestPage1 extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return ChangeNotifierProvider(
     create: (context) => Counter(), // 创建Counter实例并提供给整个应用程序
     child: Scaffold(body: ProvdierPage1()),
   );
 }
}

看下这个ChangeNotifierProvider实现:

// 继承 ListenableProvider
class ChangeNotifierProvider<T extends ChangeNotifier?>
    extends ListenableProvider<T> {
 
}

继续看父类:

//ChangeNotifierProvider继承ListenableProvider
class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {

}
//ListenableProvider继承InheritedProvider:
class InheritedProvider<T> extends SingleChildStatelessWidget {
}
 
abstract class SingleChildStatelessWidget extends StatelessWidget
    implements SingleChildWidget {
}

所以我们的页面widget实际上是一个StatelessWidget类型的。

那么这是如何做到数据刷新页面刷新的呢?
先看页面如何build的

abstract class SingleChildStatelessWidget extends StatelessWidget
    implements SingleChildWidget {
 

  @override
  Widget build(BuildContext context) => buildWithChild(context, _child);
 
}

build为调用buildWithChild ,子类实现为:

class InheritedProvider<T> extends SingleChildStatelessWidget {
   
  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
 
    return _InheritedProviderScope<T?>(
      owner: this,
      debugType: kDebugMode ? '$runtimeType' : '',
      child: builder != null
          ? Builder(
              builder: (context) => builder!(context, child),
            )
          : child!,
    );
  }
}

我们创建 ChangeNotifierProvider 时的 build 就是在这里被调用。

通过组合包装,在SingleChildStatelessWidget类中 build返回的一个 _InheritedProviderScope 类型的widget:

class _InheritedProviderScope<T> extends InheritedWidget {
  
}

abstract class InheritedWidget extends ProxyWidget {
}

abstract class ProxyWidget extends Widget {
}

相比于StatefulWidget+StatefulElement+ State<T extends StatefulWidget> 的管理机制,我们用的是 _InheritedProviderScopeElement类型的element对象:

class _InheritedProviderScope<T> extends InheritedWidget {
 
  @override
  _InheritedProviderScopeElement<T> createElement() {
    return _InheritedProviderScopeElement<T>(this);
  }
}

_InheritedProviderScopeElement实现如下:

class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {
 
  @override
  _InheritedProviderScope<T> get widget =>
      super.widget as _InheritedProviderScope<T>;
  

  @override
  void markNeedsNotifyDependents() {
    if (!_isNotifyDependentsEnabled) {
      return;
    }

    markNeedsBuild();
    _shouldNotifyDependents = true;
  }
 
}

看到这里,跟StatefullWidget一样,也是通过markNeedsBuild方法 来标记dirty来完成界面刷新的

回到一开始,刷新数据我们是通过 notifyListeners 方法进行。

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners(); // 通知观察者状态已更改
  }
} 

notifyListeners 是怎么走到markNeedsBuild呢?

先看下:

Provider.of<Counter>(context);

因为使用ChangeNotifierProvider包裹,所以Provider.of<Counter>(context);

调用create:

 ChangeNotifierProvider(
      create: (context) => Counter(), // 创建Counter实例并提供给整个应用程序
      child: Scaffold(body: ProvdierPage1()),
    );

_CreateInheritedProviderState

  class _CreateInheritedProviderState<T>
    extends _DelegateState<T, _CreateInheritedProvider<T>> {


       T get value {
        

         if (!_didInitValue) {
           _didInitValue = true;
           if (delegate.create != null) {
          
               _value = delegate.create!(element!);
          }
        
         _removeListener ??= delegate.startListening?.call(element!, _value as T);
 
         return _value as T;
       }
}

ListenableProvider

  class ListenableProvider<T extends Listenable?> extends InheritedProvider<T> {
 
       static VoidCallback _startListening(
         InheritedContext<Listenable?> e,
         Listenable? value,
       ) {
         value?.addListener(e.markNeedsNotifyDependents);
         return () => value?.removeListener(e.markNeedsNotifyDependents);
       }
 
  }

InheritedContext

e.markNeedsNotifyDependents

abstract class InheritedContext<T> extends BuildContext {

    void markNeedsNotifyDependents();
}

_InheritedProviderScopeElement

class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T> {

  @override
  void markNeedsNotifyDependents() {
    if (!_isNotifyDependentsEnabled) {
      return;
    }

    markNeedsBuild();
    _shouldNotifyDependents = true;
  }

}

所以不难看出,counter对象状态改变,都将会通知到 InheritedContext 的markNeedsNotifyDependents 函数从而调用markNeedsBuild方法.

class _InheritedProviderScopeElement<T> extends InheritedElement
    implements InheritedContext<T>
class InheritedElement extends ProxyElement 
abstract class ProxyElement extends ComponentElement
abstract class ComponentElement extends Element
abstract class Element extends DiagnosticableTree implements BuildContext
BuildContext:
  void markNeedsBuild() {
    owner!.scheduleBuildFor(this);
  }

总结

provider使用

provider 是Flutter中一种常用的状态管理库,它通过使用 ChangeNotifier 或其他类似的可监听对象,使得在状态改变时能够通知相关的观察者进行界面刷新。下面是 provider 更新界面的基本机制:

  • ChangeNotifier: ChangeNotifier 是 provider 的核心。它是一个轻量级的类,用于表示应用程序状态。当状态发生变化时,ChangeNotifier 会调用 notifyListeners() 方法。

  • Provider: Provider 是一个用于管理和获取状态的类。它接收一个 ChangeNotifier 的实例,并将它提供给整个应用程序的小部件树。

  • Consumer Widget: Consumer 是一个小部件,它订阅 ChangeNotifier 的变化。当 ChangeNotifier 调用 notifyListeners() 时,与 Consumer 关联的小部件会被重新构建,从而刷新界面。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容