flutter系列之:widgets,构成flutter的基石
简介
flutter中所有的组件都是由widgets组成的,flutter中有各种各样的widgets,这些widgets构成了flutter这个大厦。
那么flutter中的widget有什么特点呢?我们应该怎么学习widget呢? 一起来看看吧。
StatelessWidget和StatefulWidget
实时上,flutter中的widgets是受到React的启发来实现的。flutter中的widget可以分为StatefulWidget和StatelessWidget,分别代表有状态的Widgets和无状态的Widgets。
有状态和无状态,大家听起来是不是很熟悉,我们在应用程序中也经常会用到有状态的Bean和无状态的Bean。他们的原理和flutter的两类Widget其实是差不多的。
StatelessWidget因为是无状态的,所以它只会根据初始传入的配置信息来构建Widget,因为Widget是不可变的,所以StatelessWidget创建出来就不会再变化。
对于StatefulWidget来说,除了根据初始传入的配置来创建Widget之外,它内部还包含了一个State。这个State用来和用户的行为进行交互,从而对State中的值进行修改。当State被修改后,和其绑定的Widget会根据特定的算法进行比较,看是否需要进行重绘,从而将用户的交互反映在用户界面上。
widget提供了一个build方法,build方法返回一个Widget,用于生成最后的RenderObject对象。
build方法的定义如下:
Widget build(BuildContext context);
但事实上,只有StatelessWidget中才有build方法。那么StatefulWidget为什么没有build方法呢?
StatefulWidget虽然没有build方法,但是它有一个createState方法用来创建跟它关联的State:
State createState();
而这个build方法是放在State里面的。
StatelessWidget详解
什么样的组件可以做成无状态的组件呢?那些不需要和用户交互的组件就可以。
flutter中的无状态Widget都有那些呢?
这里列出几个flutter中基本和经常使用的StatelessWidget:
Text: 用来创建文本。
Row和Column: 表示的是纵向扩展和横向扩展的行和列。Row和Column是基于web的flexbox布局。
还有一个基于web的绝对定位的布局叫做Positioned,Positioned通常是和Stack一起使用的。
Stack就是一个栈的结构,在Stack中你可以将一个widget放在另外一个widget的上面。
Positioned用在Stack中,可以相对于top, right, bottom或者left边界进行相对定位,非常好用。
另外一个常用的组件就是Container,它表示的是一个长方形的元素,Container可以用BoxDecoration来修饰,用来表示背景、边框和阴影等。
Container还可以包含margins,padding和尺寸限制等特性。
接下来我们来通过一个具体的例子来说明StatelessWidget到底是怎么使用的。
假如我们想构建一个下面样式的界面,该怎么做呢?

这个界面可以分为两部分,上面的一般称之为appBar,下面的一般叫做content。
appBar按列的布局又可以分为三部分,第一部分是一个IconButton表示导航菜单,第二部分是一个Text表示页面标题,第三部分也是一个IconButton表示搜索按钮。 这三部分按照Row来进行组合.
那么按照Flutter的widget的构建原则,我们可以把appBar构建成一个Widget。因为这个Widget的行为只跟初始化状态有关,所以可以将其设置成为StatelessWidget:
class MyAppBar extends StatelessWidget {
const MyAppBar({required this.title, Key? key}) : super(key: key);
final Widget title;
@override
Widget build(BuildContext context) {
return Container(
height: 56.0, // bar的高度
padding: const EdgeInsets.symmetric(horizontal: 8.0),
decoration: BoxDecoration(color: Colors.blue[500]),
// 按Row来进行布局
child: Row(
children: [
const IconButton(
icon: Icon(Icons.menu),
tooltip: '导航菜单',
onPressed: null, // 目前不可点击
),
// Expanded组件,用于填充所有可用的空间
Expanded(
child: title,
),
const IconButton(
icon: Icon(Icons.search),
tooltip: '搜索',
onPressed: null,
),
],
),
);
}
}
上面的代码中,我们把Row包含在一个Container中,然后将这个Container返回作为appBar的实际内容。
UI下面的部分比较简单, 就是一个居中的Text。我们将其合和appBar合并起来,放在一个Column中,按行进行分割:
class MyScaffold extends StatelessWidget {
const MyScaffold({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
// 构建一个两行的column,一个是bar, 一个是具体的内容
child: Column(
children: [
MyAppBar(
title: Text(
'StatelessWidget',
textAlign: TextAlign.center,
style: Theme.of(context)
.primaryTextTheme
.headline6,
),
),
const Expanded(
child: Center(
child: Text('这是一个Text组件!'),
),
),
],
),
);
}
}
它也是一个StatelessWidget,在build方法中返回了Material这个widget。
然后,我们将MyScaffold包装在一个MaterialApp中,作为最后返回的MyApp:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// 这是应用程序的根widget
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '第一个StatelessWidget',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const SafeArea(
child: MyScaffold(),
),
);
}
}
最后在runApp方法中运行MyApp即可:
void main() {
runApp(const MyApp());
}
StatefulWidget详解
上面我们讲解了一个如何使用StatelessWidget来构造一个app的方法。大家应该对基本的流程有所熟悉。
这里要注意的是,StatelessWidget并不是说widget中不能存储任何变量,如上面的例子所示,MyAppBar这个StatelessWidget其实是包含一个title的Widget,但是这个widget是final的,也就是说定义过一次之后就不能够再变化,所以叫做StatelessWidget。
StatefulWidget和StatelessWidget不同的地方在于,StatefulWidget可以和一个State进行关联。State中可以包含一些可变的属性,这些属性可以跟用户的操作进行交互,从而完成一些比较复杂的功能。
假如我们需要下面的一个界面,界面右下方有一个按钮,点击一次,可以将中间的数字加一。

这是一个很明显的和用户交互的行为。这里我们就可以用到StatefulWidget。
这里我们创建一个MyHomePage的StatefulWidget,并创建一个_MyHomePageState的state和其进行关联:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
注意,可变属性是存在和StatefulWidget关联的state中的,而不是StatefulWidget本身。
所以我们需要在_MyHomePageState中定义一个int的_counter变量,用来存储用户点击次数。然后定义一个_incrementCounter用来对_counter进行累加。
在_incrementCounter需要调用setState方法用来对State的状态进行刷新。
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
然后在State中的build方法中就可以返回对应UI的Widget了。这里我们使用Scaffold组件,这个组件自带了appBar和body:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'按钮被点了:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
const Text(
'次',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
这里body中我们选择使用Center组件用来展示内容信息。而浮动的按钮则使用FloatingActionButton,它的onPressed方法会触发我们前面写的_incrementCounter方法,用来将_counter加一。
最后将我们构建的组件传入MaterialApp中,如下所示:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// 根Widget
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '第一个StatefulWidget',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: const MyHomePage(title: 'StatefulWidget'),
);
}
}
总结
以上,我们简单的讲解了StatelessWidget和StatefulWidget的简单使用情况。后续我们将会这些组件进行深入,敬请期待。
本文的代码可以参考:https://github.com/ddean2009/learn-flutter, 觉得好的话,请点个赞,谢谢。
更多内容请参考 http://www.flydean.com/02-flutter-widget/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
flutter系列之:widgets,构成flutter的基石的更多相关文章
- Flutter系列博文链接
Flutter系列博文链接 ↓: Flutter基础篇: Flutter基础篇(1)-- 跨平台开发框架和工具集锦 Flutter基础篇(2)-- 老司机用一篇博客带你快速熟悉Dart语法 Flutt ...
- flutter系列之:flutter架构什么的,看完这篇文章就全懂了
目录 简介 Flutter的架构图 embedder engine Flutter framework Widgets Widgets的可扩展性 Widgets的状态管理 渲染和布局 总结 简介 Fl ...
- flutter系列之:UI layout简介
目录 简介 flutter中layout的分类 常用layout举例 总结 简介 对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了.布局的英文名叫做layout,就是用来 ...
- Flutter 中文文档网站 flutter.cn 正式发布!
在通常的对 Flutter 介绍中,最耳熟能详的是下面四个特点: 精美 (Beautiful):充分的赋予和发挥设计师的创造力和想象力,让你真正掌控屏幕上的每一个像素. ** 极速 (Fast)**: ...
- 【Flutter 混合开发】添加 Flutter 到 Android Activity
Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...
- 【Flutter 混合开发】添加 Flutter 到 Android Fragment
Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...
- 【Flutter 混合开发】添加 Flutter 到 iOS
Flutter 混合开发系列 包含如下: 嵌入原生View-Android 嵌入原生View-iOS 与原生通信-MethodChannel 与原生通信-BasicMessageChannel 与原生 ...
- Flutter调研(2)-Flutter从安装到运行成功的一些坑
工作需要,因客户端有部分页面要使用flutter编写,需要QA了解一下flutter相关知识,因此,做了flutter调研,包含安装,基础知识与demo编写,第二部分是安装与环境配置. —— 在mac ...
- 【Flutter 1-2】在 Windows 10下安装Flutter+Dart+Android Studio 配置Flutter开发环境
在 Windows 10下安装Flutter+Dart+Android Studio 配置Flutter开发环境 文章首发地址 配置环境变量 由于部分网站被墙的原因,我们需要先配置Flutter国内镜 ...
随机推荐
- poj3784(对顶堆)
题意:多组数据,让你求出1~i(i为奇数&&i<=n)的中位数 思路:首先复杂度必为O(n)或O(nlogn)的(数据范围) 思索,如果题目要求1次中位数,好求!排个序,取a[( ...
- mysql 超时配置
日志报错: No operations allowed after connection closed.; nested exception is com.mysql.jdbc.exceptions. ...
- 000 上传本地库到Github远程库过程全记录
20220613 Github上新创建了一个CsImage库,之后本地创建了一个对应名称的目录,并创建本地库,进行了上传操作,记录一下过程 1.Github上CsImage库创建完成 Github上创 ...
- 【C#/VB.NET】 将PDF转为SVG/Image, SVG/Image转PDF
SVG是一种图形文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形.它在放大或者改变尺寸的情况下其图形质量不会有所损失,且与 JPG 和 GIF 图像比起 ...
- python基础知识-day8(动态参数)
1.动态参数 函数的形式参数个数不确定.函数的形式数据类型不确定,使用动态参数,*代表元组,**代表字典. 2.代码案例演示 1 def func(*args,**kwargs): 2 print(a ...
- java版第一个代码——HelloWorld!
java版第一个代码--HelloWorld! 今天来接触一下java代码: 事前准备 jdk的配置(推荐jdk8或jdk11) notepad++或idea软件 开始编写 建立文件夹存放代码 建立j ...
- C语言-typedef和#define
1.关键字 typedef 为一个数据类型起一个新的别名,typedef 的用法一般为: typedef OldName NewName; 需要强调的是,typedef 是赋予现有类型一个新的名字,而 ...
- java--运算符和表达式
运算符:就是对常量或者遍历进行操作的符号: 表达式:用运算符把常量或者变量连接起来符合java语法的式子称为表达式,不同运算符连接的表达式体现的是不同类型的表达式. 一.算术运算符 1.使用%运算符: ...
- String类型函数传递问题
String类型函数传递问题 问题 以前没有注意过的一个问题, 最近在使用String类型作为函数入参的时候, 发现函数内对于String类型的改变并不会影响到外层调用对象本身; 结论 (先说结论) ...
- Hadoop-HA 搭建高可用集群Hadoop Zookeeper
Hadoop Zookeeper 搭建(一) 一.准备工作 VMWARE虚拟机 CentOS 7 系统 虚拟机1:master 虚拟机2:node1 虚拟机3:node2 时间同步 ntpdate n ...