StatefulWidget
动态组件,开发中需要改变状态,使用动态组件
静态组件,定义后不会再改变
Text(文本),Image(图片),xxButton(按钮),TextField(输入框),Form(表单)
Text('Hello World');
单独说一下Form组件
实际业务中,在正式向服务器提交数据前,都会对各个输入框数据进行合法性校验,但是对每一个TextField都分别进行校验将会是一件很麻烦的事。还有,如果用户想清除一组TextField的内容,除了一个一个清除有没有什么更好的办法呢?为此,Flutter提供了一个Form widget,它可以对输入框进行分组,然后进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。
Form的子孙元素必须是FormField类型,FormField是一个抽象类,定义几个属性,FormState内部通过它们来完成操作。
详细
Row(水平线性布局),Column(垂直线性布局),Flex(弹性布局),Wrap|Flow(流式布局),Stack|Positioned(层叠布局)
Column(
crossAxisAlignment: CrossAxisAlignment.start,//子控件对齐方式
mainAxisSize: MainAxisSize.max, //自身大小
children: [], //子控件
)
Flex(
direction: Axis.horizontal,
children: [
Expanded(
child: Container(
height: 30,
color: Colors.red,),
flex: 1,),
Expanded(
child: Container(
height: 30,
color: Colors.blue,),
flex: 2,
),],),
Flex里面的两个Expanded按照1:2平分。
Flow因为计算复杂使用较少,优先考虑Wrap是否能实现 详细
Padding(添加补白),ConstrainedBox|SizedBox(限制类容器),DecoratedBox(装饰类容器),Transform(变换),Container,其他
布局类Widget和容器类Widget的区别
可以给子节点添加补白,Flutter中给Widget添加间距也单独抽出Widget
ConstrainedBox用于添加对子Widget的限制
ConstrainedBox(
constraints: BoxConstraints(//用于设置限制条件
minWidth: double.infinity, //宽度尽可能大
minHeight: 50.0 //最小高度为50像素
),
child: Widget,)
SizedBox用于给子Widget指定固定的宽高
SizedBox(
width: 80.0,
height: 80.0,
child: redBox)
const DecoratedBox({
Decoration decoration,
DecorationPosition position = DecorationPosition.background,
Widget child})
使用Decoration的实现类去装饰子Widget(BoxDecoration)
BoxDecoration({
Color color, //颜色
DecorationImage image,//图片
BoxBorder border, //边框
BorderRadiusGeometry borderRadius, //圆角
List boxShadow, //阴影,可以指定多个
Gradient gradient, //渐变
BlendMode backgroundBlendMode, //背景混合模式
BoxShape shape = BoxShape.rectangle, //形状
})
Transform可以实现4D矩阵变换(Matrix4)、位移(offset)、旋转(rotate)、缩放(scale);
注意:Transform只应用于绘制阶段,不应用于layout阶段,所以无论对子Widget做何种变换,其在屏幕上的位置和占用空间的大小是不会变的
RotateBox和Transform的功能类似,但是它作用于layout阶段,会影响子Widget的位置和占用空间大小。
Container是各种DecoratedBox、ConstrainedBox、Transform、Padding、Align等widget的一个组合widget,可以同时实现装饰、变换、限制的功能,
此外margin的补白是在容器外部,而padding的补白是在容器内部
Scaffold,由Flutter Material库提供,是一个路由页的骨架,可以非常容易的拼装出一个完整的页面
AppBar是一个Material风格的导航栏,它可以设置标题、导航栏菜单、底部Tab等
TabBar生成一个静态的菜单,TabBarView配合TabBar实现左右切换的View,抽屉菜单Drawer,FloatingActionButton悬浮在页面的某一个位置作为某种常用动作的快捷入口
SingleChildScrollView,ListView,GridView,CustomScrollView,滚动监听和控制ScrollController。
当内容超过显示窗口(ViewPort)的时候,Flutter就会提示Overflow错误,因此需要滚动Widget,可滚动的Widget都直接或者间接包含Scrollable widget。
类似Android中的ScrollView,只接收一个子Widget
ListView最常用的可滚动widget
ListView({
...
//可滚动widget公共参数
Axis scrollDirection = Axis.vertical,//滚动方向
bool reverse = false,//是否反向(从右到左)
ScrollController controller,//控制滚动位置和监听滚动事件
bool primary,//是否使用Widget树中默认的PrimaryScrollController
ScrollPhysics physics,//接收一个ScrollPhysics参数
EdgeInsetsGeometry padding,
//ListView各个构造函数的共同参数
double itemExtent,//强制item的高度,相对不设置会高效
bool shrinkWrap = false,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
double cacheExtent,
//子widget列表
List children = const [],
})
physics:决定可滚动widget怎样响应用户的操作,比如滑动完抬起手指,或滑动到边界时如何显示
addAutomaticKeepAlives:是否将列表项(子Widget)包裹在AutomaticKeepAlive widget中,典型的,在一个懒加载列表中,如果列表项包裹在AutomaticKeepAlive中,列表项移出视口时该列表项不会GC,会使用KeepAliveNotification来保存其状态。如果列表项自己维护KeepAlive状态,则此项为false。 addRepaintBoundaries:是否将列表项(子Widget)包裹在RepaintBoundary中。将列表项包裹在RepaintBoundary中可以避免在滚动的时候重绘,但重绘开销非常小的时候,不添加RepaintBoundary反而会高效。 ListView 的构造函数 默认构造函数
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: [
const Text("Im dedicating every day to you"),
const Text("Domestic life was never quite my style"),
const Text("When you smile, you knock me out, I fall apart"),
const Text("And I thought I was so smart"),
],);
默认构造函数会把所有的子widget提前创建好。
ListView.builder构造函数
ListView.builder({
// ListView公共参数已省略
...
@required IndexedWidgetBuilder itemBuilder, //列表的构造器,返回一个Widget
int itemCount,//列表项的数量,null表示无限
...})
ListView.separated构造函数
比ListView.build多一个separatorBuilder,用于生成分割线。
基本和ListView相同,有个一SliverGridDelegate gridDelegate参数,用于控制子widget如何排列。 两个实现类: SliverGridDelegateWithFixedCrossAxisCount 横轴为固定数量子元素, SliverGridDelegateWithMaxCrossAxisExtent 横轴子元素为固定最大长度的layout算法。
默认构造函数 GridView({})
GridView.count构造函数
内部使用SliverGridDelegateWithFixedCrossAxisCount去创建横轴为固定数量的GridView。
GridView.extent构造函数
内部使用了SliverGridDelegateWithMaxCrossAxisExtent去创建横轴子元素为固定最大长度的算法。
GridView.builder构造函数
GridView.builder(
...
@required SliverGridDelegate gridDelegate,
@required IndexedWidgetBuilder itemBuilder, //子widget构造方法
)
Pub上有一个包“flutter_staggered_grid_view” ,它实现了一个交错GridView的布局,子widget大小不一样。
CustomScrollView是可以使用Sliver自定义滚动模型(效果)的widget。CustomScrollView可以把彼此独立的可滚动的widget(Sliver)“粘”起来。
可滚动的widget如ListView,GridView都有对应的Sliver实现SliverList,SliverGrid。对于大多数Sliver来说,他们和可滚动的widget的区别是Sliver不包含Scrollable widget,本身Sliver不包含滚动模型,这些widget公用CustomScrollView的Scrollable,最终实现统一滚动效果。
详细
可以通过ScrollController来控制Scrollable widget的滚动位置,滚动事件传递等。
ScrollController构造函数
ScrollController({
double initialScrollOffset = 0.0, //初始滚动位置
this.keepScrollOffset = true, //是否保存滚动位置
})
常用方法和属性offset:可滚动组件当前滚动的位置;jumpTo(double offset)、animateTo(double offset) 跳转到指定位置。
滚动监听
ScrollController间接继承自Listenable,可以监听滚动事件
controller.addListener(()=>print(controller.offset));
滚动位置恢复
PageStorage是用于保存页面(路由)相关数据的Widget,是一个功能型widget,不影响子树的UI外观,它拥有一个存储桶(bucket),子树中的widget可以通过指定不同的PageStorageKey来存储各自的数据或状态。
需要配合keepScrollOffset使用,为true的时候,会记录滚动位置。
当一个路由中包括多个Scrollable widget的时候,在进行一些跳转操作后,滚动位置不能正确恢复,这时可以显式给Scrollable widget指定PageStoreKey来分别跟踪。
并非一个路由包含多个Scrollable widget时,就需要使用PageStoreKey分别跟踪,因为它们都是StatefullWidget,只要widget没有被从树上detach掉,其state就不会销毁(dispose),滚动位置就不会丢失。
ScrollPosition
ScrollController可以被多个Scrollable widget使用,ScrollController会为每个Scrollable widget创建一个ScrollPosition,这些ScrollPosition保存在ScrollController的position中。ScrollPosition是真正保存滑动位置信息的对象,offset只是一个便捷属性。
ScrollPosition有animateTo(),jumpTo(),来控制真正跳转滚动位置的方法,ScrollController的两个同名方法,最终会调用这两个。
滚动监听
Flutter widgets树中的子widget可以通过发送通知与父(包括祖)widget通信,父widget也可以通过NotificationListener widget来监听自己关注的通知。
NotificationListener是一个widget,模板参数是想要关注的通知类型,如果省略,则所有类型的通知都会监听。需要实现一个onNotification回调函数,实现监听处理逻辑,返回布尔值,如果返回true,则事件停止向上传递,返回false则继续向上传递。
功能性widget是指不会影响UI布局及外观的widget,通常有一定功能,如事件监听,数据存储等。
通过WillPopScope实现返回按钮拦截,包括Android的物理返回键和导航返回按钮。在onWillPop回调函数中处理相关逻辑,返回true时当前路由出栈,返回false时,当前路由不出栈。
可以高效的将数据在widget树中下传递、共享。例如正是通过InheritedWidget来共享Theme(主题)和Locale(当前语言环境)信息。
Theme widget可以为Material App定义主题数据(ThemeData),Material组件库里很多Widget都使用了主题数据,如导航栏颜色、标题字体、Icon样式等。Theme会通过InheritedWidget来为其子树widget共享样式数据。
可定义的主题数据都在ThemeData中,可以通过Theme.of(context)获取当前的ThemeData。
更多更新关注微信公众号“Flutter入门”
Flutter入门