iOSer 的 Flutter 快速入坑之道(二)

iOSer 的 Flutter 快速入坑之道(二)

前言

本文是继上一篇的有关 flutter 的第二篇文章,由于合起来篇幅太长,所以考虑分开成多篇文章上传啦。

Flutter UI

a. Widget

在 iOS 中,大部分控件都是基于 UIView 的实例,所有的控件实例在添加到视图中后,会被作为节点添加到 UI 渲染树中。其实在 Flutter 中的 Widget 概念也是类似,都会由一棵渲染树来维护所有的 Widget 子节点。

但是不同之处在于,第一,Flutter 中所有的 widget 在它的生命周期中都是不可变的。在 iOS 中,假如我们要修改并重新渲染一个控件,可以调用 setNeedDisplay 方法,然后在这个控件的节点开始,重新跑一边所有子节点,渲染生成新的视图,但是在这个过程中,控件只是产生的属性的修改,实例却还是那个实例。然而在 Flutter 中就不同了,由于 Flutter 中的 Widget 都是不可变的,所以在当某个 Widget 需要修改的时候,Flutter 会创建一个新的 Widget 来做替换。

第二,其实在 Flutter 中,Widget 不同于 View 的另一个区别就是 Widget 并不负责任何渲染相关的事情。那么是谁在负责渲染呢?Flutter 是怎么样通过 Widget 来渲染出界面的?在这里我们就要了解 Flutter 中的 Widget、Element、RenderObject 之间的联系啦。

Widget 中包含属性 Element,Element 中又包含了 RenderObject 属性。Widget 就是一个对 Element 的配置或者描述,我们开发者也是只需要和 Widget 打交道,并不需要去维护更新渲染树。

Element 就是负责维护渲染树的实例,主要作用在 Flutter 的渲染管线 build 阶段,可以把 element 看作真正的渲染树子节点。

RenderObject 是用于渲染的对象,主要作用在 Flutter 的渲染管线 布局以及绘制 阶段,也就是它将我们的界面最终绘制在屏幕上。

关于 Flutter 的底层渲染机制,其实我认为作为每个初学 Flutter 的同学都需要了解,只有更深入的了解它的渲染机制,才能更好地去使用它。

深入了解Flutter界面开发

b.如何使用 Widget 构建界面?

在上面我们大概了解了 Widget 是什么东西,以及 flutter 的渲染机制,那么实际开发中如何使用 Widget呢?

还是从 hello world 开始吧!

import 'package:flutter/material.dart';
 void main() => runApp(new MyApp());

 class MyApp extends StatelessWidget {
 @override
  Widget build(BuildContext context) {
      return new MaterialApp(
         title: 'Welcome to Flutter',
             home: new Scaffold(
                 appBar: new AppBar(
                  title: new Text('Welcome to Flutter'),
                  ),
          body: new Center(
               child: new Text('Hello World'),
           ),
          ),
       );
      }
 }

所有的 Widget 都是通过这种嵌套的方式来表示其相互之间的关系以及配置,其实很容易懂,不懂的话多看几种 Widget 的例子就知道啦。
Flutter 中文网

c.StatelessWidget 和 StatefulWidget

StatelessWidget 是不存在中间状态的控件,也就是说当它被 new 出来之后,就不会被改变了,也无法被改变。如果需要更新展示的内容,就只能销毁掉重新 new 一个。

StatefulWidget 是可以保存中间状态的控件,控件的中间状态保存在 State 类中,通过调用 setState(){} 方法来触发更新机制,将此节点将其以下的整个子树重新更新绘制。

d.setState 机制介绍

setState 的作用是告诉框架这个 Object 的 state 已经被修改了,那么这个修改它可能会对 UI 有影响,所以需要重新 build 一遍该 Object。

首先我们来了解一下一个 [State]Object 的生命周期状态。

 enum _StateLifeCycle {
 created,
 initialized,
 ready,
 defunct,
 }

当这个[State]Object 刚被创建时,它的状态就是 created 的状态。紧接着 Object 会调用 didChangeDependencies 方法,这时候 Object 的状态会转变为 initialized,但是此时,object 并没有做好被 build 的准备。然后接下来,object 会转变为 ready 的状态,表示现在已经做好 build 的准备了。object 会保持这个状态,直到 dispose 方法被调用。一旦 object 调用了 dispose 方法,那么它的状态就会转变成 defunct 的状态。

然后我们可以看到 setState 的源码。首先 flutter 会判断 setState 是否在 Object 调用 dispose 销毁后再调用,若 state 为 defunct ,说明该 Object 已经被销毁了,报错。

  if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
      throw error;
      }

接下来我们看到 flutter 还判断了 object 的状态是否为 created 以及 是否非 mounted。那么 mounted 属性是什么呢?

mounted 属性代表的意思是这个 object 是否有被 buildContext 所持有。我们知道 mounted 有安装、加入的意思。在一个[state]Object 调用 initState 方法之前,flutter 会把该[state]Object 通过与 buildContext 关联的方式嵌入,直至 Object 调用了 dispose,也就是不会再调用 build 了,这个 Object 才不被继续持有。

所以这两个条件说明该 [state]Object 还未被加入到 widget 树中,有可能是在构造函数的时候调用了 setState,这个是没有必要的,因为 object 刚被创建的时候是被标记为 dirty 的,需要调用 build。

    final dynamic result = fn() as dynamic;

接下来是判断如果 state 中的更新函数为一个异步函数,返回值是 Future 类型的,那么也报错。

最后,才是调用了 _element.markNeedsBuild 方法。那么 markNeedsBuild 函数做了什么事情呢。首先,他会判断 state 是否为 defunct,elementLifeStyle 是否为 active 以及 owner 是否为 nil。之后,将这些节点都标记为 dirty,然后加入到全局的 widget 队列中,在下一帧的时候进行重建。

那么什么是 owner,owner 的作用是什么呢?

我们可以看到 owner 其实是一个 BuildOwner 的实例,它主要负责跟踪需要 rebuild 的 widgets。 然后在 scheduleBuildFor 方法中,首先是判断 element 的 _inDirtyList 是否为 true,如果是,就说明它已经在 dirtyList 中了,于是把它的 dirtyElementsNeedsResorting 属性设置为yes,返回。
如果一切正常,那么接下来会调用到 onBuildScheduled()方法,这是一个方法回调,是 VoidCallback 类型。

那么接着,onBuildScheduled 的回调是在哪里呢。通过搜索我们发现在 WigetsBinding 的 initInstances 方法中有赋值 onBuildScheduled 代码块。

WigetsBinding 又是什么?! 从文档中我们可以了解到,文档说 WidgetsBinding 是 wigetsLayer 和 flutter 引擎的粘合剂。。

好的,不管他,先继续看内部实现,我们继续往下点追踪到一个叫
ensureVisualUpdate 的函数,在其中的一个 case 中,我们看到了 scheduleFrame 的方法。然后再往这个方法里面看,可以看到最终它调用了 window.scheduleFrame 方法。 window 的 scheduleFrame 方法会生成一个新的帧,在这个方法调用之后,flutter 引擎会最终调用 handleBeginFrame 强制刷新一个新帧,无论之前那一帧是否已经渲染结束了。

所以到这里差不多就是 setState 调用到屏幕渲染刷新的大致过程了。整理一下整个过程中调用的函数

State.setState() -> 
Element.markNeedsBuild() -> 
BuildOwner.scheduleBuildFor() -> 
WidgetBinding._handleBuildScheduled() -> 
SchedulerBinding.ensureVisualUpdate() -> 
SchedulerBinding.scheduleFrame() -> 
Window.scheduleFrame() -> 
WidgetBinding.drawFrame()    

e.如何做控件的显示和隐藏?

我们已经了解了 flutter 中 setState 的机制以及 widget 的一些特性了,那么实际开发中碰到对 widget 进行显示隐藏控制等需求的时候,应该怎么处理呢?

大概的简单思路就是通过一个变量 t 去表示 widget 的显示和隐藏,然后再封装一个函数根据这个变量来返回需要显示的 widget 或 Container()。在 flutter 中,如果需要表示返回空的一个 widget 的时候,不要返回 null,而是返回 Container()。最后在需要修改变量 t 的时候加上 setState 代码块,表示需要重新绘制。

class _TestState extends State<Test> {
  bool toggle = true;
  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  _getToggleChild() {
    if (toggle) {
      return Text('Text');
    } else {
      return Container();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Test App"),
      ),
      body: Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        child: Icon(Icons.update),
      ),
    );
  }
}   

以上是通过一个按钮点击控制控件显示与否的简单代码,蛮看下~

最后

这边主要讲有关 flutter UI 方面的东西,当然 flutter UI 相关的东西也远远不止这些,所以如果后续有补充的地方,我也会在本文中修改跟进~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 原文在此,此处只为学习 Widget与ElementWidget主要接口Stateless WidgetState...
    lltree阅读 4,489评论 0 1
  • 作为家长您是怎么对待孩子玩手机的问题的,强制没收对孩子来说已经不起作用了,反而易造成孩子的逆反心理,今天这篇文章给...
    子曰语果阅读 479评论 0 0
  • (十二) 许白曼在晋国府上住了些许日子,她能感觉到唐予正对她的感情,只是大仇未报,自己戴罪之身也未沉冤昭雪,哪还顾...
    老死不死阅读 842评论 0 54
  • 走不动的老人,搬不走的墓碑 就让他们留守在时光里 守候流水般空寂的乡村 日暮降至 兴致而归的羊群,撵着薄薄的黄昏 ...
    莫名小情许阅读 129评论 0 0
  • 感赏自己每天坚持学习读书,每天进步那么一点点都值得高兴。 大女儿看手机时间太长,昨晚她爸爸要准备说她被我制止了,感...
    延爱阅读 151评论 0 3