Flutter 布局组件
ListView 列表组件
列表布局是我们项目开发中最常用的一种布局方式。Flutter 中我们可以通过 ListView 来定义列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有以下分类
- 垂直列表
- 垂直图文列表
- 水平列表
- 动态列表
列表组件常用参数
名称 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | Axis.horizontal 水平列表 Axis.vertical 垂直列表 |
padding | EdgeInsetsGeometry | 内边距 |
resolve | bool | 组件反向排序 |
children | List | 列表元素 |
Divider 横线, ListTile 一行内容
垂直列表
class MyApp extends StatelessWidget {
const MyApp({super.key});
List<Widget> _initListData() {
List<Widget> list = [];
for (var i = 0; i < 20; i++) {
list.add(
ListTile(
leading: Image.network("https://ronhai.com/media/images/article/homebrew.png"),
title: Text('华北黄淮高温雨今起强势登场${i + 1}'),
subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
trailing:
Image.network("https://ronhai.com/media/images/article/homebrew.png"),
),
);
}
return list;
}
@override
Widget build(BuildContext context) {
return ListView(
children: _initListData(),
);
}
}
垂直图文列表
class MyApp extends StatelessWidget {
const MyApp({super.key});
List<Widget> _initListData() {
List<Widget> list = [];
for (var i = 0; i < 20; i++) {
list.add(
Image.network('https://ronhai.com/media/images/article/homebrew.png'),
);
list.add(Container(
height: 40,
padding: EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Text(
'我是一个标题${i}',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
));
}
return list;
}
@override
Widget build(BuildContext context) {
return ListView(
children: _initListData(),
);
}
}
水平列表
可以左右滑动
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
Container(
width: 160.0,
color: Colors.red,
padding: const EdgeInsets.all(8.0),
),
const SizedBox(width: 10),
SizedBox(
width: 160.0,
child: Column(
children: [
SizedBox(
width: double.infinity,
height: 160,
child: Image.network(
'https://ronhai.com/media/images/article/homebrew.png',
fit: BoxFit.cover,
),
),
const SizedBox(
height: 8,
),
const Text('World'),
],
),
),
const SizedBox(width: 10),
Container(
width: 160.0,
color: Colors.blue,
),
const SizedBox(width: 10),
Container(
width: 160.0,
color: Colors.green,
),
const SizedBox(width: 10),
Container(
width: 160.0,
color: Colors.yellow,
),
const SizedBox(width: 10),
Container(
width: 160.0,
color: Colors.orange,
),
],
),
);
}
}
ListView.builder
动态实现列表
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text(
'动态列表',
),
),
body: MyApp()),
),
);
}
class MyApp extends StatelessWidget {
final List list = [];
MyApp({super.key}) {
for (var i = 0; i < 10; i++) {
list.add("我是一个列表--$i");
}
}
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
},
);
}
}
GridView 网格布局组件
GridView 网格布局在实际项目中用的也是非常多的,当我们想让可以滚动的元素使用矩阵方式排列的时候。此时我们可以用网格列表组件 GridView 实现布局。
GridView 创建网格列表主要有下面三种方式
- 可以通过 GridView.count 实现网格布局
- 可以通过 GridView.extent 实现网格布局
- 通过 GridView.builder 实现动态网格布局
常用属性
名称 | 类型 | 说明 |
---|---|---|
scrollDirection | Axis | 滚动方法 |
padding | EdgeInsetsGeometry | 内边距 |
resolve | bool | 组件反向排序 |
crossAxisSpacing | double | 水平子 Widget 之间间距 |
mainAxisSpacing | double | 垂直子 Widget 之间间距 |
crossAxisCount | int 用在 GridView.count | 一行的 Widget 数量 |
maxCrossAxisExtent | double 用在 GridView.extent | 横轴子元素的最大长度 |
childAspectRatio | double | 子 Widget 宽高比例 |
children | [ ] | |
gridDelegate | SliverGridDelegateWithFixedCrossAxisCount SliverGridDelegateWithMaxCrossAxisExtent | 控制布局主要用在 GridView.builder 里面 |
实现网格布局
GridView.count 构造函数内部使用了 SliverGridDelegateWithFixedCrossAxisCount,我们通过它可以快速的创建横轴固定数量子元素的 GridView
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
leading: const Icon(Icons.more_horiz),
centerTitle: true,
title: const Text('网格布局'),
actions: const [
Icon(Icons.add),
],
),
body: MyHomePage()),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 5, //一行的Widget数量
childAspectRatio: 2, //子Widget宽高比例
crossAxisSpacing: 10,
mainAxisSpacing: 10,
children: const [
Icon(Icons.home),
Icon(Icons.ac_unit),
Icon(Icons.search),
Icon(Icons.settings),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.circle),
Icon(Icons.dashboard),
],
);
}
}
实现网格布局
GridView.extent 构造函数内部使用了 SliverGridDelegateWithMaxCrossAxisExtent,我们通过它可以快速的创建横轴子元素为固定最大长度的的 GridView。
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return GridView.extent(
maxCrossAxisExtent: 50, //横轴子元素的最大宽度
childAspectRatio: 1, //子Widget高比例
children: const [
Icon(Icons.home),
Icon(Icons.ac_unit),
Icon(Icons.search),
Icon(Icons.settings),
Icon(Icons.airport_shuttle),
Icon(Icons.all_inclusive),
Icon(Icons.beach_access),
Icon(Icons.cake),
Icon(Icons.circle),
Icon(Icons.dashboard),
],
);
}
}
GridView.builder
SliverGridDelegateWithFixedCrossAxisCount 实现 count 功能
SliverGridDelegateWithMaxCrossAxisExtent 实现 extent 功能
设置同 count 和 extent 一样,gridDelegate 的类型选择哪种类型渲染
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
leading: const Icon(Icons.more_horiz),
centerTitle: true,
title: const Text('网格布局'),
actions: const [
Icon(Icons.add),
],
),
body: MyHomePage()),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
itemBuilder: (context, index) {
return Container(
color: Colors.blue,
child: Center(
child: Text(
'Item ${index + 1}',
style: const TextStyle(color: Colors.white),
),
),
);
},
itemCount: 15, //数量
),
);
}
}
Flex Expanded 弹性布局
Flex 组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用 Row 或 Column 会方便一些,因为 Row 和 Column 都继承自 Flex ,参数基本相同,所以能使用 Flex 的地方基本上都可以使用 Row 或 Column 。 Flex 本身功能是很强大的,它也可以和 Expanded 组件配合实现弹性布局 。Row 和 Column 是继承 flex 布局,使用 Expanded 必需在这三个组件中,具有 flex 的容器中
水平弹性布局
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 1,
child: Image.network(
'https://ronhai.com/media/images/article/flutter.png')),
Expanded(
flex: 1,
child: Image.network(
'https://ronhai.com/media/images/article/flutter.png')),
const Expanded(
flex: 2,
child: Text('Hello World'),
)
],
);
}
}
垂直弹性布局
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Flex(
direction: Axis.vertical,
children: [
Expanded(
flex: 1,
child: Container(
color: Colors.red,
child: const Center(child: Text('1')),
)),
Expanded(
flex: 2,
child: Container(
color: Colors.pink,
child: const Center(child: Text('2')),
)),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.center,
width: double.infinity,
color: Colors.blue,
child: const Text(
'222',
style: TextStyle(color: Colors.white),
)))
],
);
}
}
Row 和 Column 线性布局
Row 水平布局组件
属性 | 说明 |
---|---|
mainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | 次轴的排序方式 |
children | 组件子元素 |
Column 垂直布局组件
属性 | 说明 |
---|---|
mainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | 次轴的排序方式 |
children | 组件子元素 |
double
double.infinity 和 double.maxFinite 可以让当前元素的 width 或者 height 达到父元素的尺寸
元素铺满
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
color: Colors.grey[200],
child: const Column(
crossAxisAlignment: CrossAxisAlignment.stretch, //交叉轴对齐方式
mainAxisAlignment: MainAxisAlignment.spaceEvenly, //主轴对齐方式
children: [
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
Icon(Icons.volunteer_activism),
],
),
);
}
}
Paddiing 组件
在 html 中常见的布局标签都有 padding 属性,但是 Flutter 中很多 Widget 是没有 padding 属性。这个时候我们可以用 Padding 组件处理容器与子元素之间的间距。
属性 | 说明 |
---|---|
padding | padding 值, EdgeInsetss 设置填充的值 |
child | 子组件 |
Stack、Align、Positioned 层叠布局
Alignment 的参数
/// 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);
Stack 组件
Stack 表示堆的意思,我们可以用 Stack 或者 Stack 结合 Align 或者 Stack 结合 Positiond 来实现页面的定位布局
属性 | 说明 |
---|---|
alignment | 配置所有子元素的显示位置 |
children | 子组件 |
Stack Align
Align 组件可以调整子组件的位置 , Stack 组件中结合 Align 组件也可以控制每个子元素的显示位置
属性 | 说明 |
---|---|
alignment | 配置所有子元素的显示位置 |
child | 子组件 |
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 300,
height: 400,
color: Colors.green,
child: const Stack(
// alignment: Alignment.center,
children: [
Align(
alignment: Alignment(1, 1),
child: Icon(Icons.home, size: 40, color: Colors.white),
),
Align(
alignment: Alignment.center,
child: Icon(Icons.search, size: 30, color: Colors.white),
),
Align(
alignment: Alignment.bottomRight,
child: Icon(Icons.settings_applications,
size: 30, color: Colors.white),
)
],
)),
);
}
}
Stack Positioned
Stack 组件中结合 Positioned 组件也可以控制每个子元素的显示位置,Positione 必须是 Stack 的直接组件才有效果
属性 | 说明 |
---|---|
top | 子元素距离顶部的距离 |
bottom | 子元素距离底部的距离 |
left | 子元素距离左侧距离 |
right | 子元素距离右侧距离 |
child | 子组件 |
width | 组件的高度 (注意:宽度和高度必须是固定值,没法使用 double.infinity) |
height | 子组件的高度 |
获取屏幕
FlutterMediaQuery 获取屏幕宽度和高度
final size =MediaQuery.of(context).size;
组件的 build 方法中可以通过,=MediaQuery.of(context).size;
Widget build(BuildContext context) {
final size =MediaQuery.of(context).size;
final width =size.width;
final height =size.height;
}
实战定位布局
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
List<Widget> _TitleList() {
List<Widget> list = [];
for (int i = 0; i < 30; i++) {
list.add(ListTile(
title: Text("这是一个标题${i + 1} "),
));
}
return list;
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('页面布局'),
),
body: Stack(
children: [
ListView(
children: _TitleList(),
),
Positioned(
left: 0,
top: 0,
width: size.width,
height: 50,
child: Container(
alignment: Alignment.center,
color: Colors.red,
child: const Text(
'我是标题头',
style: TextStyle(
color: Colors.white,
),
)))
],
));
}
}
AspectRatio
AspectRatio 的作用是根据设置调整子元素 child 的宽高比。AspectRatio 首先会在布局限制条件允许的范围内尽可能的扩展,widget 的高度是由宽度和比率决定的,类似于 BoxFit 中的 contain,按照固定比率去尽量占满区域。如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio 最终将会去优先适应布局限制条件,而忽略所设置的比率。
属性 | 说明 |
---|---|
aspectRatio | 宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值 |
child | 子组件 |
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 300,
// height: 300,
color: Colors.green,
child: AspectRatio(
aspectRatio: 1.0 / 2.5,
child: Container(
color: Colors.red,
),
)));
}
}
Card 组件
Card 是卡片组件块,内容可以由大多数类型的 Widget 构成,Card 具有圆角和阴影,这让它看起来有立体感。
属性 | 说明 |
---|---|
margin | 外边距 |
child | 子组件 |
elevation | 阴影值的深度 |
color | 背景颜色 |
shadowColor | 阴影颜色 |
margin | 外边距 |
clipBehavior | clipBehavior 内容溢出的剪切方式 Clip.none 不剪切 Clip.hardEdge 裁剪但不应用抗锯齿 Clip.antiAlias 裁剪而且抗锯齿 Clip.antiAliasWithSaveLayer 带有抗锯齿的剪辑,并在剪辑之后立即保存 saveLayer |
Shape | Card 的阴影效果,默认的阴影效果为圆角的长方形边。 shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)) ), |
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: const [
Card(
child: ListTile(
leading: Icon(Icons.account_circle),
title: Text('我是隆海'),
subtitle: Text('官网地址:ronhai.com'),
),
),
Card(
child: ListTile(
leading: Icon(Icons.account_circle),
title: Text('我是隆海'),
subtitle: Text('官网地址:ronhai.com'),
),
),
Card(
child: ListTile(
leading: Icon(Icons.account_circle),
title: Text('我是隆海'),
subtitle: Text('官网地址:ronhai.com'),
),
),
],
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: const [
Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
// color: Colors.red,
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: const <Widget>[
ListTile(
title: Text("小帮", style: TextStyle(fontSize: 28)),
subtitle: Text("高级软件工程师"),
),
Divider(),
ListTile(
title: Text("1234567890123"),
),
ListTile(title: Text("四川成都"))
],
),
),
Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
// color: Colors.red,
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: const <Widget>[
ListTile(
title: Text("小帮", style: TextStyle(fontSize: 28)),
subtitle: Text("高级软件工程师"),
),
Divider(),
ListTile(
title: Text("1234567890123"),
),
ListTile(title: Text("四川成都"))
],
),
)
],
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return ListView(
children: [
Card(
elevation: 10,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 20 / 9,
child: Image.asset(
'images/bannar.jpg',
fit: BoxFit.cover,
),
),
const ListTile(
leading: Icon(Icons.home),
title: Text('Ronhai'),
subtitle: Text('ronhai.com'),
),
],
),
),
Card(
elevation: 10,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 20 / 9,
child: Image.asset(
'images/bannar.jpg',
fit: BoxFit.cover,
),
),
const ListTile(
leading: Icon(Icons.home),
title: Text('Ronhai'),
subtitle: Text('ronhai.com'),
),
],
),
),
],
);
}
}
Wrap 组件
Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Column 表现几乎一致。但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,mainAxis 上空间不足时,则向 crossAxis 上去扩展显示
属性 | 说明 |
---|---|
direction | 主轴的方向,默认水平 |
alignment | 主轴的对其方式 |
spacing | 主轴方向上的间距 |
textDirection | 文本方向 |
verticalDirection | 定义了 children 摆放顺序,默认是 down,见 Flex 相关属性介绍。 |
runAlignment | run 的对齐方式。run 可以理解为新的行或者列,如果是水平方向布局的话, run 可以理解为新的一行 |
runSpacing | 垂直间距 |
自定义 Button 按钮
class MyButton extends StatelessWidget {
// 自定义按钮
final text;
final void Function()? onPressed;
const MyButton(this.text, {super.key, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.black26),
foregroundColor: WidgetStateProperty.all(Colors.white)),
child: Text(text));
}
}
用 Wrap 使用 Button
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
List<Widget> _initButton() {
List<Widget> list = [];
for (var i = 0; i < 30; i++) {
list.add(MyButton('第${i}集', onPressed: () {}));
}
return list;
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: Wrap(
spacing: 10, // 水平间距
runSpacing: 20, // 垂直间距
// textDirection: TextDirection.ltr, //文本方向
// direction: Axis.vertical, //主轴的对其方式
alignment: WrapAlignment.start,
children: _initButton(),
));
}
}
做一个搜索页面
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(
leading: const Icon(Icons.chevron_left_outlined),
centerTitle: true,
title: const Text('搜索页面'),
actions: const [
const Icon(Icons.more_horiz),
],
),
body: const LayoutDemo()),
);
}
}
class Button extends StatelessWidget {
// 自定义按钮
final text;
final void Function()? onPressed;
const Button(this.text, {super.key, required this.onPressed});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.black26),
foregroundColor: WidgetStateProperty.all(Colors.white)),
child: Text(text));
}
}
class LayoutDemo extends StatelessWidget {
const LayoutDemo({super.key});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: ListView(
children: [
Text(
'热搜',
style: Theme.of(context).textTheme.headlineSmall,
),
const Divider(),
Wrap(
spacing: 8.0, // 主轴(水平)方向间距
runSpacing: 4.0, // 纵轴(垂直)方向间距
children: [
Button("女装", onPressed: () {}),
Button("笔记本", onPressed: () {}),
Button("玩具", onPressed: () {}),
Button("文学", onPressed: () {}),
Button("女装", onPressed: () {}),
Button("时尚", onPressed: () {}),
Button("女装", onPressed: () {}),
Button("女装", onPressed: () {}),
],
),
const SizedBox(
height: 10,
),
const Row(
children: [
Text('历史记录',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
],
),
const Divider(),
const SizedBox(
height: 10,
),
const Column(
children: [
ListTile(
title: Text('女装'),
),
Divider(),
ListTile(
title: Text('笔记本'),
),
Divider(),
],
),
const SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: OutlinedButton.icon(
onPressed: () {},
label: const Text('清空历史记录'),
icon: const Icon(Icons.delete)),
)
],
));
}
}
Scaffold
BottomNavigationBar
BottomNavigationBar 是底部导航条,可以让我们定义底部 Tab 切换,bottomNavigationBar 是 Scaffold 组件的参数。
属性名 | 说明 |
---|---|
items | List 底部导航条按钮集合 |
iconSize | icon |
currentIndex | 默认选中第几个 |
onTap | 选中变化回调函数 |
fixedColor | 选中的颜色 |
type | BottomNavigationBarType.fixed BottomNavigationBarType.shifting |
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo'),
),
body: const MyHomePage(),
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
),
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> list = [];
@override
Widget build(BuildContext context) {
return ListView(children: [
Column(
children: list.map((value) {
return ListTile(
title: Text('$value'),
);
}).toList(),
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.all(40),
child: ElevatedButton(
onPressed: () {
setState(() {
list.add('Hello World');
});
},
child: const Text('增加')),
)
]);
}
}
实现页面切换
在 lib 目录下新建 pages 和 pages/tabs,tabs 下新建 home.dart,search.dart,profile.dart。pages 下新建 tabs.dart
main.dart
import 'package:flutter/material.dart';
import './pages/tabs.dart';
void main(List<String> args) {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Tabs',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Tabs(),
);
}
}
tabs.dart
import 'package:flutter/material.dart';
import './tabs/home.dart';
import './tabs/search.dart';
import './tabs/profile.dart';
import './tabs/user.dart';
class Tabs extends StatefulWidget {
const Tabs({super.key});
@override
State<Tabs> createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
int _currentIndex = 0;
final List<Widget> _tabs = [
const HomePage(),
const SearchPage(),
const ProfilePage(),
const UserPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Tabs'),
),
body: _tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
BottomNavigationBarItem(icon: Icon(Icons.people), label: 'User'),
],
selectedItemColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed, //超过三个tab时,需要设置type为fixed
onTap: (index) {
setState(() {
_currentIndex = index;
});
}),
);
}
}
home.dart
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('HomePage'),
);
}
}
search.dart
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({super.key});
@override
State<SearchPage> createState() => _SearchPageState();
}
class _SearchPageState extends State<SearchPage> {
@override
Widget build(BuildContext context) {
return Center(
child: Text('SearchPage'),
);
}
}
profile.dart
import 'package:flutter/material.dart';
class ProfilePage extends StatefulWidget {
const ProfilePage({super.key});
@override
State<ProfilePage> createState() => _ProfilePageState();
}
class _ProfilePageState extends State<ProfilePage> {
@override
Widget build(BuildContext context) {
return Center(
child: const Text('ProfilePage'),
);
}
}
user.dart
import 'package:flutter/material.dart';
class UserPage extends StatefulWidget {
const UserPage({super.key});
@override
State<UserPage> createState() => _UserPageState();
}
class _UserPageState extends State<UserPage> {
@override
Widget build(BuildContext context) {
return const Center(
child: Text('UserPage'),
);
}
}
FloatingActionButton
FloatingActionButton 简称 FAB ,可以实现浮动按钮,也可以实现类似闲鱼 app 的底部凸起导航
常用属性
属性名称 | 属性值 |
---|---|
child | 子视图,一般为Icon,不推荐使用文字 |
tooltip | FAB被长按时显示,也是无障碍功能 |
backgroundColor | 背景颜色 |
elevation | 未点击的时候的阴影 |
hignlightElevation | 点击时阴影值,默认12.0 |
onPressed | 点击事件回调 |
shape | 可以定义FAB的形状等 |
mini | 是否是mini类型默认false |
咸鱼按钮效果
import 'package:flutter/material.dart';
import './tabs/home.dart';
import './tabs/search.dart';
import './tabs/profile.dart';
import './tabs/user.dart';
import './tabs/settings.dart';
class Tabs extends StatefulWidget {
const Tabs({super.key});
@override
State<Tabs> createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
int _currentIndex = 0;
final List<Widget> _tabs = [
const HomePage(),
const SearchPage(),
const SettingsPage(),
const ProfilePage(),
const UserPage(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Tabs'),
),
body: _tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: '搜索'),
BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '轮廓'),
BottomNavigationBarItem(icon: Icon(Icons.people), label: '用户'),
],
selectedItemColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed, //超过三个tab时,需要设置type为fixed
onTap: (index) {
setState(() {
_currentIndex = index;
});
}),
floatingActionButton: Container(
height: 60,
width: 60,
padding: const EdgeInsets.all(4),
margin: const EdgeInsets.only(top: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.white,
),
child: FloatingActionButton(
onPressed: () {
setState(() {
_currentIndex = 2;
});
},
backgroundColor: _currentIndex == 2 ? Colors.red : Colors.white,
child: Icon(Icons.add,
color: _currentIndex == 2 ? Colors.white : Colors.black38),
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
}
Drawer
在 Scaffold 组件里面传入 drawer 参数可以定义左侧边栏,传入 endDrawer 可以定义右侧边栏。侧边栏默 认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏。
DrawerHeader
常见属性
属性 | 描述 |
---|---|
decoration | 设置顶部背景颜色 |
child | 配置子元素 |
padding | 内边距 |
margin | 外边距 |
UserAccountsDrawerHeader
常见属性
属性 | 描述 |
---|---|
decoration | 设置顶部背景颜色 |
accountName | 账户名称 |
accountEmail | 账户邮箱 |
currentAccountPicture | 用户头像 |
otherAccountsPictures | 用来设置当前账户其他账户头像 |
AppBar
AppBar 自定义顶部按钮图标、颜色
属性 | 描述 |
---|---|
leading | 在标题前面显示的一个控件,在首页通常显示应用的 logo;在其他界面通常显示为返回按钮 |
title | 标题,通常显示为当前界面的标题文字,可以放组件 |
actions | 通常使用 IconButton 来表示,可以放按钮组 |
bottom | 通常放tabBar,标题下面显示一个 Tab 导航栏 |
backgroundColor | 导航背景颜色 |
iconTheme | 图标样式 |
centerTitle | 标题是否居中显示 |
TabBar
TabBar 常见属性
属性 | 描述 |
---|---|
tabs | 显示的标签内容,一般使用Tab对象,也可以是其他的Widget |
controller | TabController对象 |
isScrollable | 是否可滚动 |
indicatorColor | 指示器颜色 |
indicatorWeight | 指示器高度 |
indicatorPadding | 底部指示器的Padding |
indicator | 指示器decoration,例如边框等 |
indicatorSize | 指示器大小计算方式,TabBarIndicatorSize.label跟文字等宽,TabBarIndicatorSize.tab跟每个tab等宽 |
labelColor | 选中label颜色 |
labelStyle | 选中label的Style |
labelPadding | 每个label的padding值 |
unselectedLabelColor | 未选中label颜色 |
unselectedLabelStyle | 未选中label的Style |
Tabbar TabBarView
实现类似头条顶部导航
- 混入SingleTickerProviderStateMixin
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin{
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TabBar TabBarView'),
),
body: const Text('头条滑动导航'),
);
}
}
- 定义TabController
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TabBar TabBarView'),
),
body: const Text('头条滑动导航'),
);
}
}
- 配置TabBar和TabBarView
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('TabBar TabBarView'),
bottom: TabBar(controller: _tabController, tabs: const [
Tab(
child: Text('关注'),
),
Tab(
child: Text('热门'),
),
Tab(
child: Text('视频'),
),
]),
),
body: TabBarView(controller: _tabController, children: [
ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('我是关注列表'),
onTap: () {},
),
],
),
ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('我是热门列表 1'),
onTap: () {},
),
],
),
ListView(
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('我是视频列表 1'),
onTap: () {},
),
],
)
]),
);
}
}
preferredSize 组件
PreferredSize 可以改变 appBar 的高度
appBar: PreferredSize(
preferredSize: const Size.fromHeight(60),////设置tabbar的高度
child: AppBar(
title: const Text('TabBar TabBarView'),
bottom: TabBar(controller: _tabController, tabs: const [
Tab(
child: Text('关注'),
),
Tab(
child: Text('热门'),
),
Tab(
child: Text('视频'),
),
Tab(
child: Text('推荐'),
),
]),
),
),
KeepAliveWrapper 缓存页面
AutomaticKeepAliveClientMixin 可以快速的实现页面缓存功能,但是通过混入的方式实现不是很优 雅, 所以我们有必要对 AutomaticKeepAliveClientMixin 混入进行封装
keepAliveWrapper.dart
import 'package:flutter/material.dart';
class KeepAliveWrapper extends StatefulWidget {
const KeepAliveWrapper(
{Key? key, @required this.child, this.keepAlive = true})
: super(key: key);
final Widget? child;
final bool keepAlive;
@override
State<KeepAliveWrapper> createState() => _KeepAliveWrapperState();
}
class _KeepAliveWrapperState extends State<KeepAliveWrapper>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return widget.child!;
}
@override
bool get wantKeepAlive => widget.keepAlive;
@override
void didUpdateWidget(covariant KeepAliveWrapper oldWidget) {
if (oldWidget.keepAlive != widget.keepAlive) {
// keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中
updateKeepAlive();
}
super.didUpdateWidget(oldWidget);
}
}
监听TabController改变事件
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 4, vsync: this);
//监听_tabController的改变事件
_tabController.addListener(() {
if (_tabController.animation!.value == _tabController.index) {
print(_tabController.index); //获取点击或滑动页面的索引值
}
});
}
@override
void dispose() {
super.dispose();
_tabController.dispose();
}