版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录:侠之大者为国为民,如果做不了侠者也别轻易做小人,多一些包容,其实很多事没你想像地那么糟糕!
哈喽,大家好,欢迎来到本章节,上一章节我们讲了Image组件、ListView组件、GridView组件 还记得吗?知识点回顾 戳这里 Fultter基础第二章在前两章节的捯饬下我相信你已经爱上了Flutter或者逐渐开始喜欢了上了,学习都是枯燥的,贵在坚持,加油!!!!
本章简要:
1、内距组件Paddiing
2、水平布局组件Row
3、垂直布局组件Column
4、权重布局组件Expanded
5、层叠 ( 帧 )组件Stack
6、纵横比组件AspectRatio
7、卡片组件Card
8、包裹组件Wrap
一、Paddiing组件
在 Android 原生控件中都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素之间的间距。
属性 说明
padding padding值, EdgeInsetss 设置填充的值
child 子组件
二、Row组件
Row水平布局组件其实理解也很简单,你可以看做是Android原生中LinearLayout布局控件属性为水平方向,或者说是一个横向的ListView控件,说是横向的ListView控件到更为贴切,因为在Flutter中Row的子元素接收的是一个List<Widget>,这也使得Row控件变得更加灵活易用,开发中Row和Column使用频率是很高的。
属性 说明
mainAxisAlignment 主轴的排序方式
crossAxisAlignment 次轴的排序方式
children 组件子元素
三、Column组件
Column组件的用法其实和Row是一模一样的,只是一个是控制水平方向的,一个是垂直方向,会了Row组件的使用,Column自然也就会使用了。
属性 说明
mainAxisAlignment 主轴的排序方式
crossAxisAlignment 次轴的排序方式
children 组件子元素
四、Expanded组件
Expanded 可以用在 Row 和 Column 布局中,其的目的就是控制子元素之间的权重比,这和在原生中LinearLayout的权重使用是一个道理。
属性 说明
flex 元素站整个父 Row /Column 的比例
child 子元素
这上面的4个组件的属性并未全部列出,只是给出了常用的属性,在学习的同时,还是建议结合源码查看,只有对源码熟悉了学的才快嘛,通过上面的简单介绍,也对这4个组件有了简单的了解,下面我现将这学到的这几个组件进行下练习,毕竟光说不练那都是假把式嘛,还是要实操的O(∩_∩)O
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: ViewLayout()),
);
}
}
class ViewLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text(
"这是Paddiing的简单应用",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 10,
),
//用容器包装下 方便观察
Container(
color: Colors.deepPurple,
height: 60,
child: Padding(
//可以通过fromLTRB分别设置左上右下的内距,也可以用all统一设置
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Image.network(
'http://qukufile2.qianqian.com/data2/pic/246708144/246708144.jpg',
fit: BoxFit.fitHeight),
),
),
SizedBox(
height: 10,
),
Text(
"这是Row应用(横向布局)",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
//spaceEvenly 主轴的排列方式最为常见
crossAxisAlignment: CrossAxisAlignment.start,
//用的比较少
children: <Widget>[
IconWidget(Icons.search, color: Colors.blue),
IconWidget(Icons.home, color: Colors.orange),
IconWidget(Icons.select_all, color: Colors.red),
],
),
SizedBox(
height: 5,
),
Text(
"这是Column应用(垂直布局)",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 5,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
//spaceEvenly 主轴的排列方式最为常见
crossAxisAlignment: CrossAxisAlignment.center,
//用的比较少
children: <Widget>[
IconWidget(Icons.search, color: Colors.blue),
SizedBox(
height: 5,
),
IconWidget(Icons.home, color: Colors.orange),
SizedBox(
height: 5,
),
IconWidget(Icons.select_all, color: Colors.red),
],
),
SizedBox(
height: 5,
),
Text(
"这是Expanded应用权重(1:2:1)",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 5,
),
Row(
children: <Widget>[
Expanded(
flex: 1, child: IconWidget(Icons.search, color: Colors.blue)),
Expanded(
flex: 2,
child: IconWidget(Icons.home, color: Colors.orange),
),
Expanded(
flex: 1,
child: IconWidget(Icons.select_all, color: Colors.red),
),
],
),
SizedBox(
height: 10,
),
Text(
"这是Expanded应用权重(1:2)",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 10,
),
Row(
children: <Widget>[
Expanded(
flex: 1, child: IconWidget(Icons.search, color: Colors.blue)),
Expanded(
flex: 2,
child: IconWidget(Icons.home, color: Colors.orange),
),
],
),
],
);
}
}
//封装一个简单的Icon 方便调度
class IconWidget extends StatelessWidget {
double size = 32.0;
Color color = Colors.red;
IconData icon;
IconWidget(this.icon, {this.color, this.size});
@override
Widget build(BuildContext context) {
return Container(
height: 50.0,
width: 50.0,
color: this.color,
child:
Center(child: Icon(this.icon, size: this.size, color: Colors.white)),
);
}
}
上面代码的布局效果如下:

读者在练习的时候尽量多手动写下,多尝试,比如我这里并没去细讲 Column 、Row中的MainAxisAlignment的属性以及效果,希望你在练习的时候能自己尝试感受下。
五、Stack组件
Stack 表示堆的意思,就是在该组件下放入的组件,会一层一层的累积,这样说理解起来可能有点抽象。想像一下,这就好比修房子,Stack就是房子的地基,而新建的第一层盖在地基上,第二层则盖在第一层上。有过Android原生开发的同学应该很好能理解,它就相当于原生中的FrameLayout控件。在Flutter中我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局,Stack是定位发生的容器,只有在 Stack中,绝对定位的 Widget 才会生效
属性 说明
alignment 配置所有子元素的显示位置
children 子组件
其中关键的属性就是 children,除了几个样式控制的参数之外,通过 children 可以传入一个List<Widget>,可以使列表用在 Stack 中进行绝对定位。
5.1 Align组件
Stack 组件中结合 Align 组件可以控制每个子元素的显示位置,既实现绝对定位
属性 说明
alignment 配置所有子元素的显示位置
child 子组件
无论是单独使用Stack或者结合Align 使用,其中对元素位置控制都是通过 alignment来指定的,下面我们来结合它的源码分析下:
///
/// The [x] and [y] arguments must not be null.
const Alignment(this.x, this.y)
: assert(x != null),
assert(y != null);
/// The distance fraction in the horizontal direction.
///
/// A value of -1.0 corresponds to the leftmost edge. A value of 1.0
/// corresponds to the rightmost edge. Values are not limited to that range;
/// values less than -1.0 represent positions to the left of the left edge,
/// and values greater than 1.0 represent positions to the right of the right
/// edge.
final double x;
/// The distance fraction in the vertical direction.
///
/// A value of -1.0 corresponds to the topmost edge. A value of 1.0
/// corresponds to the bottommost edge. Values are not limited to that range;
/// values less than -1.0 represent positions above the top, and values
/// greater than 1.0 represent positions below the bottom.
final double y;
@override
double get _x => x;
@override
double get _start => 0.0;
@override
double get _y => y;
/// The top left corner.
static const Alignment topLeft = Alignment(-1.0, -1.0);
/// The center point along the top edge.
static const Alignment topCenter = Alignment(0.0, -1.0);
/// The top right corner.
static const Alignment topRight = Alignment(1.0, -1.0);
/// The center point along the left edge.
static const Alignment centerLeft = Alignment(-1.0, 0.0);
/// The center point, both horizontally and vertically.
static const Alignment center = Alignment(0.0, 0.0);
/// The center point along the right edge.
static const Alignment centerRight = Alignment(1.0, 0.0);
/// The bottom left corner.
static const Alignment bottomLeft = Alignment(-1.0, 1.0);
/// The center point along the bottom edge.
static const Alignment bottomCenter = Alignment(0.0, 1.0);
/// The bottom right corner.
static const Alignment bottomRight = Alignment(1.0, 1.0);
这段源码一目了然,比较简单,首先Alignment是一个没有无参构造函数的类,创建该对象时必须传入非null 且是double类型的x、y值,否则报错。接下来是对 x、y值的描述,我们可以简单的理解为x(水平方向)的位置在当前容器的可见区域从左到右是(-1到1之间),超出该值范围,既超出了该容器的边,既不可以见,y(垂直方向)同理,只是从上到下而已。最后源码中帮我列出了9种常用的位置常量方便我们使用。
5.2 Positioned组件
Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置,在绝对定位中Positioned可能更适合我们的开发习惯:
属性 说明
top 子元素距离顶部的距离
bottom 子元素距离底部的距离
left 子元素距离左侧距离
right 子元素距离右侧距离
child 子组件
width 子元素的宽度
height 子元素的高度
下面我们从源码中去看下注释文档。
/// Creates a widget that controls where a child of a [Stack] is positioned.
///
/// Only two out of the three horizontal values ([left], [right],
/// [width]), and only two out of the three vertical values ([top],
/// [bottom], [height]), can be set. In each case, at least one of
/// the three must be null.
///
/// See also:
///
/// * [Positioned.directional], which specifies the widget's horizontal
/// position using `start` and `end` rather than `left` and `right`.
/// * [PositionedDirectional], which is similar to [Positioned.directional]
/// but adapts to the ambient [Directionality].
const Positioned({
Key key,
this.left,
this.top,
this.right,
this.bottom,
this.width,
this.height,
@required Widget child,
}) : assert(left == null || right == null || width == null),
assert(top == null || bottom == null || height == null),
super(key: key, child: child);
大致可以理解是在使用width的时候不能同时使用left和right,既不能同时出现这三个属性,否则报错,如果在使用width的同时又使用了left或right,既分别表示子元素从左边或者右边开始对齐移动left或right个单位值。同理height、top、bottom三者的使用亦是如此。
六、Card组件
Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。
属性 说明
margin 外边距
child 子组件
Shape Card 的阴影效果,默认的阴影效果为圆角的长方形边。
七、AspectRatio组件
AspectRatio 的作用是根据设置调整子元素 child 的宽高比, 它首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。
属性 说明
aspectRatio 宽高比,最终可能不会根据这个值去布局,
具体则要看综合因素,外层是否允许按照这
种比率进行布局,这只是一个参考值
child 子组件
该组件在理解上可能比较难,下面我们来看一个实例:
new Container(
height: 100,
child: AspectRatio(
aspectRatio: 3.0 / 1.0,
child: Container(
color: Colors.red,
),
))
示例代码是定义了一个高度为100的区域,内部AspectRatio比率设置为3,最终AspectRatio的宽是300,高是100的一个区域。
八、Wrap组件
开篇第一个例子讲了Column 或者 Row + Expanded Widget 来实现 flex 布局,但是 Row + Expanded 的实现方式有个致命问题是无法自动换行,而真正的 flex 布局是有一个 wrap 属性的,对于一行无法铺开的场景非常实用。Wrap组件可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空间不足时,则向 crossAxis 上去扩展显示。
/// Creates a wrap layout.
///
/// By default, the wrap layout is horizontal and both the children and the
/// runs are aligned to the start.
///
/// The [textDirection] argument defaults to the ambient [Directionality], if
/// any. If there is no ambient directionality, and a text direction is going
/// to be necessary to decide which direction to lay the children in or to
/// disambiguate `start` or `end` values for the main or cross axis
/// directions, the [textDirection] must not be null.
Wrap({
Key key,
this.direction = Axis.horizontal,
this.alignment = WrapAlignment.start,
this.spacing = 0.0,
this.runAlignment = WrapAlignment.start,
this.runSpacing = 0.0,
this.crossAxisAlignment = WrapCrossAlignment.start,
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
}) : super(key: key, children: children);
常用属性说明:
属性 说明
direction 主轴的方向,默认水平
alignment 主轴的对其方式
spacing 主轴方向上的间距
textDirection 文本方向
verticalDirection 定义了children 摆放顺序,默认是 down,见Flex 相关属性介绍。
runAlignment run 的对齐方式, 可以理解为新的行或者列,如果是水平方向布局的话,
run 可以理解为新的一行
runSpacing run 的间距
又讲了4个组件,加上开篇讲的4个组件本章节你已经掌握了8个常用的组件了,下面我们还是先对刚刚学到的4个组件来做个小的练习,加深下印象。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(
title: Text("呆萌"),
),
body: ViewLayout()),
);
}
}
class ViewLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text(
"这是Stack的简单应用",
style: TextStyle(fontSize: 18, color: Colors.green),
),
SizedBox(
height: 5,
),
//用容器包装下设置背景色 方便观察
Container(
color: Colors.deepPurple,
height: 60,
child: Stack(
alignment: Alignment(-1, -1), // 将子元素定位在左上,或者使用常量 topLeft
children: <Widget>[
Container(
width: 100,
height: 40,
color: Colors.red,
),
Text('你说什么?', style: TextStyle(fontSize: 16, color: Colors.white))
],
),
),
SizedBox(
height: 5,
),
Text(
"这是Stack结合Align的应用",
style: TextStyle(fontSize: 18, color: Colors.green),
),
SizedBox(
height: 5,
),
Container(
height: 100,
width: 300,
color: Colors.red,
child: Stack(
children: <Widget>[
Align(
alignment: Alignment(1, 0), //定位最右边,垂直居中
child: Icon(Icons.home, size: 30, color: Colors.white),
),
Align(
alignment: Alignment.center, //定位在容器的中心位置
child: Icon(Icons.search, size: 30, color: Colors.white),
),
Align(
alignment: Alignment.bottomLeft, //定位在容器的左下
child: Icon(Icons.ac_unit, size: 30, color: Colors.white),
)
],
),
),
SizedBox(
height: 5,
),
Text(
"这是Stack结合Positioned的应用",
style: TextStyle(fontSize: 18, color: Colors.green),
),
SizedBox(
height: 5,
),
//用容器包装下设置背景色 方便观察
Container(
color: Colors.deepPurple,
height: 60,
child: Stack(
children: <Widget>[
Positioned(
right: 10, // 让子元素从右边开始对齐
width: 120, //指定宽度为120个单位
child: Icon(Icons.access_alarm, size: 30, color: Colors.white),
),
Positioned(
bottom: 0, // 让子元素从底部开始对齐
left: 100,
height: 50,
child: Icon(Icons.memory, size: 30, color: Colors.white),
),
Positioned(
left: 5, // 让子元素从左边开始对齐
width: 150,
child: Text('你很帅,你造吗?',
style: TextStyle(fontSize: 16, color: Colors.white)),
)
],
),
),
SizedBox(
height: 5,
),
Text(
"这是Card应用",
style: TextStyle(fontSize: 18, color: Colors.green),
),
SizedBox(
height: 5,
),
Card(
margin: EdgeInsets.all(5),
color: Colors.cyan,
elevation: 10,
//10个单位的阴影
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(14.0))),
//设置圆角
child: Column(
children: <Widget>[
ListTile(
title: Text("Mr.Z", style: TextStyle(fontSize: 18)),
subtitle: Text("工程师", style: TextStyle(fontSize: 14)),
),
ListTile(
title: Text("电话:xxxxx"),
),
],
),
),
SizedBox(
height: 5,
),
Text(
"这是AspectRatio应用",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 5,
),
Container(
height: 100,
child: AspectRatio(
aspectRatio: 3.0 / 1.0,
child: Container(
color: Colors.red,
),
)),
SizedBox(
height: 5,
),
Text(
"这是Wrap应用",
style: TextStyle(fontSize: 18, color: Colors.deepOrangeAccent),
),
SizedBox(
height: 5,
),
Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
alignment:WrapAlignment.spaceEvenly,
children: <Widget>[
ButtonItem("盗墓笔记"),
ButtonItem("鬼吹灯"),
ButtonItem("桃花怪大战菊花侠"),
ButtonItem("无主之城"),
ButtonItem("琅琊榜"),
ButtonItem("仙剑奇侠传"),
ButtonItem("风云决"),
ButtonItem("哪吒"),
ButtonItem("玄门大师"),
ButtonItem("废材兄弟"),
ButtonItem("爱情公寓"),
],
)
],
);
}
}
//封装一个简单的Button 方便调度
class ButtonItem extends StatelessWidget {
final String text;
const ButtonItem(this.text, {Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text(this.text,style: TextStyle(color: Colors.red),),
textColor: Theme.of(context).cardColor,
onPressed: () {});
}
}
上述练习代码效果大致如下:

本章实战:
由于本章讲述的组件相对较多但并不复杂,本章节实战环节跳过,但希望你在学习的时候多结合源码手动练习下,虽然不难但本章节的东西在开发中是使用非常频繁的。
实例源码地址: https://github.com/zhengzaihong/flutter_learn
好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O