版权声明:本文为作者原创书籍。转载请注明作者和出处,未经授权,严禁私自转载,侵权必究!!!
情感语录:侠之大者为国为民,如果做不了侠者也别轻易做小人,多一些包容,其实很多事没你想像地那么糟糕!
哈喽,大家好,欢迎来到本章节,上一章节我们讲了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)),
);
}
}
上面代码的布局效果如下:
实例1.png
读者在练习的时候尽量多手动写下,多尝试,比如我这里并没去细讲 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: () {});
}
}
上述练习代码效果大致如下:
实例2.gif
本章实战:
由于本章讲述的组件相对较多但并不复杂,本章节实战环节跳过,但希望你在学习的时候多结合源码手动练习下,虽然不难但本章节的东西在开发中是使用非常频繁的。
实例源码地址: https://github.com/zhengzaihong/flutter_learn
好了本章节就此结束,又到了说再见的时候了,如果你喜欢请留下你的小红星,你们的支持才是创作的动力,如有错误,请热心的你留言指正, 谢谢大家观看,下章再会 O(∩_∩)O
网友评论