Flutter資料&狀態管理之[- InheritedWidget -]

張風捷特烈發表於2019-08-18

一個介面是由眾多元件拼組而成。經常需要將一個元件進行封裝,但此時有一個問題,如何讓多個元件去共享一些值。

比如下面在一個_State中使用了WidgetA元件,傳入_incrementCounter自加的方法和_counter計數值。
WidgetA又是由下面若干個自定義的Widget組成。那麼問題來了,WidgetK點選時如何讓WidgetE中的值+1?
一個最直接的方法就是通過建構函式將變數和函式一層層向下傳遞。你也許會說WTF,你好像在逗我笑?

Flutter資料&狀態管理之[- InheritedWidget -]


1.現在來模擬一下這個情景

將計數器的頁面分成五個部分,分別用一個Widget來控制。

WidgetA:控制檢視總體顯示   依賴WidgetB和WidgetF
WidgetB:控制檢視佈局排布   依賴WidgetC
WidgetC:控制檢視內容組成   依賴WidgetD
WidgetD:控制檢視計數器使用
WidgetF:控制檢視觸發計數
複製程式碼

現在要讓WidgetF的點選被WidgetD響應,下面是最笨的解決方案:構造傳參,一層層傳遞。雖然麻煩,但又不是不能用。

Flutter資料&狀態管理之[- InheritedWidget -]

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  @override
  Widget build(BuildContext context) {
    return WidgetA(_counter,_incrementCounter,widget.title);
  }
  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
}

class WidgetA extends StatelessWidget {
  WidgetA(this.counter,this.increment,this.title);
  final int counter;
  final VoidCallback increment;
  final String title;
  @override
  Widget build(BuildContext context) {
    var result= Scaffold(
      appBar: AppBar(title: Text(title),),
      body: WidgetB(counter),
      floatingActionButton: WidgetF(increment),
    );
    return result;
  }
}

class WidgetB extends StatelessWidget {
  WidgetB(this.counter);
  final int counter;
  @override
  Widget build(BuildContext context) {
    var center= Center(
      child: WidgetC(counter),
    );
    return center;
  }
}

class WidgetC extends StatelessWidget {
  WidgetC(this.counter);
  final int counter;
  @override
  Widget build(BuildContext context) {
    var column=Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:',),
        WidgetD(counter)
      ],
    );
    return column;
  }
}

class WidgetD extends StatelessWidget {
  WidgetD(this.counter);
  final int counter;
  @override
  Widget build(BuildContext context) {
    var text= Text('$counter', style: Theme.of(context).textTheme.display1,);
    return text;
  }
}

class WidgetF extends StatelessWidget {
  WidgetF(this.increment);
  final VoidCallback increment;
  @override
  Widget build(BuildContext context) {
    var button= FloatingActionButton(
      onPressed: increment,
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
    return button;
  }
}
複製程式碼

是不是感覺非常麻煩呢? 如何處理這種情況,能讓需要的引數不這麼跋山涉水?


2.第一個解決方案:InheritedWidget

這裡我們使用一個InheritedWidget來提供資料和方法,讓她們共享與五個元件之中。就像下面這樣,將值儲存於一個InheritedWidget中,隨用隨取。這樣世界終於清靜了,不用構造傳值滿天飛。

Flutter資料&狀態管理之[- InheritedWidget -]

/// 資料模型
class CountModel {
  final int count;//計數器
  final VoidCallback increment;//增長函式
  const CountModel(this.count,this.increment);
}

class CountWidget extends InheritedWidget {
  final CountModel model;
  CountWidget({
    Key key,
    @required this.model,
    @required Widget child,
  }) : super(key: key, child: child);

  static CountWidget of(BuildContext context) {//提供資料模型方法
    return context.inheritFromWidgetOfExactType(CountWidget);
  }

  //是否重建widget就取決於資料是否相同
  @override
  bool updateShouldNotify(CountWidget oldWidget) {
    return model.count != oldWidget.model.count;
  }
}
複製程式碼

再看一下現在每個子元件的實現,就無需把需要的引數一層層往下傳。
如果你的封裝層級較深,InheritedWidget將是你資料傳遞的好幫手。

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return
      CountWidget(child:  WidgetA(widget.title),model: CountModel(_counter, _incrementCounter),);
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
}

class WidgetA extends StatelessWidget {
  WidgetA(this.title);
  final String title;
  @override
  Widget build(BuildContext context) {
    var result= Scaffold(
      appBar: AppBar(title: Text(title),),
      body: WidgetB(),
      floatingActionButton: WidgetF(),
    );
    return result;
  }
}

class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var center= Center(
      child: WidgetC(),
    );
    return center;
  }
}

class WidgetC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var column=Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('You have pushed the button this many times:',),
        WidgetD()
      ],
    );
    return column;
  }
}

class WidgetD extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var counter= CountWidget.of(context).model.count;
    var text= Text('$counter', style: Theme.of(context).textTheme.display1,);
    return text;
  }
}

class WidgetF extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var increment= CountWidget.of(context).model.increment;
    var button= FloatingActionButton(
      onPressed: increment,
      tooltip: 'Increment',
      child: Icon(Icons.add),
    );
    return button;
  }
}
複製程式碼

結語

本文到此接近尾聲了,如果想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;如果想細細探究它,那就跟隨我的腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流群,歡迎小夥伴加入,共同探討Flutter的問題,本人微訊號:zdl1994328,期待與你的交流與切磋。

下一篇,將為你帶來如何對當前程式碼進行優化,讓狀態量更容易管理,敬請期待。

相關文章