Flutter中通过Row和Column来实现线性布局,类似于Android中的LinearLayout控件。Row和Column都继承自Flex,弹性布局Flex允许子组件按照一定比例来分配父容器空间。超出屏幕显示范围会自动折行的布局称为流式布局。Flutter中通过Wrap和Flow来支持流式布局。层叠布局和Android中的Frame布局是相似的,子组件可以根据距父容器四个角的位置来确定自身的位置。绝对定位允许子组件堆叠起来(按照代码中声明的顺序)。Flutter中使用Stack和Positioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。
一、线性布局
1.1主轴和交叉轴
对于线性布局,有主轴和交叉轴之分,如果布局是沿水平方向,那么主轴就是指水平方向,而交叉轴即垂直方向;如果布局沿垂直方向,那么主轴就是指垂直方向,而交叉轴就是水平方向。在线性布局中,有两个定义对齐方式的枚举类MainAxisAlignment和CrossAxisAlignment,分别代表主轴对齐和交叉轴对齐。
主轴对齐----MainAxisAlignment
交叉轴对齐----CrossAxisAlignment
下面是MainAxisAlignment,它也是一个枚举类。
enum MainAxisAlignment { start, end, center, spaceBetween, spaceAround, spaceEvenly, }
start----将子Widget放置在尽可能靠近主轴起点的位置。如果在水平方向上使用此值,则[TextDirection]必须可用以确定起点是左侧还是右侧;如果在垂直方向上使用此值,则[VerticalDirection]必须可用以确定起点是顶部还是底部。
end----将子Widget放置在尽可能靠近主轴末端的位置。如果在水平方向上使用此值,则[TextDirection]必须可用以确定结尾是左侧还是右侧;如果在垂直方向上使用此值,则必须使用[VerticalDirection]来确定末端是顶部还是底部。
center----将子Widget放置在尽可能靠近主轴中心的位置。
spaceBetween----将空闲空间均匀地放在子Widget之间。
spaceAround----在子Widget之间以及第一个和最后一个子Widget之前和之后的一半空间之间均匀地放置可用空间。这段文字有点绕,接下来看实例就非常清晰了。
spaceEvenly----将空闲空间均匀地放在子Widget之间以及第一个和最后一个子Widget子之前和之后。
下面是CrossAxisAlignment,它也是一个枚举类。
enum CrossAxisAlignment { start, end, center, stretch, baseline, }
start----将子项的起始边缘与交叉轴的起始侧对齐。如果在水平方向上使用此值,则[TextDirection]必须可用以确定起点是左侧还是右侧;如果在垂直方向上使用此值,则[VerticalDirection]必须可用以确定起点是顶部还是底部。
end----将子Widget放置在尽可能靠近交叉轴末端的位置。如果在水平方向上使用此值,则[TextDirection]必须可用以确定结尾是左侧还是右侧;如果在垂直方向上使用此值,则必须使用[VerticalDirection]来确定末端是顶部还是底部。
center----放置子Widget使其中心与交叉轴的中心对齐。这是默认的横轴对齐方式。
stretch----要求子Widget填满交叉轴。
baseline----沿交叉轴放置子Widget,使其基线匹配。如果主轴是垂直的,则将此值视为[start](因为基线始终是水平的)。
Row({ Key key, MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start, MainAxisSize mainAxisSize = MainAxisSize.max, CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection = VerticalDirection.down, TextBaseline textBaseline, List<Widget> children = const <Widget>[], })
mainAxisAlignment----表示子组件在Row所占用的水平空间内对齐方式,如果mainAxisSize值为MainAxisSize.min,则此属性无意义,因为子组件的宽度等于Row的宽度。只有当mainAxisSize的值为MainAxisSize.max时,此属性才有意义。
textDirection----表示水平方向子组件的布局顺序(是从左往右还是从右往左),默认为系统当前Locale环境的文本方向(如中文、英语都是从左往右,而阿拉伯语是从右往左)。
mainAxisSize----表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间,此时无论子widgets实际占用多少水平空间,Row的宽度始终等于水平方向的最大宽度;而MainAxisSize.min表示尽可能少的占用水平空间,当子组件没有占满水平剩余空间,则Row的实际宽度等于所有子组件占用的的水平空间。
verticalDirection----表示Row垂直的对齐方向,默认是VerticalDirection.down,表示从上到下。
crossAxisAlignment----表示子组件在纵轴方向的对齐方式,Row的高度等于子组件中最高的子元素高度。
children----子组件数组。
textBaseline----用于对齐文本的水平线。
TextBaseline是一个枚举类,它包括alphabetic和ideographic。
Column({ Key key, MainAxisAlignment mainAxisAlignment=MainAxisAlignment.start, MainAxisSize mainAxisSize=MainAxisSize.max, CrossAxisAlignment crossAxisAlignment=CrossAxisAlignment.center, TextDirection textDirection, VerticalDirection verticalDirection=VerticalDirection.down, TextBaseline textBaseline, List<Widget>children=const<Widget>[], }) 1 2 3 4 5 6 7 8 9 10 下面来看一个例子。 import'package:flutter/material.dart'; void main()=>runApp(MyApp()); class MyApp extends StatelessWidget{ //This widget is the root of your application. override Widget build(BuildContext context){ return MaterialApp( title:'Flutter Demo', theme:ThemeData( primarySwatch:Colors.blue, ), home:MyHomePage(), ); } } class MyHomePage extends StatefulWidget{ override _MyHomePageState createState()=>_MyHomePageState(); } class _MyHomePageState extends State<MyHomePage>{ override Widget build(BuildContext context){ return Scaffold( appBar:AppBar( title:Text("Home Page"), ), body:Column(mainAxisAlignment:MainAxisAlignment.start,children:< Widget>[ Row(mainAxisAlignment:MainAxisAlignment.start,children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row(mainAxisAlignment:MainAxisAlignment.end,children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row(mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row( mainAxisAlignment:MainAxisAlignment.spaceBetween, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row( mainAxisAlignment:MainAxisAlignment.spaceAround, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row( mainAxisAlignment:MainAxisAlignment.spaceEvenly, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), ])); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 下面是对应的截图,对于Row行布局MainAxisAlignment理解就会一目了然。 接下来修改Demo,看看交叉轴控制。 class _MyHomePageState extends State<MyHomePage>{ override Widget build(BuildContext context){ return Scaffold( appBar:AppBar( title:Text("Home Page"), ), body:Column( mainAxisAlignment:MainAxisAlignment.start, children:<Widget>[ Row( mainAxisAlignment:MainAxisAlignment.start, crossAxisAlignment:CrossAxisAlignment.center, children:<Widget>[ Text( 'Test1', textScaleFactor:3.0, ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row( mainAxisAlignment:MainAxisAlignment.start, crossAxisAlignment:CrossAxisAlignment.start, children:<Widget>[ Text( 'Test1', textScaleFactor:3.0, ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Row( mainAxisAlignment:MainAxisAlignment.start, crossAxisAlignment:CrossAxisAlignment.end, children:<Widget>[ Text( 'Test1', textScaleFactor:3.0, ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), ])); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 再来研究一下交叉轴crossAxisAlignment设置为CrossAxisAlignment.stretch的效果,它要求子Widget填满交叉轴。对于Row来说交叉轴就是垂直方向。 class _MyHomePageState extends State<MyHomePage>{ override Widget build(BuildContext context){ return Scaffold( appBar:AppBar( title:Text("Home Page"), ), body:Row( mainAxisAlignment:MainAxisAlignment.start, crossAxisAlignment:CrossAxisAlignment.stretch, children:<Widget>[ Text( 'Test1', textScaleFactor:3.0, ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ])); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 二、弹性布局 我们先来看一下Flex如何使用,接着来看搭配Expanded组件使用方法。 2.1 Flex Flex组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用Row或Column会方便一些,因为Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本上都可以使用Row或Column。Flex本身功能是很强大的,它也可以和Expanded组件配合实现弹性布局。 Flex({ Key key, required this.direction, this.mainAxisAlignment=MainAxisAlignment.start, this.mainAxisSize=MainAxisSize.max, this.crossAxisAlignment=CrossAxisAlignment.center, this.textDirection, this.verticalDirection=VerticalDirection.down, this.textBaseline, List<Widget>children=const<Widget>[], }) 1 2 3 4 5 6 7 8 9 10 11 和Row、Column比起来多了一个direction,它是弹性布局的方向,Row默认为水平方向,Column默认为垂直方向。修改上面的例子把Row和Column替换为Flex,可以看出效果的确是一样的。 class _MyHomePageState extends State<MyHomePage>{ override Widget build(BuildContext context){ return Scaffold( appBar:AppBar( title:Text("Home Page"), ), body:Flex( mainAxisAlignment:MainAxisAlignment.start, direction:Axis.vertical, children:<Widget>[ Flex( mainAxisAlignment:MainAxisAlignment.start, direction:Axis.horizontal, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), Flex( mainAxisAlignment:MainAxisAlignment.end, direction:Axis.horizontal, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), ])); } } 1 2 44 45 46 47 48 49 来看上面代码的运行结果: 2.2 Expanded 可以按比例“扩伸”Row、Column和Flex子组件所占用的空间。这和android里面控件的weight类似。 const Expanded({ Key key, int flex=1, required Widget child, }) 1 2 3 4 5 flex----为弹性系数,如果为0或null,则child是没有弹性的,即不会被扩伸占用的空间。如果大于0,所有的Expanded按照其flex的比例来分割主轴的全部可用空间。 class _MyHomePageState extends State<MyHomePage>{ override Widget build(BuildContext context){ return Scaffold( appBar:AppBar( title:Text("Home Page"), ), body:Flex( mainAxisAlignment:MainAxisAlignment.start, direction:Axis.vertical, children:<Widget>[ Expanded( flex:2, child:Flex( mainAxisAlignment:MainAxisAlignment.start, direction:Axis.horizontal, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), ), Expanded( flex:2, child:Flex( mainAxisAlignment:MainAxisAlignment.end, direction:Axis.horizontal, children:<Widget>[ Text( 'Test1', ), Text( 'Test2', ), Text( 'Test3', ), Text( 'Test4', ) ]), ) ])); } }