從零開始的Flutter之旅: StatelessWidgetgit
在以前的文章中,咱們介紹了StatelessWidget的特性與它在Flutter中的呈現原理。github
此次咱們接着來聊聊它的兄弟StatefulWidget,俗稱有狀態小部件。segmentfault
若是你看了我以前的文章,你可能已經很是熟悉無狀態小部件StatelessWidget。它們是由一個藍圖與不可變的element配置來實現的,實際安裝到屏幕上的是各個StatelessElement。服務器
不可變的東西我是很是喜歡的,就像寫代碼同樣,一旦定義了一個不可變的變量,我就不用再關心它以後的全部事情,由於它不可變的性質,導致它不會發生不可預期的問題,只需直接使用它便可。微信
但一個程序只有不可變的配置是不行的,咱們不可能編寫一個只繪製一次後就中止的應用。由於一旦數據改變,不可變的配置是不可能幫助咱們刷新ui,達到咱們預期的效果;而有狀態小部件StatefulWidget卻能夠輕鬆解決這些事情。網絡
StatefulWidget提供不可變的配置信息以及能夠隨着時間變化而觸發的狀態對象;經過監聽狀態的變化來達到ui的更新。架構
簡單點,咱們從flutter_github挑選一個實例。框架
當咱們點擊其中一個未讀通知信息時,咱們須要將其ui狀態變成已讀的樣式。根據狀態來改變ui,StatefulWidget可以很好的實現這種場景。來看一下其實現less
class NotificationTabPage extends BasePage<_NotificationPageState> { const NotificationTabPage(); @override _NotificationPageState createBaseState() => _NotificationPageState(); } class _NotificationPageState extends BaseState<NotificationVM, NotificationTabPage> { @override NotificationVM createVM() => NotificationVM(context); @override Widget createContentWidget() { return RefreshIndicator( onRefresh: vm.handlerRefresh, child: Scrollbar( child: ListView.builder( itemCount: vm.notifications?.length ?? 0, itemBuilder: (BuildContext context, int index) { final NotificationModel item = vm.notifications[index]; return GestureDetector( onTap: () { vm.contentTap(index); }, child: Container( color: item.unread ? Colors.white : Color.fromARGB(13, 0, 0, 0), padding: EdgeInsets.only(left: 15.0, top: 10.0, right: 15.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( item.repository.fullName, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 16.0, color: item.unread ? Colors.black87 : Color.fromARGB(255, 102, 102, 102), ), ), Row( children: <Widget>[ Padding( padding: EdgeInsets.only(top: 5.0), child: Image.asset( vm.getTypeFlagSrc(item.subject.type), width: 18.0, height: 18.0, ), ), Expanded( child: Padding( padding: EdgeInsets.only(top: 5.0, left: 10.0), child: Text( item.subject.title, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( fontSize: 14.0, color: item.unread ? Color.fromARGB(255, 17, 17, 17) : Color.fromARGB(255, 102, 102, 102), ), ), ), ), ], ), Padding( padding: EdgeInsets.only(top: 10.0), child: Divider( height: 1.0, endIndent: 0.0, color: Color.fromARGB(255, 207, 216, 220), ), ), ], ), ), ); }, ), ), ); } }
這裏的BasePage是MSVM
架構中的基類,它繼承於StatefulWidget;_NotificationPageState也是同樣,它繼承於Stateasync
abstract class BasePage<S extends BaseState> extends StatefulWidget { ... } abstract class BaseState<VM extends BaseVM, T extends StatefulWidget> extends State implements VMSContract { ... }
關於MSVM後續會專門開文章介紹,想了解的能夠期待一下
咱們來看createContentWidget方法中的佈局,找到上述狀況關聯的ui,在ListView的item中。
item佈局的狀態是根據item.unread來判斷的,未讀狀態爲ture。
當用戶onTap點擊時,將會向服務器發送thread閱讀請求,當請求成功以後,再將相應位置的item.unread值改成false。
但就這樣改變你會發現ui是不會刷新的,由於在StatefulWidget,若是你想改變某個值,同時要同步更新ui,須要使用setState方法。
_markThreadRead(int index) async { try { Response response = await dio.patch('/notifications/threads/${_notifications[index].id}'); if (response != null && response.statusCode >= 200 && response.statusCode < 300) { _notifications[index].unread = false; notifyStateChanged(); } } on DioError catch (e) { Toast.show('makThreadRead error: ${e.message}', context); } }
這裏將setState方法封裝到notifyStateChanged方法中。因此如今再回過去看ui,會發現ui已經刷新了。
以上是使用StatefulWidget來達到ui的動態改變。再對比於以前的StatelessWidget,它們之間的區別顯而易見了。
與StatelessWidget同樣,接下來看下StatefulWidget的呈現原理。
StatefulWidget也是繼承於Widget,因此它的內部也是存在createElement方法。本質也是經過createElement來建立對應的Element Tree,只不過建立的是StatefulElement;而後再調用對應的Widget Tree中的build方法來獲取相應的藍圖。
但與StatelessWidget所不一樣的是,它還有另一個方法
@protected State createState();
經過createState來建立對應的State。StatefulWidget保留了StatelessWidget的特性,即保證final數據的不變性,而對於非final可變數據,將經過Stete進行管理。
上面是以前StatelessWidget呈現原理圖,下面來對照看下StatefulWidget的。
除了Widget Tree與Element Tree,還有對應的State,它管理着可變的數據,例如item.unread。
一旦item.unread改變了,且通知到State,State將會再下一幀從新要求Widget Tree進行刷新。從新構建一個Container
因爲是同一種類型Container,將會直接被替換,同時使用更新後的item.unread,因此對應的Container的color也將發生改變。最終呈現的是佈局的刷新。
值得一提的是,State依附於Element Tree中,因此它的生命週期很是長,即便Widget Tree中的NotificationTabPage被移除重建,只要保證重建的類型是一致的,同時Widget Tree 與Element Tree的對應位置是沒有變化的,那麼Widget能夠避免重建,只是會將其標記爲髒狀態,而後它的子widget將會經過build方法進行重建,替換State中的變化的值。
若是你要監聽Widget的變化,能夠重寫didUpdateWidget
@override void didUpdateWidget(StatefulWidget oldWidget) { // TODO: implement didUpdateWidget super.didUpdateWidget(oldWidget); }
綜上所述,StatefulWidget使你能夠隨時跟蹤數據的變化並更新應用的ui。但你深刻Flutter以後,你會發現本身寫的更多的是StatelessWidget,由於須要用到的StatefulWidget基本上已經實現了,咱們更多的是對StatelessWidget的封裝,是否是頗有意思呢,期待你的加入。
文中的代碼都是來自於flutter_github,這是一個基於Flutter的Github客戶端同時支持Android與IOS,支持帳戶密碼與認證登錄。使用dart語言進行開發,項目架構是基於Model/State/ViewModel的MSVM;使用Navigator進行頁面的跳轉;網絡框架使用了dio。項目正在持續更新中,感興趣的能夠關注一下。
固然若是你想了解Android原生,相信flutter_github的純Android版本AwesomeGithub是一個不錯的選擇。
從零開始的Flutter之旅: InheritWidget
若是你喜歡個人文章模式,或者對我接下來的文章感興趣,能夠點擊一下個人頭像進行關注,固然您也能夠關注我微信公衆號:【Android補給站】
或者掃描下方二維碼,與我創建有效的溝通,同時更快更準的收到個人更新推送。