Flutter 布局
要点
Flutter 布局的核心机制是 widget。在 Flutter 中,几乎所有东西都是 widget — 甚至布局模型都是 widget。你在 Flutter 应用程序中看到的图像,图标和文本都是 widget。此外不能直接看到的也是 widget,例如用来排列、限制和对齐可见 widget 的行、列和网格。
布局
流程
- 选择一个布局Widget
- 创建一个可见Widget
- 将可见Widget添加到布局Widget
- 将布局Widget添加到页面
- 运行应用
Center
步骤1: 选择Center Widget
// 步骤4: 一个 Flutter 应用本身就是一个 widget,大多数 widget 都有一个 build() 方法,在应用的 build() 方法中实例化和返回一个 widget 会让它显示出来。
Widget build(BuildContext context) {return const MaterialApp(home: Scaffold(// 步骤3: 通过child属性将Text Widget添加到Center Widget中body: Center(child: Text('Hello World!'), // 创建可见的 Widget),),);
}
所有布局Widget
都具有以下任一项:
- 一个
child
属性,当布局Widget
只包含一个子项,比如Center
,Container
- 一个
children
属性,当布局Widget
包含多个子项,比如Row
,Column
,ListView
和Stack
对应 Material 应用,可以使用 Scaffold widget,它提供默认的 banner 背景颜色,还有用于添加抽屉、提示条和底部列表弹窗的 API
Row(列) , Column(行),Container , spacing, Padding, SizedBox, margin,Expanded
build(BuildContext context) {return MaterialApp(title: "Flutter布局学习",home: Scaffold(appBar: AppBar(title: Text("Flutter 布局学习")),body: Column(spacing: 6,children: [Column(spacing: 10,children: [Text("回乡偶书", style: TextStyle(fontSize: 24)),Text("唐.贺知章"),Text("少小离家老大回,乡音无改鬓毛衰。",style: TextStyle(color: Colors.black,fontSize: 16,height: 1.5, // 控制行高),),Text("儿童相见不相识,笑问客从何处来。",style: TextStyle(color: Colors.black, fontSize: 16),),],),Column(spacing: 6,// Text 设置左对齐// 由于Text Widget的大小是自动包裹内容的,// 所以设置Text的Alignment.left不能生效。// 当布局是Column时,可以设置Column的// crossAxisAlignment: CrossAxisAlignment.start。crossAxisAlignment: CrossAxisAlignment.start,children: [Padding(padding: EdgeInsets.fromLTRB(16, 8, 16, 4),child: Text("译文:", textAlign: TextAlign.left),),Padding(padding: EdgeInsets.fromLTRB(16, 8, 16, 4),child: Text("年少时离乡老年才归家,我的乡音虽未改变,但鬓角的毛发却已经疏落。家乡的儿童们看见我,没有一个认识我。他们笑着询问我:你是从哪里来的呀?",),),Padding(padding: EdgeInsets.fromLTRB(16, 8, 8, 4),child: Text("创作背景:"),),Padding(padding: EdgeInsets.fromLTRB(16, 8, 16, 4),child: SizedBox(width: 360,child: Text("贺知章在公元744年(天宝三载),辞去朝廷官职,告老返回故乡越州永兴(今浙江萧山),时已八十六岁,这时,距他中年离乡已经很久了,此诗便作于此时。",),),),],),Row(crossAxisAlignment: CrossAxisAlignment.start,spacing: 10,children: [Padding(padding: EdgeInsets.fromLTRB(16, 0, 0, 0),// Image.asset方法里设置的图片路径是相对于pubspec.yaml文件child: Image.asset('assets/images/layout_01@3x.png',width: 160,alignment: Alignment.center,),),// 设置Text Widget离右侧屏幕距离为16// 1. Expanded占满了父Widget的剩余空间// 2. Container size 包装Text Widget// 3. margin设置控制右边距Expanded(child: Column(spacing: 10,crossAxisAlignment: CrossAxisAlignment.start,children: [Container(// EdgeInsets.only: 仅设置一个方向的间距margin: EdgeInsets.only(right: 16),// L:Left,T:Top,R:Right,B:Bottom 通过英文来记忆4参数// padding: EdgeInsets.fromLTRB(0, 0, 16,0),padding: EdgeInsets.only(right: 16),decoration: BoxDecoration(border: Border.all()),child: Text("问题:请问这首诗表达了作者的什么样的感情?",maxLines: 4,softWrap: true,),),Container(margin: EdgeInsets.only(right: 30),child: Text("如果现在是北京时间早上八点整,我飞往巴黎,到达后巴黎当地时间为早上八点整,请问:我的生命相对延长了吗?",),),Text("答:你把表的电池扣了你是不是就不死了?"),],),),],),],),),);}
Widget
名称 | 说明 |
---|---|
Column | 垂直布局,可以包含多个子Widget,子Widget从上往下延伸 |
Row | 水平布局,可以包含多个子Widget,子Widget从左往右延伸 |
Container | 包含一个子Widget,一般用来包装显示的Widget,在这里设置宽高,内外边距 |
Padding | 包含一个子Widget,控制内边距 |
SizedBox | 包含一个子Widget,固定大小 |
Expanded | 包含一个子Widget,填充父Widget的剩余空间 |
GridView && ListView
使用 GridView 将 widget 作为二维列表展示。 GridView 提供两个预制的列表,或者你可以自定义网格。当 GridView 检测到内容太长而无法适应渲染盒时,它就会自动支持滚动。
GridView的特点
- 在网格中使用widget
- 当列的内容超出渲染容器时,它会自动支持滚动
- 创建自定义的网格,或者使用
GridView.count
设置列的数量/GridView.extent
设置单元格最大宽度
Widget _buildGrid() => GridView.extent(// 单元格最大宽度,因为子Widget是从上往下延伸,所以主轴是上下,交叉轴是左右,子Widget左右方向的是宽度maxCrossAxisExtent: 150,// 上下左右内边距padding: const EdgeInsets.all(4),// 上下单元格间距mainAxisSpacing: 4,// 左右单元格间距crossAxisSpacing: 4,children: _buildGridTileList(30),
);// 函数支持箭头形式的简写,当函数体只有一行表达式时,可以通过=>简写表示返回表达式的值。
// 从0递增到count-1获取assets/images/pic$i.jpg的图标作为GridView的children
// Generates a list of values.
// Creates a list with [length] positions and fills it with values created by calling [generator]
// for each index in the range 0 .. length - 1 in increasing order.
List<Widget> _buildGridTileList(int count) =>List.generate(count, (i) => Image.asset('assets/images/pic$i.jpg'));
ListView
是一个和Column
相似的Widget
,当内容大于自己的渲染框时,就会自动支持滚动
ListView
的特点
- 一个用来组织盒子中列表的专用
Column
- 可以水平或者垂直布局
- 当检测到空间不足时,会自动支持滚动
- 比
Column
的配置少,使用更容易,且支持滚动
Widget _buildList() {return ListView(children: [_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),_tile('The Castro Theater', '429 Castro St', Icons.theaters),_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),_tile('Roxie Theater', '3117 16th St', Icons.theaters),_tile('United Artists Stonestown Twin','501 Buckingham Way',Icons.theaters,),_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),// 分割线const Divider(),_tile('K\'s Kitchen', '757 Monterey Blvd', Icons.restaurant),_tile('Emmy\'s Restaurant', '1923 Ocean Ave', Icons.restaurant),_tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),_tile('La Ciccia', '291 30th St', Icons.restaurant),],);}ListTile _tile(String title, String subtitle, IconData icon) {return ListTile(title: Text(title,style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 20),),subtitle: Text(subtitle),leading: Icon(icon, color: Colors.blue[500]),);}
里面每一行都是ListTile
,它是Material
库中专用的行Widget
,它可以很轻松的创建一个包含三行文本以及可选的行前和行尾图标的行。 ListTile
在 Card
或者 ListView
中最常用,但是也可以在别处使用。
Stack
Stack
布局用于叠加场景,比如图片上添加文字描述
Stack
的特点
- 用于覆盖另一个
Widget
– 类似栈:后进(渲染)先出(显示在外层) - 子列表中的第一个
Widget
是基础Widget
;后面的子项覆盖在基础Widget
的顶部 Stack
的内容是无法滚动的- 可以剪切掉超出渲染框的子项
Card
Material
库中的Card
包含相关有价值信息,几乎可以由任何Widget
组成,但是通常和ListTile
一起使用,Card
只有一个子项,这个子项可以是列,行,列表,网格或者其它支持多个子项的Widget
。默认情况下,Card
的大小是0x0像素。可以使用SizeBox
控制Card
的大小
在 Flutter
中,Card
有轻微的圆角和阴影来使它具有 3D
效果。改变 Card
的 elevation
属性可以控制阴影效果
Widget _buildCard() {return SizedBox(height: 210,child: Card(child: Column(children: [ListTile(title: const Text('1625 Main Street',style: TextStyle(fontWeight: FontWeight.w500),),subtitle: const Text('My City, CA 99984'),leading: Icon(Icons.restaurant_menu, color: Colors.blue[500]),),const Divider(),ListTile(title: const Text('(408) 555-1212',style: TextStyle(fontWeight: FontWeight.w500),),leading: Icon(Icons.contact_phone, color: Colors.blue[500]),),ListTile(title: const Text('costa@example.com'),leading: Icon(Icons.contact_mail, color: Colors.blue[500]),),],),),);}
一些收获
CrossAxisAlignment(交叉轴) && MainAxisAlignment(主轴)
MainAxisAlignment
(主轴)就是与当前子Widget
延伸方向一致的轴,而CrossAxisAlignment
(交叉轴)就是与当前子Widget
延伸方向垂直的轴
比如Column
: 子Widget
是从上往下延伸的,所以主轴就是垂直方向,交叉轴就是水平方向的。
即Column
的 MainAxisAlignment
属性用于控制子Widget
在主轴(垂直轴)上的对齐方式,Column
的 CrossAxisAlignment
属性用于控制子Widget
在交叉轴(水平轴)上的对齐方式
当设置为CrossAxisAlignment.start
时表示Column
中的元素左对齐。
格式化
通过快捷键 ⌥(option) + ⇧(shift) + f
可以快速对代码格式化
内容不可见
Flutter
界面会告警提示当前的布局超过多少像素,存在内容不可见的问题
Waiting for another flutter command to release the startup lock
问题: VS Code 创建新的Flutter工程一直卡在Waiting for another flutter command to release the startup lock
方案:
# 执行后关闭VS Code 重新打开再次创建Flutter工程
killall -9 dart
ERR : Proxy failed to establish tunnel (503 Forwarding failure), uri=//pub.flutter-io.cn:443
一般是网络原因,我是终端加了端口转发,然后一直访问不了pub.flutter-io.cn
,所以去掉代理
# 去掉代理 + 重新执行 flutter pub get --verbose
export https_proxy=
export http_proxy=
参考
- Waiting for another flutter command to release the startup lock
- flutter 设置Text居左无效
- Flutter 中的 Column 小部件:全面指南
- https://docs.flutter.cn/ui/layout/constraints/
- GridView.extent中maxCrossAxisExtent的作用