老孟导读:Flutter中有这么一类组件,用于定位、装饰、控制子组件,比如 Container (定位、装饰)、Expanded (扩展)、SizedBox (固定尺寸)、AspectRatio (宽高比)、FractionallySizedBox (占父组件比例)。这些组件的使用频率非常高,下面一一介绍,最后给出项目中实际案例熟悉其用法。

【Flutter实战】系列文章地址:http://laomengit.com/guide/introduction/mobile_system.html

Container

Container 是最常用的组件之一,它是单容器类组件,即仅能包含一个子组件,用于装饰和定位子组件,例如设置背景颜色、形状等。

最简单的用法如下:

Container(
child: Text('老孟'),
)

子组件不会发生任何外观上的变化:

设置背景颜色:

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('老孟'),
),
)

效果如下:

decoration 属性设置子组件的背景颜色、形状等。设置背景为圆形,颜色为蓝色:

Container(
child: Text('老孟,专注分享Flutter技术及应用'),
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
)

默认情况下,圆形的直径等于 Container 窄边长度,相当于在矩形内绘制内切圆。

上面的情况明显不是我们希望看到了,希望背景是圆角矩形:

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 是具有固定宽高的组件,直接指定具体的宽高,用法如下:

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

AspectRatio 是固定宽高比的组件,用法如下:

Container(
height: 300,
width: 300,
color: Colors.blue,
alignment: Alignment.center,
child: AspectRatio(
aspectRatio: 2 / 1,
child: Container(color: Colors.red,),
),
)

aspectRatio 是宽高比,可以直接写成分数的形式,也可以写成小数的形式,但建议写成分数的形式,可读性更高。效果如下:

FractionallySizedBox

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,
...
)

权重组件

ExpandedFlexibleSpacer 都是具有权重属性的组件,可以控制 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 参数表示填满剩余空间的方式,说明如下:

  • tight:必须(强制)填满剩余空间。
  • loose:尽可能大的填满剩余空间,但是可以不填满。

这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,),
],
)

三个权重组建总结如下

  • Spacer 是通过 Expanded 实现的,Expanded继承自Flexible。
  • 填满剩余空间直接使用Expanded更方便。
  • Spacer 用于撑开 Row、Column、Flex 的子组件的空隙。

仿 掘金-我 效果

先看下效果:

拿到效果图先不要慌 (取出手机拍照发个朋友圈),整个列表每一行的布局基本一样,所以先写出一行的效果:

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大部分:

  1. 坐标轴,左边和底部黑色直线。
  2. 矩形柱状图。
  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】:

【Flutter实战】定位装饰权重组件及柱状图案例的更多相关文章

  1. 【Flutter实战】六大布局组件及半圆菜单案例

    老孟导读:Flutter中布局组件有水平 / 垂直布局组件( Row 和 Column ).叠加布局组件( Stack 和 IndexedStack ).流式布局组件( Wrap )和 自定义布局组件 ...

  2. Flutter实战】文本组件及五大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第二篇,这一篇讲解文本组件,文本组件包括文本展示组件(Text和RichText)和文本输入组件(TextField),基础用法和五个案例助你快速 ...

  3. 【Flutter实战】图片组件及四大案例

    老孟导读:大家好,这是[Flutter实战]系列文章的第三篇,这一篇讲解图片组件,Image有很多高级用法,希望对您有所帮助. 图片组件是Flutter基础组件之一,和文本组件一样必不可少.图片组件包 ...

  4. 【Flutter 实战】一文学会20多个动画组件

    老孟导读:此篇文章是 Flutter 动画系列文章第三篇,后续还有动画序列.过度动画.转场动画.自定义动画等. Flutter 系统提供了20多个动画组件,只要你把前面[动画核心](文末有链接)的文章 ...

  5. 【Flutter 实战】1.20版本更新及新增组件

    老孟导读:Flutter 1.20 更新了 Slider.RangeSlider.日期选择器组件.时间选择器组件的样式,新增了交换组件:InteractiveViewer,下面详细介绍其用法. 滑块 ...

  6. 《Flutter实战》开源电子书

    <Flutter实战>开源电子书 <Flutter实战> 开源了,本书为 Flutter中文网开源电子书项目,本书系统介绍了Flutter技术的各个方面,本书属于原创书籍(并非 ...

  7. Flutter入门之无状态组件

    Flutter核心理念 flutter组件采用函数式响应框架构建,它的灵感来自于React.它设计的核心思想是组件外构建UI,简单解释一下就是组件鉴于它当前的配置和状态来描述它的视图应该是怎样的,当组 ...

  8. Flutter实战视频-移动电商-02.Flutter实战建立项目和编写入口文件

    02.Flutter实战建立项目和编写入口文件 创建项目: flutter create flutter_shop 创建完成之后呢,它会提示我们, 进入flutter_shop的目录,然后执行flut ...

  9. 【Flutter实战】移动技术发展史

    老孟导读:大家好,这是[Flutter实战]系列文章的第一篇,这并不是一篇Flutter技术文章,而是介绍智能手机操作系统.跨平台技术的演进以及我对各种跨平台技术看法的文章. 智能手机操作系统 塞班( ...

随机推荐

  1. 【Ubuntu】Ubuntu系统启动过程中,输入用户名与密码后登录一直卡在紫色界面问题(未解决,最后通过重装系统)

    0. 前言 由于本电脑为公用电脑,可能由于其他人点了图像界面中推荐的内核更新,导致原来安装的NVIDIA显卡驱动 430 与升级后的 5.0 内核不兼容,从而导致输入用户名后登录一直卡在紫色界面.在排 ...

  2. [COCOS2DX-LUA]0-005.cocos2dx中关于全面屏和折叠屏的适配的一些见解

    1.随着科技的发展,我们可以看到从iphoneX的刘海屏开始,引发了各种全面屏和异形屏的出现.这是科技的进步,但是对于各大的应用厂商来说,苦不堪言. 2.当然 ,吐槽归吐槽,我们还是要理智的去对待这个 ...

  3. [JavaWeb基础] 025.JAVA把word转换成html

    用第三方插件POI把word文档转换成HTML,下面直接上代码 package com.babybus.sdteam.wordtopdf; import java.io.BufferedWriter; ...

  4. Robot Framework(15)- 扩展关键字

    如果你还想从头学起Robot Framework,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1770899.html 前言 什么是扩展 ...

  5. 关于hexo-blog-encrypt插件输入密码后无响应的问题

    解决方案:更改网站为https协议 具体请查看: https://github.com/MikeCoder/hexo-blog-encrypt/issues/114

  6. 50个SQL语句(MySQL版) 问题三

    --------------------------表结构-------------------------- student(StuId,StuName,StuAge,StuSex) 学生表 tea ...

  7. 2019-ICLR-DARTS: Differentiable Architecture Search-论文阅读

    DARTS 2019-ICLR-DARTS Differentiable Architecture Search Hanxiao Liu.Karen Simonyan.Yiming Yang GitH ...

  8. Take advantage of Checkra1n to Jailbreak iDevice for App analysis

    An unpatchable bootrom exploit called "checkm8" works on all iDevices up until the iPhone ...

  9. CentOS 虚拟机 下载及 搭建

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) CentOS 虚拟机安装包下载 : 链接:https://pan.baidu.com/s/1JDIASm ...

  10. Java实现 LeetCode 747 至少是其他数字两倍的最大数(暴力)

    747. 至少是其他数字两倍的最大数 在一个给定的数组nums中,总是存在一个最大元素 . 查找数组中的最大元素是否至少是数组中每个其他数字的两倍. 如果是,则返回最大元素的索引,否则返回-1. 示例 ...