Flutter
做为时下最流行的技术之一,凭借其出色的性能以及抹平多端的差别优点,早已引发大批技术爱好者的关注,甚至一些闲鱼
,美团
,腾讯
等大公司均已开始使用。虽然目前其生态尚未彻底成熟,但身靠背后的Google
加持,其发展速度已经足够惊人,能够预见未来对Flutter
开发人员的需求也会随之增加。html
不管是为了如今的技术尝鲜仍是未来的潮流趋势,都9102年了,做为一个前端开发者,彷佛没有理由不去尝试它。正是带着这样的心理,笔者也开始学习Flutter
,同时建了一个用于练习的仓库,后续全部代码都会托管在上面,欢迎star,一块儿学习。这是我写的Flutter系列文章:前端
今天分享的是Flutter中最经常使用到的一些基础组件,它们是构成UI界面的基础元素:容器
,行
,列
,绝对定位布局
,文本
,图片
和图标
等。git
Container
组件是最经常使用的布局组件之一,能够认为它是web开发中的div
,rn开发中的View
。其每每能够用来控制大小、背景颜色、边框、阴影、内外边距和内容排列方式等。咱们先来看下其构造函数:github
Container({ Key key, double width, double height, this.margin, this.padding, Color color, this.alignment, BoxConstraints constraints, Decoration decoration, this.foregroundDecoration, this.transform, this.child, })
width
,height
,margin
,padding
这些属性的含义和咱们已经熟知的并无区别。惟一须要注意的是,margin
和padding
的赋值不是一个简单的数字,由于其有left
, top
, right
, bottom
四个方向的值须要设置。Flutter
提供了EdgeInsets
这个类,帮助咱们方便地生成四个方向的值。一般状况下,咱们可能会用到EdgeInsets
的4种构造方法:web
EdgeInsets.all(value)
: 用于设置4个方向同样的值;EdgeInsets.only(left: val1, top: val2, right: val3, bottom: val4)
: 能够单独设置某个方向的值;EdgeInsets.symmetric(horizontal: val1, vertical: val2)
: 用于设置水平/垂直方向上的值;EdgeInsets.fromLTRB(left, top, right, bottom)
: 按照左上右下的顺序设置4个方向的值。color
该属性的含义是背景颜色,等同于web/rn中的backgroundColor。须要注意的是Flutter
中有一个专门表示颜色的Color
类,而非咱们经常使用的字符串。不过咱们能够很是轻松地进行转换,举个栗子:segmentfault
在web/rn中咱们会用'#FF0000'
或'red'
来表示红色,而在Flutter中,咱们能够用Color(0xFFFF0000)
或Colors.red
来表示。api
alignment
该属性是用来决定Container
组件的子组件将以何种方式进行排列(PS:不再用为怎么居中操心了)。其可选值一般会用到:网络
Alignment.topLeft
: 左上Alignment.topCenter
: 上中Alignment.topRight
: 右上Alignment.centerLeft
: 左中Alignment.center
: 居中Alignment.centerRight
: 右中Alignment.bottomLeft
: 左下Alignment.bottomCenter
: 下中Alignment.bottomRight
: 右下constraints
在web/rn中咱们一般会用minWidth
/maxWidth
/minHeight
/maxHeight
等属性来限制容器的宽高。在Flutter
中,你须要使用BoxConstraints
(盒约束)来实现该功能。app
// 容器的大小将被限制在[100*100 ~ 200*200]内 BoxConstraints( minWidth: 100, maxWidth: 200, minHeight: 100, maxHeight: 200, )
decoration
该属性很是强大,字面意思是装饰,由于经过它你能够设置边框
,阴影
,渐变
,圆角
等经常使用属性。BoxDecoration
继承自Decoration
类,所以咱们一般会生成一个BoxDecoration
实例来设置这些属性。less
1) 边框
能够用Border.all
构造函数直接生成4条边框,也能够用Border
构造函数单独设置不一样方向上的边框。不过使人惊讶的是官方提供的边框居然不支持虚线
(issue在这里)。
// 同时设置4条边框:1px粗细的黑色实线边框 BoxDecoration( border: Border.all(color: Colors.black, width: 1, style: BorderStyle.solid) ) // 设置单边框:上边框为1px粗细的黑色实线边框,右边框为1px粗细的红色实线边框 BoxDecoration( border: Border( top: BorderSide(color: Colors.black, width: 1, style: BorderStyle.solid), right: BorderSide(color: Colors.red, width: 1, style: BorderStyle.solid), ), )
2) 阴影
阴影属性和web中的boxShadow
几乎没有区别,能够指定x
,y
,blur
,spread
,color
等属性。
BoxDecoration( boxShadow: [ BoxShadow( offset: Offset(0, 0), blurRadius: 6, spreadRadius: 10, color: Color.fromARGB(20, 0, 0, 0), ), ], )
3) 渐变
若是你不想容器的背景颜色是单调的,能够尝试用gradient
属性。Flutter
同时支持线性渐变
和径向渐变
:
// 从左到右,红色到蓝色的线性渐变 BoxDecoration( gradient: LinearGradient( begin: Alignment.centerLeft, end: Alignment.centerRight, colors: [Colors.red, Colors.blue], ), ) // 从中心向四周扩散,红色到蓝色的径向渐变 BoxDecoration( gradient: RadialGradient( center: Alignment.center, colors: [Colors.red, Colors.blue], ), )
4) 圆角
一般状况下,你可能会用到BorderRadius.circular
构造函数来同时设置4个角的圆角,或是BorderRadius.only
构造函数来单独设置某几个角的圆角:
// 同时设置4个角的圆角为5 BoxDecoration( borderRadius: BorderRadius.circular(5), ) // 设置单圆角:左上角的圆角为5,右上角的圆角为10 BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(5), topRight: Radius.circular(10), ), )
transform
transform
属性和咱们在web/rn中常常用到的基本也没有差异,主要包括:平移
,缩放
、旋转
和倾斜
。在Flutter中,封装了矩阵变换类Matrix4
帮助咱们进行变换:
translationValues(x, y, z)
: 平移x, y, z;rotationX(radians)
: x轴旋转radians弧度;rotationY(radians)
: y轴旋转radians弧度;rotationZ(radians)
: z轴旋转radians弧度;skew(alpha, beta)
: x轴倾斜alpha度,y轴倾斜beta度;skewX(alpha)
: x轴倾斜alpha度;skewY(beta)
: y轴倾斜beta度;Container
组件的属性很丰富,虽然有些用法上和web/rn有些许差别,但基本上大同小异,因此过渡起来也不会有什么障碍。另外,因为Container
组件是单子节点组件,也就是只容许子节点有一个。因此在布局上,不少时候咱们会用Row
和Column
组件进行行
/列
布局。
Row
和Column
组件其实和web/rn中的Flex布局
(弹性盒子)特别类似,或者咱们能够就这么理解。使用Flex布局
的同窗对主轴
和次轴
的概念确定都已经十分熟悉,Row
组件的主轴就是横向,Column
组件的主轴就是纵向。且它们的构造函数十分类似(已省略不经常使用属性):
Row({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, MainAxisSize mainAxisSize = MainAxisSize.max, List<Widget> children = const <Widget>[], }) Column({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, MainAxisSize mainAxisSize = MainAxisSize.max, List<Widget> children = const <Widget>[], })
mainAxisAlignment
该属性的含义是主轴排列方式,根据上述构造函数能够知道Row
和Column
组件在主轴方向上默认都是从start开始,也就是说Row
组件默认从左到右开始排列子组件,Column
组件默认从上到下开始排列子组件。
固然,你还可使用其余的可选值:
crossAxisAlignment
该属性的含义是次轴排列方式,根据上述构造函数能够知道Row
和Column
组件在次轴方向上默认都是居中。
这里有一点须要特别注意:因为Column
组件次轴方向上(即水平)默认是居中对齐,因此水平方向上不会撑满其父容器,此时须要指定CrossAxisAlignment.stretch
才能够。
另外,crossAxisAlignment其余的可选值有:
mainAxisSize
字面意思上来讲,该属性指的是在主轴上的尺寸。其实就是指在主轴方向上,是包裹其内容,仍是撑满其父容器。它的可选值有MainAxisSize.min
和MainAxisSize.max
。因为其默认值都是MainAxisSize.max
,因此主轴方向上默认大小都是尽量撑满父容器的。
因为Row
/Column
组件和咱们熟悉的Flex布局
很是类似,因此上手起来很是容易,几乎零学习成本。
绝对定位布局在web/rn开发中也是使用频率较高的一种布局方式,Flutter
也提供了相应的组件实现,须要将Stack
和Positioned
组件搭配在一块儿使用。好比下方的这个例子就是建立了一个黄色的盒子,而且在其四个角落放置了4个红色的小正方形。Stack
组件就是绝对定位的容器,Positioned
组件经过left
,top
,right
,bottom
四个方向上的属性值来决定其在父容器中的位置。
Container( height: 100, color: Colors.yellow, child: Stack( children: <Widget>[ Positioned( left: 10, top: 10, child: Container(width: 10, height: 10, color: Colors.red), ), Positioned( right: 10, top: 10, child: Container(width: 10, height: 10, color: Colors.red), ), Positioned( left: 10, bottom: 10, child: Container(width: 10, height: 10, color: Colors.red), ), Positioned( right: 10, bottom: 10, child: Container(width: 10, height: 10, color: Colors.red), ), ], ), )
Text
组件也是平常开发中最经常使用的基础组件之一,咱们一般用它来展现文本信息。来看下其构造函数(已省略不经常使用属性):
const Text( this.data, { Key key, this.style, this.textAlign, this.softWrap, this.overflow, this.maxLines, })
data
: 显示的文本信息;style
: 文本样式,Flutter
提供了一个TextStyle
类,最经常使用的fontSize
,fontWeight
,color
,backgroundColor
和shadows
等属性都是经过它设置的;textAlign
: 文字对齐方式,经常使用可选值有TextAlign
的left
,right
,center
和justify
;softWrap
: 文字是否换行;overflow
: 当文字溢出的时候,以何种方式处理(默认直接截断)。可选值有TextOverflow
的clip
,fade
,ellipsis
和visible
;maxLines
: 当文字超过最大行数还没显示完的时候,就会根据overflow
属性决定如何截断处理。Flutter
的Text
组件足够灵活,提供了各类属性让咱们定制,不过通常状况下,咱们更多地只需下方几行代码就足够了:
Text( '这是测试文本', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: Color(0xFF999999), ), )
除了上述的应用场景外,有时咱们还会遇到富文本
的需求(即一段文本中,可能须要不一样的字体样式)。好比在一些UI设计中常常会遇到表示价格的时候,¥
符号比金额
的字号小点。对于此类需求,咱们能够用Flutter
提供的Text.rich
构造函数来建立相应的文本组件:
Text.rich(TextSpan( children: [ TextSpan( '¥', style: TextStyle( fontSize: 12, color: Color(0xFFFF7528), ), ), TextSpan( '258', style: TextStyle( fontSize: 15, color: Color(0xFFFF7528), ), ), ] ))
Image
图片组件做为丰富内容的基础组件之一,平常开发中的使用频率也很是高。看下其构造函数(已省略不经常使用属性):
Image({ Key key, @required this.image, this.width, this.height, this.color, this.fit, this.repeat = ImageRepeat.noRepeat, })
image
: 图片源,最经常使用到主要有两种(AssetImage
和NetworkImage
)。使用AssetImage
以前,须要在pubspec.yaml
文件中声明好图片资源,而后才能使用;而NextworkImage
指定图片的网络地址便可,主要是在加载一些网络图片时会用到;width
: 图片宽度;height
: 图片高度;color
: 图片的背景颜色,当网络图片未加载完毕以前,会显示该背景颜色;fit
: 当咱们但愿图片根据容器大小进行适配而不是指定固定的宽高值时,能够经过该属性来实现。其可选值有BoxFit
的fill
,contain
,cover
,fitWidth
,fitHeight
,none
和scaleDown
;repeat
: 决定当图片实际大小不足指定大小时是否使用重复效果。另外,Flutter
还提供了Image.network
和Image.asset
构造函数,实际上是语法糖。好比下方的两段代码结果是彻底同样的:
Image( image: NetworkImage('https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1402367109,4157195964&fm=27&gp=0.jpg'), width: 100, height: 100, ) Image.network( 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1402367109,4157195964&fm=27&gp=0.jpg', width: 100, height: 100, )
Icon
(图标组件)Icon
图标组件相比于图片有着放大不会失真的优点,在平常开发中也是常常会被用到。Flutter
更是直接内置了一套Material
风格的图标(你能够在这里预览全部的图标类型)。看下构造函数:
const Icon( this.icon, { Key key, this.size, this.color, })
icon
: 图标类型;size
: 图标大小;color
: 图标颜色。经过上一节的介绍,咱们对Container
,Row
,Column
,Stack
,Positioned
,Text
,Image
和Icon
组件有了初步的认识。接下来,就让咱们经过一个实际的例子来加深理解和记忆。
根据上述卡片中的内容,咱们能够定义一些字段。为了规范开发流程,咱们先给卡片定义一个数据类型的类,这样在后续的开发过程当中也能更好地对数据进行Mock和管理:
class PetCardViewModel { /// 封面地址 final String coverUrl; /// 用户头像地址 final String userImgUrl; /// 用户名 final String userName; /// 用户描述 final String description; /// 话题 final String topic; /// 发布时间 final String publishTime; /// 发布内容 final String publishContent; /// 回复数量 final int replies; /// 喜欢数量 final int likes; /// 分享数量 final int shares; const PetCardViewModel({ this.coverUrl, this.userImgUrl, this.userName, this.description, this.topic, this.publishTime, this.publishContent, this.replies, this.likes, this.shares, }); }
根据给的视觉图,咱们能够将总体进行拆分,一共划分红4个部分:Cover
,UserInfo
,PublishContent
和InteractionArea
。为此,咱们能够搭起代码的基本骨架:
class PetCard extends StatelessWidget { final PetCardViewModel data; const PetCard({ Key key, this.data, }) : super(key: key); Widget renderCover() { } Widget renderUserInfo() { } Widget renderPublishContent() { } Widget renderInteractionArea() { } @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( blurRadius: 6, spreadRadius: 4, color: Color.fromARGB(20, 0, 0, 0), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ this.renderCover(), this.renderUserInfo(), this.renderPublishContent(), this.renderInteractionArea(), ], ), ); } }
为了更好的凸现图片的效果,这里加了一个蒙层,因此此处恰好能够用得上Stack
/Positioned
布局和LinearGradient
渐变,Dom结构以下:
Widget renderCover() { return Stack( fit: StackFit.passthrough, children: <Widget>[ ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), ), child: Image.network( data.coverUrl, height: 200, fit: BoxFit.fitWidth, ), ), Positioned( left: 0, top: 100, right: 0, bottom: 0, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Color.fromARGB(0, 0, 0, 0), Color.fromARGB(80, 0, 0, 0), ], ), ), ), ), ], ); }
用户信息区域就很是适合使用Row
和Column
组件来进行布局,Dom结构以下:
Widget renderUserInfo() { return Container( margin: EdgeInsets.only(top: 16), padding: EdgeInsets.symmetric(horizontal: 16), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Row( children: <Widget>[ CircleAvatar( radius: 20, backgroundColor: Color(0xFFCCCCCC), backgroundImage: NetworkImage(data.userImgUrl), ), Padding(padding: EdgeInsets.only(left: 8)), Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Text( data.userName, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), Padding(padding: EdgeInsets.only(top: 2)), Text( data.description, style: TextStyle( fontSize: 12, color: Color(0xFF999999), ), ), ], ), ], ), Text( data.publishTime, style: TextStyle( fontSize: 13, color: Color(0xFF999999), ), ), ], ), ); }
经过这块区域的UI练习,咱们能够实践Container
组件设置不一样的borderRadius
,以及Text
组件文本内容超出时的截断处理,Dom结构以下:
Widget renderPublishContent() { return Container( margin: EdgeInsets.only(top: 16), padding: EdgeInsets.symmetric(horizontal: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Container( margin: EdgeInsets.only(bottom: 14), padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Color(0xFFFFC600), borderRadius: BorderRadius.only( topRight: Radius.circular(8), bottomLeft: Radius.circular(8), bottomRight: Radius.circular(8), ), ), child: Text( '# ${data.topic}', style: TextStyle( fontSize: 12, color: Colors.white, ), ), ), Text( data.publishContent, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 15, fontWeight: FontWeight.bold, color: Color(0xFF333333), ), ), ], ), ); }
在这个模块,咱们会用到Icon
图标组件,能够控制其大小和颜色等属性,Dom结构以下:
Widget renderInteractionArea() { return Container( margin: EdgeInsets.symmetric(vertical: 16), padding: EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Row( children: <Widget>[ Icon( Icons.message, size: 16, color: Color(0xFF999999), ), Padding(padding: EdgeInsets.only(left: 6)), Text( data.replies.toString(), style: TextStyle( fontSize: 15, color: Color(0xFF999999), ), ), ], ), Row( children: <Widget>[ Icon( Icons.favorite, size: 16, color: Color(0xFFFFC600), ), Padding(padding: EdgeInsets.only(left: 6)), Text( data.likes.toString(), style: TextStyle( fontSize: 15, color: Color(0xFF999999), ), ), ], ), Row( children: <Widget>[ Icon( Icons.share, size: 16, color: Color(0xFF999999), ), Padding(padding: EdgeInsets.only(left: 6)), Text( data.shares.toString(), style: TextStyle( fontSize: 15, color: Color(0xFF999999), ), ), ], ), ], ), ); }
经过上面的一个例子,咱们成功地把一个看起来复杂的UI界面一步步拆解,将以前提到的组件都用了个遍,而且最终获得了不错的效果。其实,平常开发中90%以上的需求都离不开上述提到的基础组件。所以,只要稍加练习,熟悉了Flutter
中的基础组件用法,就已经算是迈出了一大步哦~
这里还有银行卡和朋友圈的UI练习例子,因为篇幅缘由就不贴代码了,能够去github仓库看。
本文首先介绍了Flutter
中构建UI界面最经常使用的基础组件(容器
,行
,列
,绝对定位布局
,文本
,图片
和图标
)用法。接着,介绍了一个较复杂的UI实战例子。经过对Dom结构的层层拆解,前文提到过的组件获得一个综合运用,也算是巩固了前面所学的概念知识。
不过最后不得不吐槽一句:Flutter
的嵌套真的很难受。。。若是不对UI布局进行模块拆分,那绝对是噩梦般的体验。并且不像web/rn开发样式能够单独抽离,Flutter
这种将样式当作属性的处理方式,一眼看去真的很难理清dom结构,对于新接手代码的开发人员而言,须要费点时间理解。。。