likes
comments
collection
share

flutter从provider看flutter页面刷新的两种机制

作者站长头像
站长
· 阅读数 18

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(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 的管理机制,我们用的是 _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(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!); //创建 Counter 对象
          }
        
         _removeListener ??= delegate.startListening?.call(element!, _value as T); //Counter添加监听函数 counter数据改变通过notifyListeners通知监听函数
 
         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使用

无论是StateFullWidget的 state调用setState从而调用 _element!.markNeedsBuild(); 还是provider的ChangeNotifierProvider( SingleChildStatelessWidget 包装 InheritedWidget 调用 markNeedsNotifyDependents 的) elememt 调用 markNeedsBuild(); 都是调用 markNeedsBuild来通知BuildOwner标记为脏元素来重新构建界面树。

StateFullwidget通过state持有状态,provider 通过 InheritedWidget 获取数据并传递。

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

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

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

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