0.前言
剛接觸Flutter的小夥伴在StatefulWidget控制元件時會感覺難以接受
本人一開始也是,不過對React的瞭解讓我很快理解了Flutter的狀態觀念
本篇就說一下我對StatefulWidget一族的理解,希望可以幫你解決一些疑慮
1.從Slider開始說起
也許你在第一次使用Slider的時候會碰壁,你會發現它拖不動!
但如果你比較細心可以發現監聽的值是在變化的,這跟Android是不同的
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,從而實現狀態的更新
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中元件的拼合是非常方便的,使用也很簡潔
---->[_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中你想怎麼封怎麼封,只要狀態改變,我就給你響應,這是很優雅的。
---->[_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:關於修改
你也可以很容易的通過佈局容器修改元件的相對位置
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所不能及的
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
,期待與你的交流與切磋。