自定义Flutter控件

在Flutter开发中,我们会经常和各种控件打交道,它们也能满足业务的大部分需求。但是,我们往往需要将多个控件组合起来,才能实现业务的需求,而且这样写出来的代码维护起来非常困难。因此,我们可以把那些需要多个控件组合才能实现的功能自定义化,成为一个自定义控件,易于维护。

网络图片

无状态控件

Flutter框架给我们提供了StatelessWidget和StatefulWidget两个抽象类,用于自定义控件,首先我们看一下StatelessWidget抽象类。它可以定义一个不需要可变状态的控件,我们可以称其为“无状态控件”,它通过构建一系列其他控件来描述用户界面的一部分,构建过程以递归方式执行,直到用户界面的描述完全具体化。

比如,下面是一个名为“GreenBoard”的StatelessWidget子类的框架,它的里面包括一个Container控件,中文名称是“容器”,然后设置color属性,也就是背景色为绿色。

class GreenBoard extends StatelessWidget {
  const GreenBoard({ Key key }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: const Color(0xFF2DBD3A)
    );
  }
}

上面代码中,应用了《Dart入门—类与方法》的知识,大家可以去简单了解下。另外,我们覆盖了build这个抽象方法,该方法用于描述由此控件所实现的那一部分用户界面。其中,抽象类BuildContext是该控件在控件树中的位置句柄。

通常情况下,控件的构造函数参数不止一个,每个参数对应一个final修饰的属性,修改上一个例子,使其成为一个可以设置背景颜色和子控件的通用控件。

class Board extends StatelessWidget {
  const Board({
    Key key,
    this.color: const Color(0xFF2DBD3A),
    this.child,
  }) : super(key: key);

  final Color color;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color, 
      child: child,
    );
  }
}

按照Flutter框架的语法规范,控件的构造函数只能使用命名参数,命名参数可以使用@required注解为必需参数。另外语法规范还规定了,第一个参数是key,最后一个参数是childchildren或其他类似参数。

如果一个无状态控件的父控件会定期更改控件的配置,或者它依赖于频繁更改的继承控件,那么优化build方法的性能,以保持流畅的渲染性能是非常重要的。有以下做法可以用于减少重新构建无状态控件的影响:

  • 尽可能减少build方法传递创建的节点数量及其创建的任何控件。例如,我们可以考虑只使用Align(对齐控件)或CustomSingleChildLayout(自定义单个子控件布局控件),而不是精心安排Row(行布局控件)、Column(列布局控件)、Padding(填充控件)和SizedBox(指定大小的框控件)来定位单个子控件。想绘制正确的图形效果时,考虑一下使用CustomPaint(画布控件),而不是复杂的多个Container(容器)分层和Decoration(装饰)。
  • 尽可能使用const修饰控件,并为控件提供一个const构造函数,以便控件的调用方法也可以这样做。

有状态控件

在了解完如何使用StatelessWidget定义一个无状态控件后,我们学习如何使用StatefulWidget定义一个具有可变状态的控件,我们可以称其为“有状态控件”。首先要搞清楚的是,状态是什么?状态是在构建控件时可以同步读取的信息,并且在控件的生命周期内可以改变,控件的使用者应该在状态发生变化时使用State.setState方法及时通知框架。

StatefulWidget的实例本身是不可变的,我们需要将其可变状态存储在由createState方法创建的单独的State对象中,或者存储在该State所订阅的对象中。例如,我们将之前名为“GreenBoard”的StatelessWidget子类改造成StatefulWidget的子类框架。

class GreenBoard extends StatefulWidget {
  const GreenBoard({ Key key }) : super(key: key);

  @override
  _GreenBoardState createState() => new _GreenBoardState();
}

class _GreenBoardState extends State<GreenBoard> {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: const Color(0xFF2DBD3A)
    );
  }
}

只要我们调用了一个StatefulWidget,框架就会调用createState,这意味着,在控件树中,可能有多个不同位置的State对象与同一个StatefulWidget关联。同样的,如果一个StatefulWidget被我们从树中移除,并且再次被我们插入到树中,框架将再次调用createState来创建一个新的State对象,简化了State对象的生命周期。

我们再将之前名为“Board”的StatelessWidget子类改造成StatefulWidget的子类框架。除了可以设置背景颜色和子控件,还有一个可以被调用的,用来改变它的内部状态的方法。

class Board extends StatefulWidget {
  const Board({
    Key key,
    this.color: const Color(0xFF2DBD3A),
    this.child,
  }) : super(key: key);

  final Color color;
  final Widget child;

  _BoardState createState() => new _BoardState();
}

class _BoardState extends State<Board> {
  double _size = 1.0;

  void grow() {
    setState(() { _size += 0.1; });
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: widget.color,
      transform: new Matrix4.diagonal3Values(_size, _size, 1.0),
      child: widget.child,
    );
  }
}

StatefulWidget有两种不同的类型:

  • 第一种是在State.initState中分配资源并将其置于State.dispose中,而且不依赖于InheritedWidget或调用State.setState。这样的控件通常在应用程序或页面的根部使用,并通过ChangeNotifier、Stream或其他这样的对象与子控件通信。遵循这种模式的有状态控件相对便宜(在CPU和GPU周期方面),因为它们是一次构建的,而且不会更新。因此,它们可以实现一些非常复杂的build方法。
  • 第二种是使用State.setState或依赖于InheritedWidget的控件。这些控件通常会在应用程序的生命周期中重新构建很多次,因此降低重新构建这种控件的影响至关重要。实际上,它们也可以使用State.initState或State.didChangeDependencies来分配资源,但重点是它们要重新构建。

有以下做法可以用于减少重新构建有状态控件的影响:

  • 把状态推到树叶上。例如,我们的页面上有一个嘀嗒嘀嗒的时钟,此时不应该将状态置于页面的顶部,因为这样的话,每当时钟嘀嗒时,整个页面都会重新构建。我们应该创建一个专用的时钟控件,这样只会更新它自己。
  • 尽可能减少build方法传递创建的节点数量及其创建的任何控件。理想情况下,有状态的控件只会创建一个控件,而这个控件将是一个RenderObjectWidget。很显然,在实际开发中,我们不一定能做到这一点,但控件越接近这个理想状态,效率就越高。
  • 如果子树不更改,则缓存表示该子树的控件,并在每次使用该子树时重新使用它。重用控件的效率要比创建新的控件要高效得多,将有状态的部分分解成一个带有子参数的控件是这样做的常见方法。
  • 尽可能使用const修饰控件,这相当于缓存一个控件,并重新使用它。

关于有状态与无状态的选择

如果自定义的控件可以与用户进行交互,比如通过键盘输入内容、通过滑动屏幕移动滑块、点击时改变状态,又或者是随着时间的推移而变化,比如数据Feed会更新状态。这时我们应该选择使用StatefulWidget创建一个有状态控件。

如果自定义的控件仅依赖于对象本身的配置信息,仅仅是用于展示给定的信息。那我们应该选择使用StatelessWidget创建一个无状态控件。

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

推荐阅读更多精彩内容

  • 无状态控件 Flutter框架给我们提供了StatelessWidget和StatefulWidget两个抽象类,...
    雪纳瑞的哈士奇阅读 263评论 0 0
  • 1.前言 本文涵盖了Widget,State,BuildContext,InheritedWidget等术语的相关...
    巧巧的二表哥阅读 2,594评论 0 5
  • Flutter中Widget,State和BuildContext的概念是每个Flutter开发人员需要完全理解的...
    DramaScript阅读 5,495评论 0 1
  • 本文主要介绍了Flutter布局相关的内容,对相关知识点进行了梳理,并从实际例子触发,进一步讲解该如何去进行布局。...
    Q吹个大气球Q阅读 9,712评论 6 51
  • 转自 Q吹个大气球Q 本文主要介绍了Flutter布局相关的内容,对相关知识点进行了梳理,并从实际例子触发,进一步...
    chilim阅读 1,916评论 0 17