做者:闲鱼技术-意境css
Flutter做为一种全新的响应式,跨平台,高性能的移动开发框架。从开源以来,已经获得愈来愈多开发者的喜好。闲鱼是最先一批与谷歌展开合做,并在重要的商品详情页中使用上线的公司。一路走来,积累了大量的开发经验。虽然愈来愈多的技术大牛在flutter世界中弄得风声水起,可是确定有不少的flutter小白但愿能快速上手,享受flutter编程的乐趣。本文就是面向刚刚踏上futter的同窗,从Flutter体系中最基本的一个概念widget入手学习Flutter。但愿能助力每一位初学者。html
可能你们要问的第一个问题是为何从Widget开始?前端
从flutter的架构图中不难看出widget是整个视图描述的基础。Flutter 的核心设计思想即是java
Everything’s a Widget
复制代码
即一切即Widget。在flutter的世界里,包括views,view controllers,layouts等在内的概念都创建在Widget之上。widget是flutter功能的抽象描述。因此掌握Flutter的基础就是学会使用widget开始。android
本文会从你们熟悉的UI绘制视角来介绍flutter组件和布局的基础知识。首先罗列了UI开发中最为经常使用,最为基础的组件。下面逐一进行介绍。css3
Text几乎是UI开发中最为重要的组件之一了,UI上面文字的展现基本上都要靠Text组件来完成。Flutter提供了原生的Text组件。Text的配置属性是很丰富的,属性主要分为两个部分一个是对齐&显示控制相关的在Text类的属性中,另外一类是样式相关的属性使用单独的类TextStyle进行控制。跟native控件相比(以android为例),Text的组件基本上提供了同等的能力,而且提供了更加丰富的样式装饰能力。详细的属性能够参考官方文档flutter text.web
设置文字&文字大小&颜色&行数限制&文本对齐算法
const Text( "hello flutter!",
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis, // 溢出显示。。。
style: TextStyle(fontSize: 30.0,// 文字大小
color: Colors.yellow),// 文字颜色
),
复制代码
效果以下:编程
图片也是UI部分开发最为重要的组件之一。在能看图随看文字的年代,图片是页面展现的重中之重!Flutter一样原生提供了Image组件。下面重点介绍一下几个重点:缓存
怎样设置图片显示的缩放方式呢? Flutter中的图片缩放是fit字段来控制的。这是对最终图片展现效果影响很大的一个参数,也是容易出错的点。下面逐个分析一下flutter Image组件提供的缩放方式。
缩放属性值在BoxFit枚举中
下面列出的图片是flutter官方对各类缩放作的图片示例。基本上都表述很清楚了,就整理出来供你们查阅。
属性 | 缩放效果 |
---|---|
fill |
![]() |
contain |
![]() |
cover |
![]() |
fitWidth |
![]() |
fitHeight |
![]() |
none |
![]() |
scaleDown |
![]() |
怎样从各类来源加载图片? 默认的Image组件不能直接显示图片,他须要一个ImageProvider来提供具体的图片资源的(即Image中的image字段须要赋值)。咋一看这确实很是麻烦,可是实际上ImageProvider并不须要彻底从新本身实现。在Image类中目前提供了一下几个实现好的ImageProvider,基本能知足常见的需求。
ImageProvider | 用途 |
---|---|
Image.asset | 从asset资源文件中获取图片 |
Image.network | 从网络获取图片 |
Image.file | 从本地file文件中获取图片 |
Image.memory | 从内存中获取图片 |
Image一样支持GIF图片
网络请求Image是你们最多见的操做。这里重点说明两个点:
ImageCache是ImageProvider默认使用的图片缓存。ImageCache使用的是LRU的算法。默承认以存储1000张图片。若是以为缓存太大,能够经过设置ImageCache的maximumSize属性来控制缓存图片的数量。也能够经过设置maximumSizeBytes来控制缓存的大小(默认缓存大小10MB)。
若是想要使用cdn优化,能够经过url增长后缀的方式实现。默认实现中没有这个点,可是考虑到cdn优化的可观收益,建议你们利用好这个优化。
在实际开发中,考虑到图片加载速度可能不能达到预期。因此但愿能增长渐入效果&增长placeHolder的功能。Flutter一样提供的这样的组件——FadeInImage。
FadeInImage也提供了从多种渠道加载图片的能力。这块跟上面所说差别不大。这里再也不赘述。
new Image.network(
'https://gw.alicdn.com/tfs/TB1CgtkJeuSBuNjy1XcXXcYjFXa-906-520.png',
fit: BoxFit.contain,
width: 150.0,
height: 100.0,
),
复制代码
Flutter的设计思想就是彻底的widget化。这也就是说连最基本的padding,Center都是widget。设想一下若是每次写view,连padding,Center都要本身包一个组件是一种怎样的体验?做为一个工程师,别给只给我谈思想,实际操做的操做效率也一样很是重要。flutter 官方也意识到了这个问题,他们从实际编写效率的角提供了一个友好高效的封装,这就是Container!首先没有任何疑问,Container 自己也是一个widget。可是他却提供了对基础widget的封装,提升了UI基础装饰能力的表达效率。Container相似于android中的ViewGroup。
相信大部分的属性你们都会感受很是亲切,结合代码注释都比较容易理解,这里就再也不赘述。其中须要重点解释一下的是:Decoration和BoxConstraints。
Decoration是对Container进行装饰的描述。其概念相似与android中的shape。通常实际场景中会使用他的子类BoxDecoration。BoxDecoration提供了对背景色,边框,圆角,阴影和渐变等功能的定制能力。 须要注意几个点:
BoxConstraints实际上是对Container组件大小的描述。BoxConstraints属性比较简单。若是不太清楚能够研究一下盒子模型。这里有个点须要重点说明一下:
new Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(15.0),
margin: const EdgeInsets.all(15.0),
decoration: new BoxDecoration(
border: new Border.all(
color: Colors.red,
),
image: const DecorationImage(
image: const NetworkImage(
'https://gw.alicdn.com/tfs/TB1CgtkJeuSBuNjy1XcXXcYjFXa-906-520.png',
),
fit: BoxFit.contain,
),
//borderRadius: const BorderRadius.all(const Radius.circular(6.0)),
borderRadius: const BorderRadius.only(
topLeft: const Radius.circular(3.0),
topRight: const Radius.circular(6.0),
bottomLeft: const Radius.circular(9.0),
bottomRight: const Radius.circular(0.0),
),
),
child: Text(''),
),
复制代码
手势操做是最多见的UI交互操做。在Flutter中手势识别也是一个widget!这点对新人来讲又是一个新鲜的地方。一般来讲能够经过GestureDetector类来完成点击事件的处理。使用时只须要将GestureDetector包裹在目标widget外面,再实现对应事件的函数便可。从点击到长按,从缩放到拖动,这个类基本上都有相应的实现。具体能够参见组件文档。
页面布局应该是UI编写最为根本的知识,其主要的描述的是父子组件子子组件之间的位置关系。首先咱们理解一下官方文档的逻辑:
将布局分为单孩子和多孩子是Flutter布局的一大特点。这点对native研发同窗来讲会比较新鲜。单孩子组件主要继承自SingleChildRenderObjectWidget。这些组件能提供丰富的装饰能力(例如container),也能提供部分特定的布局能力(例如center)。多孩子组件继承自MultiChildRenderObjectWidget,能提供更加丰富的布局能力(Flex,Stack,flow),但几乎没有装饰的能力。下面介绍几个重点布局:
Flutter在布局上也提供了完整的Flex布局能力。可是在Flutter官方文档中Layout Widgets,是看不到任何Flex的影子的。映入眼帘的倒是Row,Column,这些是什么鬼?其实不难发现相似Row,Column 这样的组件,他们的基类都是Flex。Row和Column差异是设置了不一样的flex-direction。而之所这么设计,是由于Flutter的widget从开始设计之初就考虑到UI布局语义保持的重要性。这块应该部分借鉴了前端的经验,极力避免一个div搞定所有页面的尴尬(固然flutter也可使用Flex来作一样的事情,可是并不建议这么作)。 Flutter使用的Flex模型基本上跟传统的Css相似。这块前端同窗能够快速上手。可是Flex对于客户端同窗来讲是一种全新的布局方式。Flex的基础知识能够参看flex布局基础。因为篇幅有限这里不展开叙述。这里只重点强调一个点: 以下图flex布局概念以下:
ok,看完这些知识,咱们实际需求角度实际操做几个case来熟悉一下Flex。
new Flex(direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
width: 40.0,
height: 60.0,
color: Colors.pink,
child: const Center(
child: const Text("left"),
)),
new Container(
width: 80.0,
height: 60.0,
color: Colors.grey,
child: const Center(
child: const Text("middle"),
)),
new Container(
width: 60.0,
height: 60.0,
color: Colors.yellow,
child: const Center(
child: const Text("right"),
)),
],
),
复制代码
new Flex(
direction: Axis.horizontal,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Flexible(
flex: 2,
fit: FlexFit.loose,
child: new Container(
color: Colors.blue,
height: 60.0,
alignment: Alignment.center,
child: const Text('left!',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
textDirection: TextDirection.ltr),
),
),
new Flexible(
flex: 1,
fit: FlexFit.loose,
child: new Container(
color: Colors.red,
height: 60.0,
alignment: Alignment.center,
child: const Text('right',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.black),
textDirection: TextDirection.ltr),
),
),
],
)
复制代码
在实际开发中,仍是须要在一些Widgets的上面再覆盖上新的Widgets。这时候就须要层式布局了。这种布局在Native上,以android为例,相似于relativeLayout 或者FrameLayout。在Flutter中使用的是Stack。
实际使用中Stack中的子Widgets分为两种:
对于non-positioned children, 咱们经过控制Stack的alignment属性来控制对齐方式。Positioned的布局方式相似于H5&weex中的position布局中的absolute布局方式。经过设置距离父组件上下左右的距离,Positioned对象能在Stack布局中更加灵活的控制view的展示方式。
new Container(
color: Colors.yellow,
height: 150.0,
width: 500.0,
child: new Stack(children: <Widget>[
new Container(
color: Colors.blueAccent,
height: 50.0,
width: 100.0,
alignment: Alignment.center,
child: Text('unPositioned'),
),
new Positioned(
left: 40.0,
top: 80.0,
child: new Container(
color: Colors.pink,
height: 50.0,
width: 95.0,
alignment: Alignment.center,
child: Text('Positioned'),
)),
]))
复制代码
当你看完Flutter Widge文档的时候,咱们忽然发现一个略显尴尬的问题:组件是否显示怎么控制?貌似全部的组件中都没有这个属性!这不坑了,咋办?
目前看方法无非以下几个:
核心将该真实widget或者widget树从renderTree中移除。
具体到实践级别主要分为两个:
@override
Widget build(BuildContext context) {
return isVisible
? Widget //真的Widget
: new Container(); //空Widget 仅仅占位 并不显示
}
复制代码
在父容器的children字段的list中,删除掉对应的cell。
Offstage 是一个widget。Offstage的offstage属性设置为true,那么Offstage以及他的child都将不会被绘制到界面上。 sample code以下:
@override
Widget build(BuildContext context) {
return new Offstage(
offstage: !isVisible,
child:child);
}
复制代码
设置widget的透明度,使之不可见。可是这样的方法是反作用的。由于这个对应的widget树是已经通过了完整的layout&paint过程,成本高。同时设置透明度自己也要耗费必定的计算资源,形成了二次浪费。须要注意的是即使变透明了,占据的位置还在。你们慎重选择使用。 sample code以下:
@override
Widget build(BuildContext context) {
return new AnimatedOpacity(
duration: Duration(milliseconds: 10),
opacity: isVisible ? 1.0 : 0.0,
child:child);
}
复制代码
visibility的控制仍是比较麻烦的。这是Flutter设计上不符合正常习惯的一个点,须要你们重点关注。
widget是immutable的,发生变化的时候须要重建,因此谈不上状态。StatefulWidget 中的状态保持实际上是经过State类来实现的。State拥有一套本身的生命周期,下面作一个简单的介绍。
名称 | 状态 |
---|---|
initState | 插入渲染树时调用,只调用一次 |
didChangeDependencies | state依赖的对象发生变化时调用 |
didUpdateWidget | 组件状态改变时候调用,可能会调用屡次 |
build | 构建Widget时调用 |
deactivate | 当移除渲染树的时候调用 |
dispose | 组件即将销毁时调用 |
生命周期状态图以下:
几个注意点
didChangeDependencies有两种状况会被调用。
正常的退出流程中会执行deactivate而后执行dispose。可是也会出现deactivate之后不执行dispose,直接加入树中的另外一个节点的状况。
这里的状态改变包括两种可能:1.经过setState内容改变 2.父节点的state状态改变,致使孩子节点的同步变化。
须要指出的是若是想要知道App的生命周期,那么须要经过WidgetsBindingObserver的didChangeAppLifecycleState 来获取。经过该接口能够获取是生命周期在AppLifecycleState类中。经常使用状态包含以下几个:
名称 | 状态 |
---|---|
resumed | 可见并能相应用户的输入 |
inactive | 处在并不活动状态,没法处理用户相应 |
paused | 不可见并不能相应用户的输入,可是在后台继续活动中 |
一个实际场景中的例子:
在不考虑suspending的状况下:从后台切入前台生命周期变化以下:
从前台压后台生命周期变化以下:
Dart语言对大部分开发者而言是很陌生的一种语言。google为啥会选择如此'冷门'的语言来开发flutter?主要缘由以下:
我的认为是两个主要的点:
可能刚开始接触flutter的同窗最疑惑的一个问题就是widget和view的关系了。那么简单分析一下: widget是对页面UI的一种描述。他功能类有点似于android中的xml,或者web中的html。widget在渲染的时候会转化成element。Element相比于widget增长了上下文的信息。element是对应widget,在渲染树的实例化节点。因为widget是immutable的,因此同一个widget能够同时描述多个渲染树中的节点。可是Element是描述固定在渲染书中的某一个特定位置的点。简单点说widget做为一种描述是能够复用的,可是element却跟须要绘制的节点一一对应。那element是最终渲染的view么?抱歉,还不是。element绘制时会转化成rendObject。RendObject才是真正通过layout和paint并绘制在屏幕上的对象。在flutter中有三套渲染相关的tree,分别是:widget tree, element tree & rendObject tree。三者的渲染流程以下:
那可能有人会问,为何须要增增长中间这层的Element tree?
StatelessWidget是状态不可变的widget。初始状态设置之后就不可再变化。若是须要变化须要从新建立。StatefulWidget能够保存本身的状态。那问题是既然widget都是immutable的,怎么保存状态?其实Flutter是经过引入了State来保存状态。当State的状态改变时,能从新构建本节点以及孩子的Widget树来进行UI变化。注意:若是须要主动改变State的状态,须要经过setState()方法进行触发,单纯改变数据是不会引起UI改变的。
本文详细解释了基础组件的用法,也解答了一些初学者的疑惑。但愿能给刚踏上flutter学习之路的人一些帮助。若是对文本的内容有疑问或指正,欢迎告知咱们。
闲鱼技术团队是一只短小精悍的工程技术团队。咱们不只关注于业务问题的有效解决,同时咱们在推进打破技术栈分工限制(android/iOS/Html5/Server 编程模型和语言的统一)、计算机视觉技术在移动终端上的前沿实践工做。做为闲鱼技术团队的软件工程师,您有机会去展现您全部的才能和勇气,在整个产品的演进和用户问题解决中证实技术发展是改变生活方式的动力。 简历投递:guicai.gxy@alibaba-inc.com