Flutter实战经验(七):widgets 介绍
liuian 2025-05-21 14:57 3 浏览
Flutter 从 React 中吸取灵感,通过现代化框架创建出精美的组件。它的核心思想是用 widget 来构建你的 UI 界面。 Widget 描述了在当前的配置和状态下视图所应该呈现的样子。
当 widget 的状态改变时,它会重新构建其描述(展示的 UI),框架则会对比前后变化的不同,以确定底层渲染树从一个状态转换到下一个状态所需的最小更改。
1. Hello world
创建一个最小的 Flutter 应用简单到仅需调用 runApp() 方法并传入一个 widget 即可:
runApp() 函数会持有传入的 Widget,并且使它成为 Widget 树中的根节点。在这个例子中,Widget 树有两个 widgets, Center 及其子控件 Text 。框架会强制让根 Widget 铺满整个屏幕,也就是说“Hello World”会在屏幕上居中显示。在这个例子我们需要指定文字的方向,当使用 MaterialApp widget 时,你需要考虑这一点,之后我会进一步的描述。
在写应用的过程中,取决于是否需要管理状态,你通常会创建一个新的组件继承 StatelessWidget 或 StatefulWidget。 Widget 的主要工作是实现 build方法,build()该方法根据其它较低级别的 widget 来描述这个 Widget。框架会逐一构建这些 widget,直到最底层的描述 widget 几何形状的 RenderObject。
2. 基础 widgets
Flutter 自带了一套强大的基础 Widget,下面列出了一些常用的:
Text
Text这个 widget 可以用来在应用内创建带样式的文本。
Row, Column
这两个 flex widgets 可以让你在水平 (Row) 和垂直(Column) 方向创建灵活的布局。它是基于 web 的 flexbox 布局模型设计的。
Stack
Stack widget 不是线性(水平或垂直)定位的,而是按照绘制顺序将 widget 堆叠在一起。你可以用 Positioned widget 作为 Stack 的子 widget,以相对于 Stack 的上,右,下,左来定位它们。 Stack 是基于 Web 中的绝对位置布局模型设计的。有点类似Android的相对布局RelativeLayout。
Container
Container widget 可以用来创建一个可见的矩形元素。 Container 可以使用 BoxDecoration 来进行装饰,如背景,边框,或阴影等。 Container 还可以设置外边距、内边距和尺寸的约束条件等。另外,Container可以使用矩阵在三维空间进行转换。
下面是一些简单的 widget,它们结合了上面提到的 widget 和一些其他的 widget:
请确认在 pubspec.yaml 文件中 flutter 部分有 uses-material-design: true 这条,它能让你使用预置的 Material icons。
为了获得(MaterialApp)主题的数据,许多 Material Design 的 widget 需要在 MaterialApp 中才能显现正常。
因此,请使用 MaterialApp 运行应用。
MyAppBar 创建了一个高 56 独立像素,左右内边距 8 像素的 Container。在容器内,MyAppBar 以 Row 布局来组织它的子元素。中间的子 widget(title widget),被标记为 Expanded,这意味着它会扩展以填充其它子 widget 未使用的可用空间。你可以定义多个Expanded 子 widget,并使用 flex 参数确定它们占用可用空间的比例。
MyScaffold widget 将其子 widget 组织在垂直列中。在列的顶部,它放置一个 MyAppBar 实例,并把 Text widget 传给它来作为应用的标题。把 widget 作为参数传递给其他 widget 是一个很强大的技术,它可以让你以各种方式创建一些可重用的通用组件。最后,MyScaffold 使用 Expanded 来填充剩余空间,其中包含一个居中的消息。
3. 使用 Material 组件
Flutter 提供了许多 widget,可帮助你构建遵循 Material Design 的应用。 Material 应用以 MaterialApp widget 开始,它在你的应用的底层下构建了许多有用的 widget。这其中包括 Navigator,它管理由字符串标识的 widget 栈,也称为“routes”。 Navigator可以让你在应用的页面中平滑的切换。使用 MaterialApp widget 不是必须的,但这是一个很好的做法。
现在我们已经从 MyAppBar 和 MyScaffold 切换到了 material.dart 中的 AppBar 和 Scaffold widget,我们的应用更“Material”了一些。例如,标题栏有了阴影,标题文本会自动继承正确的样式,此外还添加了一个浮动操作按钮。
注意,widget 作为参数传递给了另外的 widget。Scaffold 将许多不同的 widget 作为命名参数,每个 widget 都放在了 Scofford 布局中的合适位置。同样的,AppBar widget 允许我们给 leading、title widget 的 actions 传递 widget。这种模式在整个框架会中重复出现,在设计自己的 widget 时可以考虑这种模式。
备忘
Material 是 Flutter 中两个自带的设计之一,如果想要以 iOS 为主的设计,可以参考 Cupertino components,它有自己版本的 CupertinoApp 和 CupertinoNavigationBar。
4. 处理手势
大多数应用都需要通过系统来处理一些用户交互。构建交互式应用程序的第一步是检测输入手势,这里通过创建一个简单的按钮来了解其工作原理:
GestureDetector widget 没有可视化的展现,但它能识别用户的手势。当用户点击 Container 时, GestureDetector 会调用其 onTap() 回调,在这里会向控制台打印一条消息。你可以使用 GestureDetector 检测各种输入的手势,包括点击,拖动和缩放。
许多 widget 使用 GestureDetector 为其他 widget 提供可选的回调。例如,IconButton、RaisedButton 和 FloatingActionButton widget 都有 onPressed() 回调,当用户点击 widget 时就会触发这些回调。
有关更多信息,请参阅Flutter 中文文档:点击、拖动和其他手势。
5. 根据用户输入改变 widget
到目前为止,这个页面仅使用了无状态的 widget。无状态 widget 接收的参数来自于它的父 widget,它们储存在 final 成员变量中。当 widget 需要被 build() 时,就是用这些存储的变量为创建的 widget 生成新的参数。
为了构建更复杂的体验,例如,以更有趣的方式对用户输入做出反应—应用通常带有一些状态。 Flutter 使用 StatefulWidgets 来实现这一想法。 StatefulWidgets 是一种特殊的 widget,它会生成 State 对象,用于保存状态。看看这个基本的例子,它使用了前面提到的RaisedButton:
您可能想知道为什么 StatefulWidget 和 State 是独立的对象。在 Flutter 中,这两种类型的对象具有不同的生命周期。 Widget 是临时对象,用于构造应用当前状态的展示。而 State 对象在调用 build() 之间是持久的,以此来存储信息。
上面的示例接受用户输入并直接在其 build() 方法中直接使用结果。在更复杂的应用中,widget 层次不同的部分可能负责不同的关注点;例如,一个 Widget 可能呈现复杂的用户界面,来收集像日期或位置这样特定的信息,而另一个 widget 可能使用该信息来改变整体的展现。
在 Flutter 中,widget 通过回调得到状态改变的通知,同时当前状态通知给其他 widget 用于显示。重定向这一流程的共同父级是 State,下面稍微复杂的示例显示了它在实践中的工作原理:
注意创建两个新的无状态 widget 的方式,它清楚地分离了 显示 计数器(CounterDisplay)和 改变 计数器(CounterIncrementor)。尽管最终结果与前面的示例相同,但是责任的分离将更大的复杂性封装在各个 widget 中,保证了父级的简单性。
6. 整合在一起
下面是一个更完整的示例,汇集了上面介绍的概念:假定一个购物应用显示各种出售的产品,并在购物车中维护想购买的物品。首先定义一个用于展示的类,ShoppingListItem:
ShoppingListItem 遵循无状态 widget 的通用模式。它将构造函数中接受到的值存储在 final 成员变量中,然后在 build() 函数中使用它们。例如,inCart 布尔值使两种样式进行切换:一个使用当前主题的主要颜色,另一个使用灰色。
当用户点击列表中的一项,widget 不会直接改变 inCart 的值,而是通过调用从父 widget 接收到的 onCartChanged 函数。这种方式可以在组件的生命周期中存储状态更长久,从而使状态持久化。甚至,widget 传给 runApp() 的状态可以持久到整个应用的生命周期。
当父级接收到 onCartChanged 回调时,父级会更新其内部状态,从而触发父级重建并使用新的 inCart 值来创建新的 ShoppingListItem 实例。尽管父级在重建时会创建 ShoppingListItem 的新实例,但是由于框架会将新构建的 widget 与先前构建的 widget 进行比较,仅将差异应用于底层的 RenderObject,这种代价是很小的。
这里有一个示例展示父组件是如何存储可变状态:
ShoppingList 类继承自 StatefulWidget,这意味着这个 widget 存储着可变状态。当 ShoppingList 首次插入到 widget 树中时,框架调用 createState() 函数来创建 _ShoppingListState 的新实例,以与树中的该位置相关联。
(注意,State 的子类通常以下划线开头进行命名,表示它们的实现细节是私有的)
当该 widget 的父 widget 重建时,父 widget 首先会创建一个 ShoppingList 的实例,但是框架会复用之前创建的 _ShoppingListState,而不会重新调用 createState。
为了访问当前 ShoppingList 的属性,_ShoppingListState 可以使用它的 widget 属性。当父组件重建一个新的 ShoppingList时,_ShoppingListState 会使用新的 widget 值来创建。如果希望在 widget 属性更改时收到通知,则可以重写 didUpdateWidget() 函数,该函数将 oldWidget 作为参数传递,以便将 oldWidget 与当前 widget。
当处理 onCartChanged 回调时,_ShoppingListState 通过增加或删除 _shoppingCart 中的产品来改变其内部状态。为了通知框架它改变了它的内部状态,需要调用 setState()。调用 setState() 会将该 widget 标记为“dirty”(脏的),并且计划在下次应用需要更新屏幕时重新构建它。如果在修改 widget 的内部状态后忘记调用 setState,框架将不知道这个 widget 是“dirty”(脏的),并且可能不会调用 widget 的 build() 方法,这意味着用户界面可能不会更新以展示新的状态。通过以这种方式管理状态,你不需要编写用于创建和更新子 widget 的单独代码。相反,你只需实现 build 函数,它可以处理这两种情况。
7. 响应 widget 的生命周期事件
在 StatefulWidget 上调用 createState() 之后,框架将新的状态对象插入到树中,然后在状态对象上调用 initState()。 State 的子类可以重写 initState 来完成只需要发生一次的工作。例如,重写 initState 来配置动画或订阅平台服务。实现 initState需要调用父类的 super.initState 方法来开始。
当不再需要状态对象时,框架会调用状态对象上的 dispose() 方法。可以重写 dispose 方法来清理状态。例如,重写 dispose 以取消计时器或取消订阅平台服务。实现 dispose 时通常通过调用 super.dispose 来结束。
8. Keys
使用 key 可以控制框架在 widget 重建时与哪些其他 widget 进行匹配。默认情况下,框架根据它们的 untimeType 以及它们的显示顺序来匹配。使用 key 时,框架要求两个 widget 具有相同的 key 和 runtimeType。
Key 在构建相同类型 widget 的多个实例时很有用。例如,ShoppingList ,它只构建刚刚好足够的 ShoppingListItem 实例来填充其可见区域:
- 如果没有 key,当前构建中的第一个条目将始终与前一个构建中的第一个条目同步,在语义上,列表中的第一个条目如果滚动出屏幕,那么它应该不会再在窗口中可见。
- 通过给列表中的每个条目分配为“语义” key,无限列表可以更高效,因为框架将通过相匹配的语义 key 来同步条目,并因此具有相似(或相同)的可视外观。此外,语义上同步条目意味着在有状态子 widget 中,保留的状态将附加到相同的语义条目上,而不是附加到相同数字位置上的条目。
9. 全局 key
全局 key 可以用来标识唯一子 widget。全局 key 在整个 widget 结构中必须是全局唯一的,而不像本地 key 只需要在兄弟 widget 中唯一。由于它们是全局唯一的,因此可以使用全局 key 来检索与 widget 关联的状态。
相关推荐
- vue怎么和后端php配合
-
Vue和后端PHP可以通过HTTP请求进行配合。首先,前端Vue可以使用axios库或者Vue自带的$http对象来发送HTTP请求到后端PHP接口。通过axios库发送POST、GET、PUT等请求...
- Ansible最佳实践之 AWX 使用 Ansible 与 API 通信
-
#头条创作挑战赛#API简单介绍红帽AWX提供了一个类似Swagger的RESTful风格的Web服务框架,可以和awx直接交互。使管理员和开发人员能够在webUI之外控制其...
- PHP8.3 错误处理革命:Exception 与 Error 全面升级
-
亲爱的小伙伴,好久没有发布信息了,最近学习了一下PHP8.3的升级,都有哪些优化和提升,把学到的分享出来给需要的小伙伴充下电。技术段位:高可用性必修目标收益:精准错误定位+异常链路追踪适配场景...
- 使用 mix/vega + mix/db 进行现代化的原生 PHP 开发
-
最近几年在javascript、golang生态中游走,发现很多npm、gomod的优点。最近回过头开发MixPHPV3,发现composer其实一直都是一个非常优秀的工具,但是...
- 15 个非常好用的 JSON 工具
-
JSON(JavaScriptObjectNotation)是一种流行的数据交换格式,已经成为许多应用程序中常用的标准。无论您是开发Web应用程序,构建API,还是处理数据,使用JSON工具可以大...
- php8环境原生实现rpc
-
大数据分布式架构盛行时代的程序员面试,常常遇到分布式架构,RPC,本文的主角是RPC,英文名为RemoteProcedureCall,翻译过来为“远程过程调用”。主流的平台中都支持各种远程调用技术...
- 「PHP编程」如何搭建私有Composer包仓库?
-
在前一篇文章「PHP编程」如何制作自己的Composer包?中,我们已经介绍了如何制作自己的composer包,以及如何使用composer安装自己制作的composer包。不过,这其中有...
- WAF-Bypass之SQL注入绕过思路总结
-
过WAF(针对云WAF)寻找真实IP(源站)绕过如果流量都没有经过WAF,WAF当然无法拦截攻击请求。当前多数云WAF架构,例如百度云加速、阿里云盾等,通过更改DNS解析,把流量引入WAF集群,流量经...
- 【推荐】一款 IDEA 必备的 JSON 处理工具插件 — Json Assistant
-
JsonAssistant是基于IntelliJIDEs的JSON工具插件,让JSON处理变得更轻松!主要功能完全支持JSON5JSON窗口(多选项卡)选项卡更名移动至主编辑器用...
- 技术分享 | 利用PHAR协议进行PHP反序列化攻击
-
PHAR(“PhpARchive”)是PHP中的打包文件,相当于Java中的JAR文件,在php5.3或者更高的版本中默认开启。PHAR文件缺省状态是只读的,当我们要创建一个Phar文件需要修改...
- php进阶到架构之swoole系列教程(一)windows安装swoole
-
目录概述安装Cygwin安装swoolephp7进阶到架构师相关阅读概述这是关于php进阶到架构之swoole系列学习课程:第一节:windows安装swoole学习目标:在Windows环境将搭建s...
- go 和 php 性能如何进行对比?
-
PHP性能很差吗?每次讲到PHP和其他语言间的性能对比,似乎都会发现这样一个声音:单纯的性能对比没有意义,主要瓶颈首先是数据库,其次是业务代码等等。好像PHP的性能真的不能单独拿出来讨论似的。但其实一...
- Linux(CentOS )手动搭建LNMP(Linux+Nginx+Mysql+PHP)坏境
-
CentOS搭建LNMP(Linux+Nginx+Mysql+PHP)坏境由于网上各种版本新旧不一,而且Linux版本也不尽相同,所以自己写一遍根据官网的提示自己手动搭建过程。看官方文档很重要,永远...
- json和jsonp区别
-
JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种非官方跨域数据交互协议。一个是描述信息的格式,一个是信息传递的约定方法。一、...
- web后端正确的返回JSON
-
在web开发中,前端和后端发生数据交换传输现在最常见的形式就是异步ajax交互,一般返回给js都是json,如何才是正确的返回呢?前端代码想要获取JSON数据代码如下:$.get('/user-inf...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
python使用fitz模块提取pdf中的图片
-
《人人译客》如何规划你的移动电商网站(2)
-
Jupyterhub安装教程 jupyter怎么安装包
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- table.render (33)
- uniapp textarea (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- react-admin (33)
- vscode切换git分支 (35)
- vscode美化代码 (33)
- python bytes转16进制 (35)