前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从零开始的Flutter之旅: StatefulWidget

从零开始的Flutter之旅: StatefulWidget

作者头像
Rouse
发布2020-03-20 10:44:12
1.1K0
发布2020-03-20 10:44:12
举报
文章被收录于专栏:Android补给站

1 往期回顾

从零开始的 Flutter 之旅: StatelessWidget

在之前的文章中,我们介绍了 StatelessWidget 的特性与它在 Flutter 中的呈现原理。

这次我们接着来聊聊它的兄弟 StatefulWidget,俗称有状态小部件。

2特性

如果你看了我之前的文章,你可能已经非常熟悉无状态小部件 StatelessWidget。它们是由一个蓝图与不可变的 element 配置来实现的,实际安装到屏幕上的是各个 StatelessElement。

不可变的东西我是非常喜欢的,就像写代码一样,一旦定义了一个不可变的变量,我就不用再关心它之后的所有事情,因为它不可变的性质,致使它不会发生不可预期的问题,只需直接使用它即可。

但一个程序只有不可变的配置是不行的,我们不可能编写一个只绘制一次后就停止的应用。因为一旦数据改变,不可变的配置是不可能帮助我们刷新 ui,达到我们预期的效果;而有状态小部件 StatefulWidget 却可以轻松解决这些事情。

StatefulWidget 提供不可变的配置信息以及可以随着时间变化而触发的状态对象;通过监听状态的变化来达到 ui 的更新。

简单点,我们从flutter_github(文章底部会给出链接)项目中挑选一个实例。

当我们点击其中一个未读通知信息时,我们需要将其 ui 状态变成已读的样式。根据状态来改变 ui,StatefulWidget 能够很好的实现这种场景。来看一下其实现

代码语言:javascript
复制
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 也是一样,它继承于 State

代码语言:javascript
复制
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 方法。

代码语言:javascript
复制
 _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,它们之间的区别显而易见了。

3呈现原理

与 StatelessWidget 一样,接下来看下 StatefulWidget 的呈现原理。

StatefulWidget 也是继承于 Widget,所以它的内部也是存在 createElement 方法。本质也是通过 createElement 来创建对应的 Element Tree,只不过创建的是 StatefulElement;然后再调用对应的 Widget Tree 中的 build 方法来获取相应的蓝图。

但与 StatelessWidget 所不同的是,它还有另外一个方法

代码语言:javascript
复制
  @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

代码语言:javascript
复制
  @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是一个不错的选择。

4下期预告

从零开始的 Flutter 之旅: InheritWidget

flutter_github: https://github.com/idisfkj/flutter_github

AwesomeGithub: https://github.com/idisfkj/AwesomeGithub

喜欢请点关注

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Android补给站 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档