從零開始的Flutter之旅: StatefulWidget

往期回顧

從零開始的Flutter之旅: StatelessWidgetgit

在以前的文章中,咱們介紹了StatelessWidget的特性與它在Flutter中的呈現原理。github

此次咱們接着來聊聊它的兄弟StatefulWidget,俗稱有狀態小部件。bash

特性

若是你看了我以前的文章,你可能已經很是熟悉無狀態小部件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補給站】

或者掃描下方二維碼,與我創建有效的溝通,同時更快更準的收到個人更新推送。

相關文章
相關標籤/搜索