从零开始的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补给站】

或者扫描下方二维码,与我创建有效的沟通,同时更快更准的收到个人更新推送。

相关文章
相关标签/搜索