[雜談]-Flutter中StatefulWidget的開啟方式

張風捷特烈發表於2019-07-20
0.前言

剛接觸Flutter的小夥伴在StatefulWidget控制元件時會感覺難以接受
本人一開始也是,不過對React的瞭解讓我很快理解了Flutter的狀態觀念
本篇就說一下我對StatefulWidget一族的理解,希望可以幫你解決一些疑慮


1.從Slider開始說起

也許你在第一次使用Slider的時候會碰壁,你會發現它拖不動!
但如果你比較細心可以發現監聽的值是在變化的,這跟Android是不同的

[雜談]-Flutter中StatefulWidget的開啟方式

var slider = Slider(
  value: 0,
  max: 100,
  min: 0,
  onChanged: (e) {
    print('onChanged:$e');
  },
  onChangeStart: (e) {
    print('onChangeStart:$e');
  },
  onChangeEnd: (e) {
    print('onChangeEnd:$e');
  });
///------主場景-------
var scaffold = Scaffold(
    body: Center(child: slider,)
);
var app = MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: scaffold,
);
void main() => runApp(app);
複製程式碼

2:如何讓Slider有用武之地

現在回想一下Android怎麼改變屬性

在Android裡控制元件修改其屬性可以直接`物件.set屬性`來設定  
但在FLutter裡你會奇怪的發現:當你`slider.value=20;`時會報錯  
這真是讓人不爽,物件更改屬性不是天經地義嗎?但Flutter說:對不起,你不能
複製程式碼

這讓我恍然大悟,為什麼Widget原始碼裡說所有的元件都是恆定的,它只是對元素的描述
元件的屬性無法被改變因為屬性都是final修飾的,既然無法修改,那又為什麼會有狀態一說?

其實恆定和變化是相對的,多個恆定的狀態的連續重演就會產生動態效果  
就像電影也只是圖片的疊加,一張圖片是恆定的,它也只是用畫素對一個場景的色彩資訊進行的描述
但多個恆定的照片連續播放時就會產生動態的效果,讓我們感覺裡面的人是活的,世界是運動的
這其中化腐朽為神奇的關鍵就是如何持續渲染,就像電影如何連續一幀幀的播放  
這時狀態類中的setState()應聲而出,交給我,只要喊我一聲,我就為你們更新狀態  
複製程式碼

這和React是如出一轍的,這種方式在我看來是非常優雅的。物件更改自身屬性與之相比就笨重了許多
前者可以通過一個狀態來表述、更新、修改自己,而後者只是能通過他本身來親力親為


3:如何正確開啟Slider

上面說需要狀態,那就需要一個StatefulWidget,如下:有一個私有的變數_value,
在Slider拖動的過程中執行_render方法進行渲染,在渲染時先將Slider的值給_value
在setState方法呼叫之後,build將會重新執行,那麼Slider的值就會使用_value,從而實現狀態的更新

[雜談]-Flutter中StatefulWidget的開啟方式

class TextSlider extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TextSliderState();
}

class _TextSliderState extends State<TextSlider> {
  double _value = 0;

  @override
  Widget build(BuildContext context) {
    var show = _buildSlider(_value);
    return show;
  }

  _buildSlider(double value) {
    var slider = Slider(
      value: value,
      max: 100,
      min: 0,
      onChanged: (e) {
        print('onChanged:$e');
        _render();
      },
      onChangeStart: (e) {
        print('onChangeStart:$e');
      },
      onChangeEnd: (e) {
        print('onChangeEnd:$e');
      },
    );
    
    return slider;
  }

  _render(double value) {
    _value = value;
    setState(() {});
  }
}
複製程式碼

4:這樣的優勢

可能你會覺得只是使用一個Slider,還要寫個類,未免有點小題大做

麻煩必然有其價值,簡單必然有其侷限。這便是宇宙的平衡。
一開始學程式設計時,定義了一個Circle類,可以用物件來算面積,
當時就想,這有必要嗎,一個方法就搞定了啊,是不是有點小題大做。
之後漸漸發現物件導向的魅力,我不知你們對萬物皆物件如何理解,這裡說一下我的看法:

萬物皆物件並不是站在人類的角度說世間的實體都是物件,而是站在另一個維度  
一個應用便是一個小世界,裡面有眾多物件相互協調合作,來完成應用的功能。
這個小世界中的一切皆為物件。Coder需要管理這些物件的樣貌,生死,家族關係,社交關係以及工作流程。
而物件的產生是要靠類來建立,所以類是至關重要的,其建立需要站在統領世界的上帝視角。
所以程式設計對我而言就是在創世,而我便是創世神,思想的高度可以讓你的眼前有一個完全不一樣的世界。  
複製程式碼

話說回來,為什麼要這樣做呢?

三個詞: 易複用、好維護、可擴充

這三個詞會伴隨Coder的程式設計生涯,如何讓自己創造的世界更好的運作,是我們殫精竭慮的  
從設計模式到資料結構,從編碼到重構,我們努力調整維持這個世界的秩序,讓它們脫離bug的魔爪  
程式導向中的零星程式碼通過一個類的整合,形成一個創物的藍圖,用來召喚(new)物件

不知你是否有所感覺,Android中控制元件用起來是比較卡手的,總的來說就是太難複用,程式碼零星  
比如,一個Slider滑動時Text跟隨顯示,在Activity中建立兩個物件,讓兩者協調,
一兩個還好,多了就會感覺分佈零散,而且冗餘難看,為此自定義一個View?還是饒了我吧  
Android中控制元件的組合感覺很笨重,就連點選一下還有先找個id,但我也此心不改,未之樂此不疲,沒辦法,這就是愛  

玩前端接觸React的時候我就像尋到新歡,React的元件非常吸引我,靈活,簡潔,優雅  
再看Android,恨鐵不成鋼。直到現在Flutter出現了,它帶著React的風采出現在移動端,甚至全端  
Flutter中對於介面感覺非常友好,雖然剛來時一堆括號的巢狀讓人難以適應,但漸漸你會發現他的美  
Widget認為介面上的元素都成為元件,使用簡單,非常容易複用。
複製程式碼

5:元件間的組合

看一下Flutter中組合Slider和Text是多麼簡潔,只要新增一些就行了
如果Android自定義這樣的控制元件,需要自定義ViewGroup,將兩個元件拼合
所以Flutter中元件的拼合是非常方便的,使用也很簡潔

[雜談]-Flutter中StatefulWidget的開啟方式

---->[_TextSliderState#build]----
var show = Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[_buildText(_value), _buildSlider(_value)],
);

_buildText(double value) {
  return Text(
    value.toStringAsFixed(2),//保留兩位有效數字
    style: TextStyle(fontSize: 20),
  );
}
複製程式碼

6:狀態的魅力

比如需要象下面這樣滑動到50之後核取方塊選中,當點選核取方塊清零
放在Android中想想都覺得凌亂,但自定義控制元件有麻煩,就像爐石起手全是高費的卡手心情
在Flutter中你想怎麼封怎麼封,只要狀態改變,我就給你響應,這是很優雅的。

[雜談]-Flutter中StatefulWidget的開啟方式

---->[_TextSliderState#build]----
var show = Column(
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        _buildCheckBox(_value),
        _buildText(_value),
      ],
    ),
    _buildSlider(_value)
  ],);

_buildCheckBox(double value) {
  var checked = value > 50;
  return Checkbox(
    value: checked,
    onChanged: (bool) {
      _render(0);
    },
  );
}
複製程式碼

只是修改巢狀是有點小麻煩,如果有類似wedgetChild.father=wedgetFather這樣的認乾爹就好了


7:關於修改

你也可以很容易的通過佈局容器修改元件的相對位置

[雜談]-Flutter中StatefulWidget的開啟方式

var show = Row(
  mainAxisSize: MainAxisSize.min,
  crossAxisAlignment: CrossAxisAlignment.center,
  children: <Widget>[
    Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        _buildCheckBox(_value),
        _buildText(_value),
      ],
    ),
    _buildSlider(_value)
  ],
);
複製程式碼

8.關於監聽

要知道你定義的每個元件都是可以拿去複用的,和Flutter原生元件地位是一樣的
我們在需要拖動的監聽,那麼就需要在渲染之前進行回撥,讓使用者可以接受回參

class TextSlider extends StatefulWidget {

  ValueChanged onChanged;
  TextSlider({this.onChanged});

  @override
  State<StatefulWidget> createState() => _TextSliderState();
}

typedef ValueChanged = void Function(double value);


  _render(double value) {
    if(widget.onChanged!=null){
      widget.onChanged(value);
    }
    _value = value;
    print(value);
    setState(() {});  }
複製程式碼

9.複用的靈活

一個元件類形成之後,複用就非常方便了,如果Android實現下面的拖動更新
邏輯上不復雜,但是程式碼將會非常多,因為Android很難複用元件,只能一個個來。
Flutter中實現起來就很簡潔,甚至監聽也非常方便。比如下面的:
短短几行程式碼就實現了四個的各自拖動監聽,這是笨重的xml所不能及的

[雜談]-Flutter中StatefulWidget的開啟方式

var a = (a) {
  print("a:$a");
};

var b = (b) {
  print("b:$b");
};

var c = (c) {
  print("c:$c");
};

var d = (d) {
  print("d:$d");
};

var childs= [a,b,c,d].map((fun){
  return SizedBox.fromSize(size: Size(250, 100), child: TextSlider(onChanged: fun,),);
}).toList();

var scaffold = Scaffold(
    body: Center(child: Wrap(children:childs,),)
);
複製程式碼

10.小結

Flutter針對介面是非常友好的,它可以作為Android檢視很好地補充。
更不用說Flutter強大的跨平臺能力,它已成為一顆新星,正冉冉升起。
你還在等什麼,見證一下Flutter的魅力吧,相信你會喜歡上它的。


結語

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

相關文章