老孟导读:Flutter中有这么一类组件,用于定位、装饰、控制子组件,好比 Container (定位、装饰)、Expanded (扩展)、SizedBox (固定尺寸)、AspectRatio (宽高比)、FractionallySizedBox (占父组件比例)。这些组件的使用频率很是高,下面一一介绍,最后给出项目中实际案例熟悉其用法。
【Flutter实战】系列文章地址:http://laomengit.com/guide/introduction/mobile_system.htmlhtml
Container 是最经常使用的组件之一,它是单容器类组件,即仅能包含一个子组件,用于装饰和定位子组件,例如设置背景颜色、形状等。git
最简单的用法以下:程序员
Container( child: Text('老孟'), )
子组件不会发生任何外观上的变化:
github
设置背景颜色:api
Container( color: Colors.blue, child: Text('老孟'), )
设置内边距( padding ) 和 外边距( margin )微信
Container( color: Colors.blue, child: Container( margin: EdgeInsets.all(10), padding: EdgeInsets.all(20), color: Colors.red, child: Text('老孟'), ), )
效果以下:
less
decoration 属性设置子组件的背景颜色、形状等。设置背景为圆形,颜色为蓝色:ide
Container( child: Text('老孟,专一分享Flutter技术及应用'), decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue), )
默认状况下,圆形的直径等于 Container 窄边长度,至关于在矩形内绘制内切圆。布局
上面的状况明显不是咱们但愿看到了,但愿背景是圆角矩形:flex
Container( child: Text('老孟,专一分享Flutter技术及应用'), padding: EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( shape: BoxShape.rectangle, borderRadius: BorderRadius.all(Radius.circular(20)), color: Colors.blue), )
除了背景咱们能够设置边框效果,代码以下:
Container( child: Text('老孟,专一分享Flutter技术及应用'), padding: EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: Colors.blue, width: 2, ), ), )
建立圆角图片和圆形图片:
Container( height: 200, width: 200, decoration: BoxDecoration( image: DecorationImage( image: NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'), fit: BoxFit.cover, ), border: Border.all( color: Colors.blue, width: 2, ), borderRadius: BorderRadius.circular(12), ), )
修改其形状为圆形,代码以下:
Container( height: 200, width: 200, decoration: BoxDecoration( image: DecorationImage( image: NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg'), fit: BoxFit.cover, ), border: Border.all( color: Colors.blue, width: 2, ), shape: BoxShape.circle, ), )
设置对齐方式为居中,背景色为蓝色,代码以下:
Container( color: Colors.blue, child: Text('老孟,一个有态度的程序员'), alignment: Alignment.center, )
注意:设置对齐方式后,Container将会充满其父控件,至关于Android中 match_parent 。
Alignment 已经封装了经常使用的位置,
经过名字就知道其位置,这里要介绍一下其余的位置,好比在距离左上角1/4处:
Container( alignment: Alignment(-.5,-.5), child: Text('老孟,专一分享Flutter技术及应用'), )
因此这里有一个很是重要的坐标系,Alignment 坐标系以下:
组件的中心为坐标原点。
设置固定的宽高属性:
Container( color: Colors.blue, child: Text('老孟,专一分享Flutter技术及应用'), alignment: Alignment.center, height: 60, width: 250, )
经过 constraints 属性设置最大/小宽、高来肯定大小,若是不设置,默认最小宽高是0,最大宽高是无限大(double.infinity),约束width代码以下:
Container( color: Colors.blue, child: Text('老孟,专一分享Flutter技术及应用'), alignment: Alignment.center, constraints: BoxConstraints( maxHeight: 100, maxWidth: 300, minHeight: 100, minWidth: 100, ), )
经过transform能够旋转、平移、缩放Container,旋转代码以下:
Container( color: Colors.blue, child: Text('老孟,专一分享Flutter技术及应用'), alignment: Alignment.center, height: 60, width: 250, transform: Matrix4.rotationZ(0.5), )
注意:Matrix4.rotationZ()参数的单位是弧度而不是角度
SizedBox 是具备固定宽高的组件,直接指定具体的宽高,用法以下:
SizedBox( height: 60, width: 200, child: Container( color: Colors.blue, alignment: Alignment.center, child: Text('老孟,专一分享Flutter技术及应用'), ), )
设置尺寸无限大,以下:
SizedBox( height: double.infinity, width: double.infinity, ... )
虽然设置了无限大,子控件是否会无限长呢?不,不会,子控件依然会受到父组件的约束,会扩展到父组件的尺寸,还有一个便捷的方式设置此方式:
SizedBox.expand( child: Text('老孟,专一分享Flutter技术及应用'), )
SizedBox 能够没有子组件,但仍然会占用空间,因此 SizedBox 很是适合控制2个组件之间的空隙,用法以下:
Column( children: <Widget>[ Container(height: 30,color: Colors.blue,), SizedBox(height: 30,), Container(height: 30,color: Colors.red,), ], )
AspectRatio 是固定宽高比的组件,用法以下:
Container( height: 300, width: 300, color: Colors.blue, alignment: Alignment.center, child: AspectRatio( aspectRatio: 2 / 1, child: Container(color: Colors.red,), ), )
aspectRatio 是宽高比,能够直接写成分数的形式,也能够写成小数的形式,但建议写成分数的形式,可读性更高。效果以下:
FractionallySizedBox 是一个相对父组件尺寸的组件,好比占父组件的70%:
Container( height: 200, width: 200, color: Colors.blue, child: FractionallySizedBox( widthFactor: .8, heightFactor: .3, child: Container( color: Colors.red, ), ), )
经过 alignment 参数控制子组件显示的位置,默认为居中,用法以下:
FractionallySizedBox( alignment: Alignment.center, ... )
Expanded、Flexible 和 Spacer 都是具备权重属性的组件,能够控制 Row、Column、Flex 的子控件如何布局的组件。
Flexible 组件能够控制 Row、Column、Flex 的子控件占满父组件,好比,Row 中有3个子组件,两边的宽是100,中间的占满剩余的空间,代码以下:
Row( children: <Widget>[ Container( color: Colors.blue, height: 50, width: 100, ), Flexible( child: Container( color: Colors.red, height: 50, ) ), Container( color: Colors.blue, height: 50, width: 100, ), ], )
仍是有3个子组件,第一个占1/6,第二个占2/6,第三个占3/6,代码以下:
Column( children: <Widget>[ Flexible( flex: 1, child: Container( color: Colors.blue, alignment: Alignment.center, child: Text('1 Flex/ 6 Total',style: TextStyle(color: Colors.white),), ), ), Flexible( flex: 2, child: Container( color: Colors.red, alignment: Alignment.center, child: Text('2 Flex/ 6 Total',style: TextStyle(color: Colors.white),), ), ), Flexible( flex: 3, child: Container( color: Colors.green, alignment: Alignment.center, child: Text('3 Flex/ 6 Total',style: TextStyle(color: Colors.white),), ), ), ], )
子组件占比 = 当前子控件 flex / 全部子组件 flex 之和。
Flexible中 fit 参数表示填满剩余空间的方式,说明以下:
这2个看上去不是很好理解啊,什么叫尽量大的填满剩余空间?何时填满?看下面的例子:
Row( children: <Widget>[ Container( color: Colors.blue, height: 50, width: 100, ), Flexible( child: Container( color: Colors.red, height: 50, child: Text('Container',style: TextStyle(color: Colors.white),), ) ), Container( color: Colors.blue, height: 50, width: 100, ), ], )
这段代码是在最上面代码的基础上给中间的红色Container添加了Text子控件,此时红色Container就不在充满空间,再给Container添加对齐方式,代码以下:
Row( children: <Widget>[ Container( color: Colors.blue, height: 50, width: 100, ), Flexible( child: Container( color: Colors.red, height: 50, alignment: Alignment.center, child: Text('Container',style: TextStyle(color: Colors.white),), ) ), Container( color: Colors.blue, height: 50, width: 100, ), ], )
此时又填满剩余空间。
你们是否还记得 Container 组件的大小是如何调整的吗?Container 默认是适配子控件大小的,但当设置对齐方式时 Container 将会填满父组件,所以是否填满剩余空间取决于子组件是否须要填满父组件。
若是把 Flexible 中子组件由 Container 改成 OutlineButton,代码以下:
Row( children: <Widget>[ Container( color: Colors.blue, height: 50, width: 100, ), Flexible( child: OutlineButton( child: Text('OutlineButton'), ), ), Container( color: Colors.blue, height: 50, width: 100, ), ], )
OutlineButton 正常状况下是不充满父组件的,所以最终的效果应该是不填满剩余空间:
下面再来介绍另外一个权重组件 Expanded ,源代码以下:
class Expanded extends Flexible { /// Creates a widget that expands a child of a [Row], [Column], or [Flex] /// so that the child fills the available space along the flex widget's /// main axis. const Expanded({ Key key, int flex = 1, @required Widget child, }) : super(key: key, flex: flex, fit: FlexFit.tight, child: child); }
Expanded 继承字 Flexible,fit 参数固定为 FlexFit.tight,也就是说 Expanded 必须(强制)填满剩余空间。上面的 OutlineButton 想要充满剩余空间能够直接使用 Expanded :
Row( children: <Widget>[ Container( color: Colors.blue, height: 50, width: 100, ), Expanded( child: OutlineButton( child: Text('OutlineButton'), ), ), Container( color: Colors.blue, height: 50, width: 100, ), ], )
Spacer 也是一个权重组件,源代码以下:
@override Widget build(BuildContext context) { return Expanded( flex: flex, child: const SizedBox.shrink(), ); }
Spacer 的本质也是 Expanded 的实现的,和Expanded的区别是:Expanded 能够设置子控件,而 Spacer 的子控件尺寸是0,所以Spacer适用于撑开 Row、Column、Flex 的子控件的空隙,用法以下:
Row( children: <Widget>[ Container(width: 100,height: 50,color: Colors.green,), Spacer(flex: 2,), Container(width: 100,height: 50,color: Colors.blue,), Spacer(), Container(width: 100,height: 50,color: Colors.red,), ], )
三个权重组建总结以下:
先看下效果:
拿到效果图先不要慌 (取出手机拍照发个朋友圈😊),整个列表每一行的布局基本同样,因此先写出一行的效果:
class _SettingItem extends StatelessWidget { const _SettingItem( {Key key, this.iconData, this.iconColor, this.title, this.suffix}) : super(key: key); final IconData iconData; final Color iconColor; final String title; final Widget suffix; @override Widget build(BuildContext context) { return Container( height: 45, child: Row( children: <Widget>[ SizedBox( width: 30, ), Icon(iconData,color: iconColor,), SizedBox( width: 30, ), Expanded( child: Text('$title'), ), suffix, SizedBox( width: 15, ), ], ), ); } }
消息中心和其余行最后的样式不同,单独封装,带红色背景的组件:
class _NotificationsText extends StatelessWidget { final String text; const _NotificationsText({Key key, this.text}) : super(key: key); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( shape: BoxShape.rectangle, borderRadius: BorderRadius.all(Radius.circular(50)), color: Colors.red), child: Text( '$text', style: TextStyle(color: Colors.white), ), ); } }
灰色后缀组件:
class _Suffix extends StatelessWidget { final String text; const _Suffix({Key key, this.text}) : super(key: key); @override Widget build(BuildContext context) { return Text( '$text', style: TextStyle(color: Colors.grey.withOpacity(.5)), ); } }
将这些封装好的组件组合起来:
class SettingDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Column( children: <Widget>[ _SettingItem( iconData: Icons.notifications, iconColor: Colors.blue, title: '消息中心', suffix: _NotificationsText( text: '2', ), ), Divider(), _SettingItem( iconData: Icons.thumb_up, iconColor: Colors.green, title: '我赞过的', suffix: _Suffix( text: '121篇', ), ), Divider(), _SettingItem( iconData: Icons.grade, iconColor: Colors.yellow, title: '收藏集', suffix: _Suffix( text: '2个', ), ), Divider(), _SettingItem( iconData: Icons.shopping_basket, iconColor: Colors.yellow, title: '已购小册', suffix: _Suffix( text: '100个', ), ), Divider(), _SettingItem( iconData: Icons.account_balance_wallet, iconColor: Colors.blue, title: '个人钱包', suffix: _Suffix( text: '10万', ), ), Divider(), _SettingItem( iconData: Icons.location_on, iconColor: Colors.grey, title: '阅读过的文章', suffix: _Suffix( text: '1034篇', ), ), Divider(), _SettingItem( iconData: Icons.local_offer, iconColor: Colors.grey, title: '标签管理', suffix: _Suffix( text: '27个', ), ), ], ); } }
至此就结束了。
先来看下效果:
关于动画部分的内容会在后面的章节具体介绍。这个效果分为3大部分:
坐标轴的实现以下:
class _Axis extends StatelessWidget { final Widget child; const _Axis({Key key, this.child}) : super(key: key); @override Widget build(BuildContext context) { return Container( decoration: BoxDecoration( border: Border( left: BorderSide(color: Colors.black, width: 2), bottom: BorderSide(color: Colors.black, width: 2), ), ), child: child, ); } }
单个柱状图实现:
class _Cylinder extends StatelessWidget { final double height; final double width; final Color color; const _Cylinder({Key key, this.height, this.width, this.color}) : super(key: key); @override Widget build(BuildContext context) { return AnimatedContainer( duration: Duration(seconds: 1), height: height, width: width, color: color, ); } }
生成多个柱状图:
final double _width = 20.0; List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0]; Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: List.generate(_heightList.length, (index) { return _Cylinder( height: _heightList[index], width: _width, color: Colors.primaries[index % Colors.primaries.length], ); }))
将此合并,而后更改每个柱状图的高度:
class CylinderChart extends StatefulWidget { @override _CylinderChartState createState() => _CylinderChartState(); } class _CylinderChartState extends State<CylinderChart> { final double _width = 20.0; List<double> _heightList = [60.0, 80.0, 100.0, 120.0, 140.0]; @override Widget build(BuildContext context) { return Center( child: Container( height: 200, width: 250, child: Stack( children: <Widget>[ _Axis(), Positioned.fill( left: 5, right: 5, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: List.generate(_heightList.length, (index) { return _Cylinder( height: _heightList[index], width: _width, color: Colors.primaries[index % Colors.primaries.length], ); })), ), Positioned( top: 0, left: 30, child: OutlineButton( child: Text('反转'), onPressed: () { setState(() { _heightList = _heightList.reversed.toList(); }); }, ), ) ], ), ), ); } }
搞定。
老孟Flutter博客地址(330个控件用法):http://laomengit.com
欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:
![]() |
![]() |