1、Widget Widget是个抽象类,定义如下:
@immutable abstract class Widget extends DiagnosticableTree { const Widget({ this.key }); final Key key; @protected Element createElement(); static bool canUpdate(Widget oldWidget, Widget newWidget) { return oldWidget.runtimeType == newWidget.runtimeType && oldWidget.key == newWidget.key; } }说明: (1)Key: 这个key属性主要的作用是决定是否在下一次build时复用旧的widget,决定的条件在canUpdate()方法中。 (2)canUpdate(...)是一个静态方法,它主要用于在Widget树重新build时复用旧的widget。只要newWidget与oldWidget的runtimeType和key同时相等时就会用newWidget去更新Element对象的配置,否则就会创建新的Element。 (3)StatelessWidget和StatefulWidget是Widget常用的两个子类,他们两也是abstract抽象类。
2、StatefulWidget
abstract class StatefulWidget extends Widget { const StatefulWidget({ Key key }) : super(key: key); @override StatefulElement createElement() => new StatefulElement(this); @protected State createState(); }说明: (1)一个StatefulWidget类会对应一个State类,State表示与其对应的StatefulWidget要维护的状态。本质上就是一个StatefulElement对应一个State实例。 (2)当State被改变时,可以手动调用其setState()方法通知Flutter framework状态发生改变,Flutter framework在收到消息后,会重新调用其build方法重新构建widget树,从而达到更新UI的目的。
3、State生命周期 (1)举个生命周期例子 (a)首次启动StatefulWidget,生命周期执行如下:
initState didChangeDependencies build(b)然后点击热重载按钮,生命周期执行如下:
reassemble didUpdateWidget build(c)在widget树中将当前StatefulWidget移除,然后热重载,生命周期执行如下:
reassemble deactivate dispose(2)生命周期方法说明 (a)initState:当Widget第一次插入到Widget树时会被调用,Flutter framework只会调用一次该回调。 (b)didChangeDependencies:当State对象的依赖发生变化时会被调用。 (c)build:用于构建Widget子树。 (d)reassemble:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。 (e)didUpdateWidget:在widget重新构建时,如果Widget.canUpdate返回true则会调用此回调。 (f)deactivate:当State对象从树中被移除时,会调用此回调。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。 (g) dispose:当State对象从树中被永久移除时调用;通常在此回调中释放资源。 注意:在继承StatefulWidget重写其方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中先调用父类方法。
4、状态管理 最常见的状态管理方法如下: (1)Widget管理自己的state。 该Widget本身是StatefulWidget。 (2)父widget管理子widget状态。 父类为StatefulWidget, 子类为StatelessWidget。子类通过回调将其状态导出到其父项。 (3)混合管理(父widget和子widget都管理状态)。 此时父类和子类都继承于StatefulWidget。
5、全局状态管理 当应用中包括一些跨widget(甚至跨路由)的状态需要同步时,上面介绍的方法很难胜任了。比如,我们有一个设置页,里面可以设置应用语言,但是我们为了让设置实时生效,我们期望在语言状态发生改变时,我们的APP Widget能够重新build一下,但我们的APP Widget和设置页并不在一起。正确的做法是通过一个全局状态管理器来处理这种“相距较远”的widget之间的通信。 目前主要方法:实现一个全局的事件总线,将语言状态改变对应为一个事件,然后在APP Widget所在的父widget initState 方法中订阅语言改变的事件,当用户在设置页切换语言后,我们触发语言改变事件,然后APP Widget那边就会收到通知,然后重新build一下即可。
6、基础widget (1)Text:可以创建一个带格式的文本。 (2)Row、 Column:可让在水平(Row)和垂直(Column)方向上创建灵活的布局。 (3)Stack:(和Android中的FrameLayout相似),Stack允许子 widget 堆叠, 你可以使用 Positioned 来定位他们相对于Stack的上下左右四条边的位置。Stacks是基于Web开发中的绝对定位(absolute positioning )布局模型设计的。 (4)Container:Container可以创建矩形视觉元素。container 可以装饰一个BoxDecoration, 如 background、一个边框、或者一个阴影。 Container 也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container可以使用矩阵在三维空间中对其进行变换。
7、按钮 Material widget库中提供了多种按钮Widget如RaisedButton、FlatButton、OutlineButton等。 所有Material 库中的按钮都有如下相同点:按下时都会有“水波动画”;有一个onPressed属性来设置点击回调。
8、图片 (1)在Flutter中,我们可以通过Image来加载并显示图片,Image的数据源可以是asset、文件、内存以及网络。 (2)ImageProvider ImageProvider 是一个抽象类,主要定义了图片数据获取的接口load()。从不同的数据源获取图片需要实现不同的ImageProvider,如AssetImage是实现了从Asset中加载图片的ImageProvider,而NetworkImage实现了从网络加载图片的ImageProvider。 (3)Image widget有一个必选的image参数,它对应一个ImageProvider。
9、字体图标 (1)Flutter中,可以像web开发一样使用iconfont“字体图标”,它是将图标做成字体文件,然后通过指定不同的字符而显示不同的图片。 (2)在Flutter开发中,iconfont和图片相比有如下优势: 体积小:可以减小安装包大小。 矢量的:iconfont都是矢量图标,放大不会影响其清晰度。 可以应用文本样式:可以像文本一样改变字体图标的颜色、大小对齐等。 可以通过TextSpan和文本混用。
10、单选开关Switch和复选框Checkbox 单选开关Switch和复选框Checkbox,它们都是继承自StatelessWidget,所以它们本身不会保存当前选择状态,所以一般都是在父widget中管理选中状态。当用户点击Switch或Checkbox时,它们会触发onChanged回调,我们可以在此回调中处理选中状态改变逻辑。
11、输入框TextField (1)监听文本变化的方式有:onChange回调、controller监听。 (2)控制焦点 焦点可以通过FocusNode和FocusScopeNode来控制,默认情况下,焦点由FocusScope来管理,它代表焦点控制范围,可以在这个范围内可以通过FocusScopeNode在输入框之间移动焦点、设置默认焦点等。我们可以通过FocusScope.of(context) 来获取widget树中默认的FocusScopeNode。
12、表单Form (1)可以对输入框数据进行合法性校验。 (2)可以对一组输入框进行统一处理。
13、布局类Widget概述 Flutter中,根据Widget是否需要包含子节点将Widget分为了三类: LeafRenderObjectWidget:没有子节点的widget。 SingleChildRenderObjectWidget:包含一个子widget。 MultiChildRenderObjectWidget:包含多个子Widget,一般都有一个children参数。 举几个布局类Widget: (1)线性布局Row/Column (2)弹性布局(Flex和Expanded配合) (3)流式布局Wrap/Flow (4)层叠布局Stack(使用Positioned定位)
14、Row/Column(线性布局) (1)Row和(水平布局)Column(垂直布局)都继承自Flex(弹性布局)。 (2)对于线性布局,有主轴和纵轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而纵轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而纵轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和纵轴对齐。
15、Flex和Expanded配合(弹性布局)
const Expanded({ int flex = 1, @required Widget child, })flex为弹性系数,如果为0或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其flex的比例来分割Flex主轴的全部空闲空间。例如:
//Flex的两个子widget按1:2来占据水平空间 Flex( direction: Axis.horizontal, children: <Widget>[ Expanded( flex: 1, child: Container( height: 30.0, color: Colors.red, ), ), Expanded( flex: 2, child: Container( height: 30.0, color: Colors.green, ), ), ], ),16、Wrap和Flow(流式布局) 我们把超出屏幕显示范围会自动折行的布局称为流式布局。Flutter中通过Wrap和Flow来支持流式布局。
17、容器类Widget和布局类Widget区别 布局类Widget一般都需要接收一个widget数组(children),他们直接或间接继承自(或包含)MultiChildRenderObjectWidget ;而容器类Widget一般只需要接收一个子Widget(child),他们直接或间接继承自(或包含)SingleChildRenderObjectWidget。 举几个容器类Widget: (1)padding (2)限制类容器ConstrainedBox/SizedBox ConstrainedBox用于对子widget添加额外的约束。 (3)装饰容器DecoratedBox (4)变换Transform Transform可以在其子Widget绘制时对其应用一个矩阵变换。 注意:Transform的变换是应用在绘制阶段,而并不是应用在布局(layout)阶段,所以无论对子widget应用何种变化,其占用空间的大小和在屏幕上的位置都是固定不变的,因为这些是在布局阶段就确定的。 (5)Container
18、可滚动的widget (1)滚动条Scrollbar 如果要给可滚动widget添加滚动条,只需将Scrollbar作为可滚动widget的父widget即可。
Scrollbar( child: SingleChildScrollView( ... ), );(2)SingleChildScrollView SingleChildScrollView类似于Android中的ScrollView,它只能接收一个子Widget。 (3)ListView 如何自动拉升ListView以填充屏幕剩余空间呢?答案是使用Flex。前面已经介绍过在Flex布局中,可以使用Expanded自动拉伸组件大小的Widget,我们也说过Column是继承自Flex的,所以我们可以直接使用Column+Expanded来实现,代码如下:
@override Widget build(BuildContext context) { return Column(children: <Widget>[ ListTile(title:Text("商品列表")), Expanded( child: ListView.builder(itemBuilder: (BuildContext context, int index) { return ListTile(title: Text("$index")); }), ), ]); }(4)GridView (5)CustomScrollView自定义滚动模型 CustomScrollView是可以使用sliver来自定义滚动模型(效果)的widget。它可以包含多种滚动模型,举个例子,假设有一个页面,顶部需要一个GridView,底部需要一个ListView,而要求整个页面的滑动效果是统一的,即它们看起来是一个整体,如果使用GridView+ListView来实现的话,就不能保证一致的滑动效果,因为它们的滚动效果是分离的,所以这时就需要一个"胶水",把这些彼此独立的可滚动widget(Sliver)"粘"起来,而CustomScrollView的功能就相当于“胶水”。
在Flutter中,Sliver通常指具有特定滚动效果的可滚动块。可滚动widget,如ListView、GridView等都有对应的Sliver实现如SliverList、SliverGrid等。对于大多数Sliver来说,它们和可滚动Widget最主要的区别是Sliver不会包含Scrollable Widget,也就是说Sliver本身不包含滚动交互模型 ,正因如此,CustomScrollView才可以将多个Sliver"粘"在一起,这些Sliver共用CustomScrollView的Scrollable,最终实现统一的滑动效果。