Flutter页面布局主要使用Row和Column两种基本布局组件,可组合使用并结合Expanded、Flex等组件实现更灵活的页面布局。
1 页面布局
- StatelessWidget和StatefulWidget
- 一Widgets可被定义为无状态组件(StatelessWidget)和有状态组件(StatefulWidget)。
- StatelessWidget是一个不需要可变状态的小组件类型,只需要初始化时的构造函数。
- StatefulWidget是个持有可变状态的小组件类型,复写createState()返回State对象。
- Row和Column:常用的布局类小组件,Row使用水平方向排列子组件,Column则垂直排列。
- Expanded
- Expanded小组件是父组件所在主轴方向上的包裹组件,用于扩展子组件填充剩余空间。
- 当Expanded小组件嵌套在Row,或Column小组件中时,会根据自身比例分配可用空间。
- Stack和Positioned
- Stack组件用于将多个子组件叠放在一起,Positioned组件用于获取并约束子组件的位置和大小。
- 而使用Align组件和PositionedDirectional组件,则可以控制小组件在相对定位坐标系中的位置。
- SingleChildScrollView
- 产生可滚动的组件区域,只有一个子组件,使用Column或Row嵌套大量组件时可能超出屏幕尺寸。
- 此时用SingleChildScrollView组件将其包裹,使之在应用程序中可滚动并能够适应不同屏幕尺寸。
2 填充组件
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('填充组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// return Container(
// padding: const EdgeInsets.all(160),
// child: const Text('Hello, Flutter!'),
// );
// 占用内存比Container小
return const Padding(
// Padding组件属性比较单一
padding: EdgeInsets.all(160),
child: Text('Hello, Flutter!'),
);
}
}
3 线性布局
- mainAxisAlignment(主轴排序方式)、crossAxisAlignment(次轴排序方式)、children(组件子元素)。
3-1 水平布局组件
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平布局组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// 无穷大,占满屏幕
height: double.infinity,
color: Colors.black12,
// 外部若没有Container,行将自适应
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// 相对于Container设置的
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home),
IconContainer(Icons.search, color: Colors.yellow),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
),
);
// return Padding(
// padding: const EdgeInsets.all(10),
// // 水平布局
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// IconContainer(Icons.home),
// // IconContainer(Icons.search, color: Colors.yellow),
// IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
// ],
// ),
// );
}
}
class IconContainer extends StatelessWidget {
// 默认传入颜色为红色
Color color;
// 没有默认颜色使用required关键字
IconData icon;
IconContainer(this.icon, {Key? key, this.color = Colors.red})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
3-2 垂直布局组件
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('垂直布局组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
// 无穷大,占满屏幕
height: double.infinity,
color: Colors.black12,
// 垂直布局
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// 相对于Container设置的
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconContainer(Icons.home),
IconContainer(Icons.search, color: Colors.yellow),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
),
);
// return Padding(
// padding: const EdgeInsets.all(10),
// child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// IconContainer(Icons.home),
// IconContainer(Icons.search, color: Colors.yellow),
// IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
// ],
// ),
// );
}
}
class IconContainer extends StatelessWidget {
// 默认传入颜色为红色
Color color;
// 没有默认颜色使用required关键字
IconData icon;
IconContainer(this.icon, {Key? key, this.color = Colors.red})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
4 弹性布局
- Flex组件可以沿着水平或垂直方向排列子组件,如果知道主轴方向,使用Row或Column会很方便。
- Row和Column都继承自Flex,参数基本相同,所以能使用Flex的地方基本都可使用Row和Column。
- Flex组件功能强大,也可以和Expanded组件配合实现弹性布局。
4-1 水平左右比例
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平左右比例')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Flex(
// 水平位置
direction: Axis.horizontal,
// 垂直位置
// direction: Axis.vertical,
children: [
Expanded(
// 该元素设置宽度无效
child: IconContainer(Icons.home),
// 左右侧比例1:2
flex: 1,
),
Expanded(
child: IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
flex: 2,
),
],
);
}
}
class IconContainer extends StatelessWidget {
// 默认传入颜色为红色
Color color;
// 没有默认颜色使用required关键字
IconData icon;
IconContainer(this.icon, {Key? key, this.color = Colors.red})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 130,
width: 130,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
4-2 水平右侧固定
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('水平右侧固定')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
// 水平位置
// direction: Axis.horizontal,
// 垂直位置
// direction: Axis.vertical,
children: [
Expanded(
// 该元素设置宽度无效
child: IconContainer(Icons.home),
flex: 1,
),
IconContainer(Icons.ac_unit_sharp, color: Colors.orange),
],
);
}
}
class IconContainer extends StatelessWidget {
// 默认传入颜色为红色
Color color;
// 没有默认颜色使用required关键字
IconData icon;
IconContainer(this.icon, {Key? key, this.color = Colors.red})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
// 右侧固定宽度,左侧自适应
height: 80,
width: 80,
color: color,
child: Icon(icon, color: Colors.white, size: 28),
);
}
}
4-3 实现弹性布局
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('实现弹性布局')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Container(
width: double.infinity,
height: 200,
color: Colors.white,
),
Row(
children: [
Expanded(
child: SizedBox(
// 水平方向考虑高度
height: 200,
child: Image.network(
'https://gimg2.baidu.com/image_search'
'/src=http%3A%2F%2Fc-ssl.duitang.com%'
'2Fuploads%2Fblog%2F202009%2F20%2F202'
'00920220228_e6d34.jpg&refer=http%3A%'
'2F%2Fc-ssl.duitang.com&app=2002&size'
'=f9999,10000&q=a80&n=0&g=0n&fmt=auto'
'?sec=1725155093&t=b3ea8cb72ee2c8770fda07b2e4bd5872',
fit: BoxFit.cover,
),
),
flex: 2,
),
Expanded(
flex: 1,
child: SizedBox(
// 垂直方向则考虑宽度
height: 200,
child: Column(
children: [
// 图片可能不会铺满,使用SizedBox
Expanded(
child: Image.network(
'https://c-ssl.dtstatic.com/uploads'
'/blog/202211/05/20221105172125_7ca'
'2f.thumb.1000_0.jpg',
fit: BoxFit.cover,
),
flex: 1,
),
const SizedBox(height: 1),
Expanded(
child: SizedBox(
// 占满屏幕
width: double.infinity,
child: Image.network(
'https://pic.90sheji.com/original_origin_pic'
'/18/10/25/52c0e55eebb42d07e3d23ae7dde692df.'
'png%21/fwfh/5808x0/clip/0x3856a0a0/quality/'
'90/unsharp/true/compress/true/watermark/url'
'/LzkwX3dhdGVyX3Y2LnBuZw==/repeat/true',
fit: BoxFit.cover,
),
),
flex: 1,
),
],
),
),
),
],
),
],
);
}
}
5 层叠组件
- Stack即堆
- 使用Stack,或Stack结合Align,或Stack结合Positioned实现页面的定位布局。
- Stack属性包括:alignment(配置所有子元素的显示位置)、children(子组件)。
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('层叠组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// Column则排列一起
return Stack(
alignment: Alignment.center,
children: [
Container(
height: 400,
width: 400,
color: Colors.green,
),
Container(
height: 200,
width: 200,
color: Colors.yellow,
),
const Text('Hello, Flutter!'),
],
);
}
}
5-1 Positioned
- Stack组件结合Positioned组件可以控制每个子元素的显示位置。
- left(子元素距离左侧距离)、right(子元素距离右侧距离)、height(子组件高度)。
- top(子元素距离顶部的距离)、bottom(子元素距离底部的距离)、child(子组件)。
- width(子组件的宽度,注意宽度和高度必须是固定值,没法使用double.infinity)。
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Positioned')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 400,
width: 400,
color: Colors.green,
// Stack是相对于外部容器进行定位
child: Stack(
// 若无外部容器就相对于整个屏幕定位
children: [
Positioned(
// 居于容器左下角
left: 0,
bottom: 0,
child: Container(
height: 200,
width: 200,
color: Colors.yellow,
),
),
const Positioned(
// 居于容器右侧中间
right: 0,
top: 200,
child: Text('Hello, Flutter!'),
),
],
),
);
}
}
5-2 浮动导航功能
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('浮动导航功能')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 获取设备的宽度和高度
final size = MediaQuery.of(context).size;
return Stack(
children: [
ListView(
// 将隐藏的列表01显示出来
padding: const EdgeInsets.only(top: 50),
children: const [
ListTile(title: Text('列表01')),
ListTile(title: Text('列表02')),
ListTile(title: Text('列表03')),
ListTile(title: Text('列表04')),
ListTile(title: Text('列表05')),
ListTile(title: Text('列表06')),
ListTile(title: Text('列表07')),
ListTile(title: Text('列表08')),
ListTile(title: Text('列表09')),
ListTile(title: Text('列表10')),
ListTile(title: Text('列表11')),
ListTile(title: Text('列表12')),
ListTile(title: Text('列表13')),
ListTile(title: Text('列表14')),
ListTile(title: Text('列表15')),
ListTile(title: Text('列表16')),
ListTile(title: Text('列表17')),
ListTile(title: Text('列表18')),
ListTile(title: Text('列表19')),
ListTile(title: Text('列表20')),
],
),
Positioned(
left: 0,
top: 0,
// 无法使用double.infinity
width: size.width,
height: 50,
child: Row(
children: [
Expanded(
flex: 1,
child: Container(
alignment: Alignment.center,
height: 50,
color: Colors.purple,
child: const Text(
'二级导航',
style: TextStyle(color: Colors.white),
),
),
),
],
),
),
],
);
}
}
5-3 优化浮动导航
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('优化浮动导航')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 获取设备的宽度和高度
final size = MediaQuery.of(context).size;
return Stack(
children: [
ListView(
padding: const EdgeInsets.only(bottom: 50),
children: const [
ListTile(title: Text('列表01')),
ListTile(title: Text('列表02')),
ListTile(title: Text('列表03')),
ListTile(title: Text('列表04')),
ListTile(title: Text('列表05')),
ListTile(title: Text('列表06')),
ListTile(title: Text('列表07')),
ListTile(title: Text('列表08')),
ListTile(title: Text('列表09')),
ListTile(title: Text('列表10')),
ListTile(title: Text('列表11')),
ListTile(title: Text('列表12')),
ListTile(title: Text('列表13')),
ListTile(title: Text('列表14')),
ListTile(title: Text('列表15')),
ListTile(title: Text('列表16')),
ListTile(title: Text('列表17')),
ListTile(title: Text('列表18')),
ListTile(title: Text('列表19')),
ListTile(title: Text('列表20')),
],
),
Positioned(
left: 0,
// 二级导航居于底部
bottom: 0,
child: Container(
// 配置子元素的宽度和高度
width: size.width,
height: 50,
alignment: Alignment.center,
color: Colors.purple,
child: const Text(
'二级导航',
style: TextStyle(color: Colors.white),
),
),
),
],
);
}
}
5-4 Align方法介绍
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Align方法介绍')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
// 方法一
// alignment: Alignment.topCenter,
width: 200,
height: 200,
color: Colors.green,
// 方法二
// child: const Align(
// alignment: Alignment.center,
// child: Text('Hello, Flutter!'),
// ),
// 方法三
// child: const Center(
// child: Text('Hello, Flutter!'),
// ),
// (Alignment.x*childwidth/2+childwidth/2,
// Alignment.y*childHeight/2+childHeight/2)
child: const Align(
// 通过坐标转换公式计算偏移位置
alignment: Alignment(-1, 1),
child: Text('Hello, Flutter!'),
),
);
}
}
5-5 Stack结合Align
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Stack结合Align')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// return Stack(
// children: const [
// Align(alignment: Alignment.topLeft, child: Text('收藏')),
// Align(alignment: Alignment.topRight, child: Text('购买')),
// ],
// );
return Column(
children: [
SizedBox(
width: double.infinity,
height: 40,
child: Stack(
children: const [
Align(alignment: Alignment.topLeft, child: Text('收藏')),
Align(alignment: Alignment.topRight, child: Text('购买')),
],
),
),
SizedBox(
width: double.infinity,
height: 40,
// 相对于容器SizedBox来定位
child: Stack(
children: const [
Positioned(left: 10, child: Text('收藏')),
Positioned(right: 10, child: Text('购买')),
],
),
),
],
);
}
}
6 比例组件
- 作用是根据设置调整子元素child的宽高比,首先在布局限制条件允许的范围内尽可能的扩展。
- Widget的高度由宽度和比率决定,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
- 若满足所有限制条件后仍无法找到一个可行尺寸,将优先适应布局限制条件,忽略设置的比率。
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('比例组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// 页面上显示一个容器,宽度是屏幕的宽度,高度是宽度的一半
return AspectRatio(
aspectRatio: 2 / 1,
child: Container(
color: Colors.green,
),
);
}
}
7 卡片组件
- Card是卡片组件块,内容由大多数类型组件构成,具有圆角和阴影,看起来更具立体感。
- margin(外边距)、elevation(阴影值的深度)、color(背景颜色)、shadowColor(阴影颜色)。
- child(子组件)、Shape(阴影效果,默认为圆角长方形边)、clipBehavior(内容溢出的剪切方式)。
- Clip.none(不剪切)、Clip.harEdge(剪切但不抗锯齿)、Clip.antiAlias(剪切并抗锯齿)。
- Clip.antiAliasWithSaveLayer:带有抗锯齿的剪切,并在剪切之后立即保存saveLayer。
- AspectRatio(宽高比,最终可能不会根据这个值布局,具体看综合因素,只是一个参考值)。
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('卡片组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Card(
// 卡片的阴影效果
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
// 卡片阴影值的深度
elevation: 10,
// 边距
margin: const EdgeInsets.all(30),
child: Column(
children: const [
ListTile(
title: Text('Luxury', style: TextStyle(fontSize: 28)),
subtitle: Text('高级软件工程师'),
),
// 分割线
Divider(),
ListTile(title: Text('电话:18856666110')),
ListTile(title: Text('地址:福建省厦门市思明区')),
],
),
),
// 两卡片之间的距离
// const SizedBox(height: 10),
Card(
// 卡片阴影
elevation: 10,
// 背景颜色
color: Colors.greenAccent,
child: Column(
children: const [
ListTile(
title: Text('Aurora', style: TextStyle(fontSize: 28)),
subtitle: Text('高级开发工程师'),
),
// 分割线
Divider(),
ListTile(title: Text('电话:18856666120')),
ListTile(title: Text('地址:福建省厦门市湖里区')),
],
),
),
],
);
}
}
7-1 圆形图片
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('圆形图片')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
'https://gimg2.baidu.com/image_search'
'/src=http%3A%2F%2Fc-ssl.duitang.com%'
'2Fuploads%2Fitem%2F201905%2F19%2F201'
'90519103932_lCZT3.jpeg&refer=http%3A'
'%2F%2Fc-ssl.duitang.com&app=2002&siz'
'e=f9999,10000&q=a80&n=0&g=0n&fmt=aut'
'o?sec=1725157682&t=1c8606884ee741fc8'
'5920124947c8969',
fit: BoxFit.cover,
),
),
ListTile(
// ClipOval实现圆形图片
leading: ClipOval(
child: Image.network(
'https://img0.baidu.com/it/u=377968'
'1995,915534195&fm=253&fmt=auto&app'
'=138&f=JPEG?w=500&h=500',
fit: BoxFit.cover,
height: 50,
width: 50,
),
),
title: const Text('桃夭'),
subtitle: const Text('桃之夭夭,灼灼其华。'),
),
],
),
),
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
'https://img0.baidu.com/it'
'/u=2050583615,2484881629&'
'fm=253&fmt=auto&app=138&f'
'=JPEG?w=890&h=500',
fit: BoxFit.cover,
),
),
const ListTile(
// CircleAvatar实现圆形图片
leading: CircleAvatar(
// 设置头像半径
radius: 25,
backgroundImage: NetworkImage(
'https://gimg2.baidu.com/image_search'
'/src=http%3A%2F%2Fb-ssl.duitang.com%'
'2Fuploads%2Fitem%2F201803%2F23%2F201'
'80323164300_ZQLAt.jpeg&refer=http%3A'
'%2F%2Fb-ssl.duitang.com&app=2002&siz'
'e=f9999,10000&q=a80&n=0&g=0n&fmt=aut'
'o?sec=1725157893&t=24cf15995121c713c'
'6081a03a3596499',
),
),
title: Text('蒹葭'),
subtitle: Text('蒹葭苍苍,白露为霜。'),
),
],
),
),
],
);
}
}
7-2 循环遍历
import './res/mydata.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('循环遍历')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
List<Widget> _initCardData() {
var tempList = listData.map((value) {
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 20,
margin: const EdgeInsets.all(10),
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(value['imageUrl'], fit: BoxFit.cover),
),
ListTile(
// ClipOval实现圆形图片
leading: ClipOval(
child: Image.network(
value['imageUrl'],
fit: BoxFit.cover,
height: 50,
width: 50,
),
),
title: Text(value['title']),
subtitle: Text(value['author']),
),
],
),
);
});
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return ListView(
// 调用方法
children: _initCardData(),
);
}
}
8 按钮组件
- onPressed(必填参,按下按钮时触发回调,接收一个方法,传null表禁用,显示相关样式)。
- child(子组件)、style(通过ButtonStyle进行装饰,ButtonStyle里面的常用参数,如下所示)。
- ElevatedButton:凸起按钮,默认带有阴影和灰色背景,按下后阴影变大。
- TextButton:文本按钮,默认背景透明并且不带阴影,按下后会有背景色。
- OutlinedButton:默认带边框,不带阴影背景透明,按下后边框颜色变亮同时出现背景和阴影。
- IconButton:一个可点击的Icon图标,不包含文字,默认是没有背景的,点击后将会出现背景。
- 带图标按钮:ElevatedButton、TextButton、OutlinedButton都有一个icon构造函数,可带图标。
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('按钮组件')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 凸起按钮
ElevatedButton(
// 打印ElevatedButton
onPressed: () {
print('ElevatedButton');
},
child: const Text('普通按钮'),
),
// 文本按钮
TextButton(
onPressed: () {},
child: const Text('文本按钮'),
),
// 带边框的按钮
const OutlinedButton(
onPressed: null,
child: Text('边框按钮'),
),
IconButton(onPressed: () {}, icon: const Icon(Icons.thumb_up)),
],
),
const SizedBox(height: 20),
// 调整按钮位置
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 带图标的按钮
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('发送'),
),
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.info),
label: const Text('消息'),
),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('增加'),
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
// 修改按钮和图标的颜色
ElevatedButton(
// 背景颜色和文字颜色
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
onPressed: () {
print('ElevatedButton');
},
child: const Text('普通按钮'),
),
],
),
],
);
}
}
8-1 修改宽和高
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('修改宽和高')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Container(
height: 60,
width: 180,
child: ElevatedButton(
onPressed: () {},
child: const Text('确认'),
),
),
SizedBox(
height: 60,
width: 180,
child: ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.search),
label: const Text('搜索'),
),
),
],
),
],
);
}
}
8-2 自适应按钮
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('自适应按钮')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
// 占满屏幕
flex: 1,
child: Container(
margin: const EdgeInsets.all(20),
height: 40,
// 没效果了
// width: 180,
child: ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromARGB(220, 219, 35, 35),
),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: const Text('登录'),
),
),
),
],
),
],
);
}
}
8-3 圆形与圆角
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('圆形与圆角')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
SizedBox(
height: 60,
width: 60,
child: ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all(
const CircleBorder(
side: BorderSide(
width: 3,
color: Colors.yellow,
),
),
),
),
onPressed: () {},
child: const Text('圆形'),
),
),
ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
onPressed: () {},
child: const Text('圆角'),
),
],
),
],
);
}
}
8-4 边框的修改
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('边框的修改')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton(
style: ButtonStyle(
side: MaterialStateProperty.all(
const BorderSide(width: 1, color: Colors.red),
),
),
onPressed: () {},
child: const Text('带边框的按钮'),
),
],
),
],
);
}
}
9 Wrap组件
- 该组件可实现流布局,单行的Wrap和Row表现几乎一致,单列的Wrap和Column表现也几乎一致。
- Row与Column都是单行单列,Wrap则突破限制,mainAxis上空间不足时向crossAxis上扩展显示。
- 属性说明
- direction(主轴的方向,默认水平)、spacing(主轴方向上的间距)。
- alignment(主轴的对齐方式)、textDirection(文本方向)、runSpacing(run的间距)。
- verticalDirection(定义了children的摆放顺序,默认是down,详见Flex相关属性)。
- runAlignment(run的对齐方式,run可理解为新的行或列,水平方向布局可理解为新的一行)。
9-1 Wrap组件使用
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Wrap组件使用')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// return Button('第1集', onPressed: (){});
return Padding(
padding: const EdgeInsets.all(10),
child: Wrap(
// 居中显示
// alignment: WrapAlignment.center,
// x轴上的间距数(水平间距)
spacing: 5,
// y轴上的间距数(垂直间距)
runSpacing: 10,
// 垂直方向排列
// direction: Axis.vertical,
children: [
Button('第1集', onPressed: () {}),
Button('第2集', onPressed: () {}),
Button('第3集', onPressed: () {}),
Button('第4集', onPressed: () {}),
Button('第5集', onPressed: () {}),
Button('第6集', onPressed: () {}),
Button('第7集', onPressed: () {}),
Button('第8集', onPressed: () {}),
Button('第9集', onPressed: () {}),
Button('第10集', onPressed: () {}),
Button('第11集', onPressed: () {}),
Button('第12集', onPressed: () {}),
Button('第13集', onPressed: () {}),
Button('第14集', onPressed: () {}),
Button('第15集', onPressed: () {}),
Button('第16集', onPressed: () {}),
Button('第17集', onPressed: () {}),
Button('第18集', onPressed: () {}),
],
),
);
}
}
// 自定义按钮组件
class Button extends StatelessWidget {
// 按钮的文字
String text;
// 方法
void Function()? onPressed;
Button(this.text, {Key? key, required this.onPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromRGBO(244, 244, 244, 244),
),
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: onPressed,
child: Text(text),
);
}
}
9-2 搜索页面布局
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('搜索页面布局')),
body: const MyApp(),
),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.all(10),
children: [
Row(
children: [
Text('热搜', style: Theme.of(context).textTheme.titleLarge),
],
),
const Divider(),
Wrap(
spacing: 10,
runSpacing: 10,
children: [
Button('女装', onPressed: () {}),
Button('笔记本', onPressed: () {}),
Button('玩具', onPressed: () {}),
Button('文学', onPressed: () {}),
Button('时尚', onPressed: () {}),
Button('扭扭棒', onPressed: () {}),
Button('健身', onPressed: () {}),
Button('台式机', onPressed: () {}),
],
),
const SizedBox(height: 10),
Row(
children: [
Text('历史记录', style: Theme.of(context).textTheme.titleLarge),
],
),
const Divider(),
Column(
children: const [
ListTile(title: Text('女装')),
Divider(),
ListTile(title: Text('手机')),
Divider(),
ListTile(title: Text('电脑')),
Divider(),
],
),
const SizedBox(height: 40),
// 自适应
// OutlinedButton.icon(
// onPressed: (){},
// icon: const Icon(Icons.delete),
// label: const Text('清空历史记录'),
// ),
Padding(
padding: const EdgeInsets.all(40),
child: OutlinedButton.icon(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: () {},
icon: const Icon(Icons.delete),
label: const Text('清空历史记录'),
),
),
],
);
}
}
// 自定义按钮组件
class Button extends StatelessWidget {
// 按钮的文字
String text;
// 方法
void Function()? onPressed;
Button(this.text, {Key? key, required this.onPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
const Color.fromRGBO(244, 244, 244, 244),
),
foregroundColor: MaterialStateProperty.all(Colors.black45),
),
onPressed: onPressed,
child: Text(text),
);
}
}