ListView
ListView 列表组件
- 列表布局是我们项目开发中最常用的一种布局方式。Flutter中我们可以通过ListView来定义列表项,支持垂直和水平方向展示。通过一个属性就可以控制列表的显示方向。列表有以下分类:
- 垂直列表
- 垂直图文列表
- 水平列表
- 动态列表
列表组件常用参数:
名称 | 类型 | 含义 |
---|---|---|
scrollDirection | Axis | Axis.horizontal 水平列表 Axis.vertical 垂直列表 |
padding | EdgeInsetsGeometry | 内边距 |
resolve | bool | 组件反向排序 |
children | List | 列表元素 |
垂直列表
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
leading: Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
title: const Text('华北黄淮高温雨今起强势登场'),
subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
),
const Divider(),
ListTile(
leading: Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
title: const Text('保监局50天开32罚单 “断供”违规资金为房市降温'),
subtitle: const Text("中国天气网讯 保监局50天开32罚单 “断供”违规资金为房市降温"),
),
const Divider(),
ListTile(
title: const Text('华北黄淮高温雨今起强势登场'),
subtitle: const Text("中国天气网讯 21日开始,华北黄淮高温雨今起强势登场"),
trailing:
Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg")
),
const Divider(),
ListTile(
leading: Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
title: const Text('普京现身俄海军节阅兵:乘艇检阅军舰'),
),
const Divider(),
ListTile(
leading: Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
title: const Text('鸿星尔克捐1个亿帮助困难残疾群体 网友:企业有担当'),
),
const Divider(),
ListTile(
leading: Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
title: const Text('行业冥灯?老罗最好祈祷苹果的AR能成'),
),
], // children
); // ListView
}
}
水平列表 可以左右滑动
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 180,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
width: 180.0,
color: Colors.red,
),
Container(
width: 180.0,
color: Colors.orange,
child: Column(
children: <Widget>[
Image.network("https://www.au-sonpo.co.jp/common/img/id0026.jpeg"),
const Text('我是一个文本')
], // children
), // Column
),
Container(
width: 180.0,
color: Colors.blue,
),
Container(
width: 180.0,
color: Colors.deepOrange,
),
Container(
width: 180.0,
color: Colors.deepPurpleAccent,
),
], // children
), // ListView
);
}
}
ListView动态列表组件 以及循环动态数据
- for循环实现动态列表
import 'package:flutter/material.dart';
import './myFont.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.yellow,
), // ThemeData
home: Scaffold(
appBar: AppBar(title: const Text("Flutter ICON")),
body: const MyHomePage(),
), // Scaffold
); // MaterialApp
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
List<Widget> _initListView(){
List<Widget> list=[];
for (var i = 0; i < 10; i++) {
list.add(
const ListTile(
title: Text("我是一个列表"),
)
);
}
return list;
}
@override
Widget build(BuildContext context) {
return ListView(
children: _initListView(),
);
}
}
2、ListView.builder实现动态列表
import 'package:flutter/material.dart';
import './myFont.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.yellow,
), // ThemeData
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter ICON")
), // AppBar
body: MyHomePage(),
), // Scaffold
); // MaterialApp
}
}
class MyHomePage extends StatelessWidget {
List list = [];
MyHomePage({Key? key}) : super(key: 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]}"),
); // ListTile
} // itemBuilder
); // ListView.builder
}
}
GridView网格布局组件
GridView网格布局在实际项目中用的也是非常多的,当我们想让可以滚动的元素使用矩阵方式排列的时候。此时我们可以用网格列表组件GridView实现布局。
GridView创建网格列表主要有下面三种方式
- 1、可以通过GridView.count 实现网格布局
- 2、可以通过GridView.extent 实现网格布局
- 3、通过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 实现网格布局
GridView.count
构造函数内部使用了 SliverGridDelegateWithFixedCrossAxisCount
,我们通过它可以快速的创建横轴固定数量子元素的 GridView
。
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
childAspectRatio: 1.0,
children: const <Widget>[
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),
], // children
); // GridView
} // BuildContext
}
GridView.extent 实现网格布局
GridView.extent
构造函数内部使用了 SliverGridDelegateWithMaxCrossAxisExtent
,我们通过它可以快速的创建横轴子元素为固定最大长度的的 GridView
。
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
List<Widget> _getListData() {
List<Widget> list = [];
for (var i = 0; i < 20; i++) {
list.add(
Container(
alignment: Alignment.center,
color: Colors.blue,
child: Text(
'这是第$i条数据',
style: const TextStyle(color: Colors.white, fontSize: 20),
), // Text
// height: 400, //设置高度没有反应
) // Container
);
}
return list;
}
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisSpacing: 20.0, //水平子 Widget 之间间距
mainAxisSpacing: 20.0, //垂直子 Widget 之间间距
padding: const EdgeInsets.all(10),
crossAxisCount: 2, //一行的 Widget 数量
childAspectRatio: 0.8, //宽度和高度的比例
children: _getListData(),
); // GridView.count
} // build
}
GridView.count 实现动态列表
import 'package:flutter/material.dart';
import 'res/listData.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: const Text("FLutter App")),
body: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
List<Widget> _getListData() {
var tempList = listData.map((value){
return Container(
decoration: BoxDecoration(
border: Border.all(
color:const Color.fromRGBO(233, 233,233, 0.9),
width: 1
) // Border
), // BoxDecoration
child:Column(
children: <Widget>[
Image.network(value['imageUrl']),
const SizedBox(height: 12),
Text(
value['title'],
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20
), // TextStyle
) // Text
], // children
), // Column
); // Container
});
// ('xxx','xxx')
return tempList.toList();
}
@override
Widget build(BuildContext context) {
return GridView.count(
crossAxisSpacing:10.0 , //水平子 Widget 之间间距
mainAxisSpacing: 10.0, //垂直子 Widget 之间间距
padding: const EdgeInsets.all(10),crossAxisCount: 2, //一行的 Widget 数量
// childAspectRatio:0.7, //宽度和高度的比例
children: _getListData(),
);
}
}
GridView.builder实现动态列表
SliverGridDelegateWithFixedCrossAxisCount
import 'package:flutter/material.dart';
import 'res/listData.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
), // ThemeData
home: Scaffold(
appBar: AppBar(title: const Text("FLutter App")),
body: const HomePage(),
), // Scaffold
); // MaterialApp
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
Widget _getListData(context, index) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: const Color.fromRGBO(233, 233, 233, 0.9),
width: 1
)
), // Border
child: Column(
children: <Widget>[
Image.network(listData[index]['imageUrl']),
const SizedBox(height: 12),
Text(
listData[index]['title'],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
)
],
),
// height: 400, //设置高度没有反应
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
//注意
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 10.0, //水平子 Widget 之间间距
mainAxisSpacing: 10.0, //垂直子 Widget 之间间距
crossAxisCount: 2, //一行的 Widget 数量
),
itemCount: listData.length,
itemBuilder: _getListData,
);
}
}
SliverGridDelegateWithMaxCrossAxisExtent
import 'package:flutter/material.dart';
import 'res/listData.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text("FLutter App")),
body: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
Widget _getListData(context, index) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: const Color.fromRGBO(233, 233, 233, 0.9), width: 1)
),
child: Column(
children: <Widget>[
Image.network(listData[index]['imageUrl']),
const SizedBox(height: 12),
Text(
listData[index]['title'],
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 20),
)
],
), // height: 400, //设置高度没有反应
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
//注意
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent (
crossAxisSpacing:10,
mainAxisSpacing:10,
maxCrossAxisExtent: 300,
),
itemCount: listData.length,
itemBuilder: _getListData,
);
}
}
Paddiing组件
在html中常见的布局标签都有 padding
属性,但是 Flutter
中很多 Widget
是没有 padding
属性。这个时候我们可以用 Padding
组件处理容器与子元素之间的间距。
名称 | 类型 | 含义 |
---|---|---|
padding | EdgeInsetss | padding 值, EdgeInsetss 设置填充的值 |
child | [] | 子组件 |
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView.count(
// padding: const EdgeInsets.all(10),
crossAxisCount: 2,
childAspectRatio: 1,
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Image.network(
'https://www.au-sonpo.co.jp/common/img/id0026.jpeg',
fit: BoxFit.cover
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Image.network(
'https://www.au-sonpo.co.jp/common/img/id0026.jpeg',
fit: BoxFit.cover
),
),
],
); // GridView
}
}
线性布局(Row和Column)
Row 水平布局组件
名称 | 类型 | 含义 |
---|---|---|
mainAxisAlignment | MainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 次轴的排序方式 |
children | [] | 组件子元素 |
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
color: Colors.black26,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.search, color: Colors.blue),
IconContainer(Icons.send, color: Colors.orange),
],
), // Row
); // Container
} // build
}
class IconContainer extends StatelessWidget {
Color color;
double size;
IconData icon;
IconContainer(
this.icon,
{
Key? key, this.color = Colors.red, this.size = 32.0
}
) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
width: 100.0,
color: color,
child: Center(
child: Icon(icon, size: size, color: Colors.white)
),
);
}
}
Column垂直布局组件
名称 | 类型 | 含义 |
---|---|---|
mainAxisAlignment | MainAxisAlignment | 主轴的排序方式 |
crossAxisAlignment | CrossAxisAlignment | 次轴的排序方式 |
children | [] | 组件子元素 |
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
), // ThemeData
home: Scaffold(
appBar: AppBar(title: const Text("Flutter App")),
body: const HomePage(),
), // Scaffold
); // MaterialApp
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
color: Colors.black26,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.search, color: Colors.blue),
IconContainer(Icons.send, color: Colors.orange),
],
), // Column
); // Container
}
}
class IconContainer extends StatelessWidget {
Color color;
double size;
IconData icon;
IconContainer(this.icon,
{Key? key, this.color = Colors.red, this.size = 32.0}
) // IconContainer
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
width: 100.0,
color: color,
child: Center(
child: Icon(icon, size: size, color: Colors.white)
), // Center
); // Container
}
}
double.infinity 和 double.maxFinite
double.infinity 和double.maxFinite可以让当前元素的width或者height达到父元素的尺寸
static const double nan = 0.0 / 0.0;
static const double infinity = 1.0 / 0.0;
static const double negativeInfinity = -infinity;
static const double minPositive = 5e-324;
static const double maxFinite = 1.7976931348623157e+308;
如下可以让Container铺满整个屏幕
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
color: Colors.black26,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.search, color: Colors.blue),
IconContainer(Icons.send, color: Colors.orange),
],
), // Column
); // Container
}
如下可以让Container的宽度和高度等于父元素的宽度高度
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 400,
width: 600,
color: Colors.red,
child: Container(
height: double.maxFinite,
width: double.infinity,
color: Colors.black26,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconContainer(Icons.home, color: Colors.red),
IconContainer(Icons.search, color: Colors.blue),
IconContainer(Icons.send, color: Colors.orange),
],
), // Column
), // Container
); // Container
}
}
弹性布局(Flex Expanded)
Flex
组件可以沿着水平或垂直方向排列子组件,如果你知道主轴方向,使用 Row
或 Column
会方便一些,因为 Row
和 Column
都继承自 Flex
,参数基本相同,所以能使用 Flex
的地方基本上都可以使用 Row
或 Column
。 Flex
本身功能是很强大的,它也可以和 Expanded
组件配合实现弹性布局 。
水平弹性布局
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(title: const Text("Flutter App")),body: const HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Flex(
direction: Axis.horizontal,
children: [
Expanded(
flex: 2, child: IconContainer(Icons.home, color: Colors.red)
), // Expanded
Expanded(
flex: 1,
child: IconContainer(Icons.search, color: Colors.orange),
) // Expanded
],
); // Flex
}
}
class IconContainer extends StatelessWidget {
Color color;
double size;
IconData icon;
IconContainer(this.icon,
{
Key? key, this.color = Colors.red, this.size = 32.0
}
) // IconContainer
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
width: 100.0,
color: color,
child: Center(
child: Icon(icon, size: size, color: Colors.white) // Center
), // Container
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
), // ThemeData
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter App")
), // AppBar
body: const HomePage(),
), // Scaffold
); // MaterialApp
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(flex: 2, child:
IconContainer(Icons.home, color: Colors.red) // IconContainer
), // Expanded
Expanded(
flex: 1,
child: IconContainer(Icons.search, color: Colors.orange), // IconContainer
)
],
); // Row
}
}
class IconContainer extends StatelessWidget {
Color color;
double size;
IconData icon;
IconContainer(this.icon,
{
Key? key, this.color = Colors.red, this.size = 32.0
})
: super(key: key); // IconContainer
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
width: 100.0,
color: color,
child: Center(child: Icon(icon, size: size, color: Colors.white)),
); // Container
}
}
垂直弹性布局
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter App")
), // AppBar
body: const HomePage(),
), // Scaffold
); // MaterialApp
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
flex: 2, child: IconContainer(Icons.home, color: Colors.red) // IconContainer
),
Expanded(
flex: 1,
child: IconContainer(Icons.search, color: Colors.orange), // IconContainer
)
],
); // Column
}
}
class IconContainer extends StatelessWidget {
Color color;
double size;
IconData icon;
IconContainer(this.icon,
{Key? key, this.color = Colors.red, this.size = 32.0})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 100.0,
width: 100.0,
color: color,
child: Center(
child: Icon(icon, size: size, color: Colors.white)
),
);
}
}
使用Row 或Column 结合Expanded实现特殊布局
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
), // ThemeData
home: Scaffold(
appBar: AppBar(
title: const Text("Flutter App")
), // AppBar
body: const HomePage(),
), // Scaffold
); // MaterialApp
}
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView(
children: [
Container(width: double.infinity,
height: 200,
color: Colors.black,
), // Container
const SizedBox(height: 10),
Row(
children: [
Expanded(
flex: 2,
child: SizedBox(
height: 180,
child: Image.network(
"https://www.au-sonpo.co.jp/common/img/id0026.jpeg",
fit: BoxFit.cover
), // Image
), // SizedBox
), // Expanded
const SizedBox(width: 10),
Expanded(
flex: 1,
child: SizedBox(
height: 180,
child: Column(
children: [
Expanded(
flex: 1,
child: SizedBox(
width:double.infinity ,
child: Image.network(
"https://www.au-sonpo.co.jp/common/img/id0026.jpeg",
fit: BoxFit.cover
), // Image
), // SizedBox
), // Expanded
const SizedBox(height: 10),
Expanded(
flex: 2,
child: SizedBox(
width:double.infinity ,
child: Image.network(
"https://www.au-sonpo.co.jp/common/img/id0026.jpeg",
fit: BoxFit.cover
), // Image
), // SizedBox
) // Expanded
],
), // Column
) // SizedBox
) // Expanded
],
) // Row
],
); // ListView
}
}
层叠布局(Stack、Align、Positioned)
Stack组件
Stack
表示堆的意思,我们可以用 Stack
或者 Stack
结合 Align
或者 Stack
结合 Positiond
来实现页面的定位布局。
Column垂直布局组件
名称 | 类型 | 含义 |
---|---|---|
alignment | Alignment | 配置所有子元素的显示位置 |
children | [] | 组件子元素 |
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Stack(
alignment: Alignment.topLeft,
children: <Widget>[
Container(
height: 400,
width: 300,
color: Colors.red,
),
const Text('我是一个文本',
style: TextStyle(fontSize: 40, color: Colors.white))
],
),
);
}
}
Stack Align
Align
组件可以调整子组件的位置 , Stack
组件中结合 Align
组件也可以控制每个子元素的显示位置。
名称 | 类型 | 含义 |
---|---|---|
alignment | Alignment | 配置所有子元素的显示位置 |
children | [] | 组件子元素 |
Align
结合 Container
的使用
- 我们先来看一个简单的例子:
FlutterLogo
是Flutter SDK
提供的一个组件,内容就是Flutter
的log
。
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 120.0,
width: 120.0,
color: Colors.blue.shade50,
child: const Align(
alignment: Alignment.topRight,
child: FlutterLogo(
size: 60,
), // FlutterLogo
), // Align
); // Container
}
}
Align 结合 Alignment 参数
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 120.0,
width: 120.0,
color: Colors.blue.shade50,
child: const Align(
alignment: Alignment(2, 0.0),
child: FlutterLogo(
size: 60,
), // FlutterLogo
) // Align
); // Container
}
}
Alignment Widget
会以矩形的中心点作为坐标原点,即 Alignment(0.0, 0.0) 。 x 、 y
的值从 -1
到 1
分别代表矩形左边到右边的距离和顶部到底边的距离,因此 2
个 水平
(或 垂直
)单位则等于矩形的 宽
(或 高
),如 Alignment(-1.0, -1.0)
代表矩形的左侧顶点,而 Alignment(1.0, 1.0)
代表右侧底部终点,而 Alignment(1.0, -1.0)
则正是右侧顶点,即 Alignment.topRight
。为了使用方便,矩形的原点、四个顶点,以及四条边的终点在 Alignment
类中都已经定义为了静态常量。
Alignment
可以通过其坐标转换公式将其坐标转为子元素的具体偏移坐标:(Alignment.x*childWidth/2+childWidth/2, Alignment.y*childHeight/2+childHeight/2)
其中 childWidth
为子元素的宽度, childHeight
为子元素高度。
现在我们再看看上面的示例,我们将 Alignment(2, 0.0)
带入上面公式, (2*120/2+120/2, 0*120/2+120/2)
,可得 FlutterLogo
的实际偏移坐标正是 (180,60)
。下面再看一个例子:
Center
继承自 Align
,它比 Align
只少了一个 alignment
参数;由于 Align
的构造函数中 alignment
值为 Alignment.center
,所以,我们可以认为 Center
组件其实是对齐方式确定。
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: const <Widget>[
Align(
alignment: Alignment(1,-0.2),
child: Icon(
Icons.home,size: 40,color: Colors.white
), // Icon
), // Align
Align(
alignment: Alignment.center,
child:
Icon(Icons.search,size: 30,color: Colors.white
), // Icon
), // Align
Align(
alignment: Alignment.bottomRight,
child: Icon(
Icons.settings_applications,size: 30,color: Colors.white
), // Icon
) // Align
],
), // Stack
), // Container
); // Center
}
}
Stack Positioned
Stack
组件中结合 Positioned
组件也可以控制每个子元素的显示位置
名称 | 类型 | 含义 |
---|---|---|
top | Positioned | 子元素距离顶部的距离 |
bottom | Positioned | 子元素距离底部的距离 |
left | Positioned | 子元素距离左侧距离 |
right | Positioned | 子元素距离右侧距离 |
child | Positioned | 子组件 |
width | Positioned | 组件的高度 (注意:宽度和高度必须是固定值,没法使用double.infinity) |
height | Positioned | 子组件的高度 |
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 400,
width: 300,
color: Colors.red,
child: Stack(
// alignment: Alignment.center,
children: const <Widget>[
Positioned(
left: 10,
child: Icon(Icons.home,size: 40,color: Colors.white),
),
Positioned(
bottom: 0,
left: 100,
child: Icon(Icons.search,size: 30,color: Colors.white),
),
Positioned(
right: 0,
child: Icon(Icons.settings_applications,size: 30,color: Colors.white),
)
],
),
),
);
}
}
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;
}
Flutter Stack Positioned 固定导航案例
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Stack(
children: [
ListView(
padding: const EdgeInsets.only(top: 45),
children: const [
ListTile(
title: Text("这是一个标题 "),
),
ListTile(
title: Text("这是一个标题"),
),
ListTile(
title: Text("这是一个标题"),
),
...
],
),
Positioned(
top: 0,
left: 0,
height: 40,
width: size.width,
child: Container(
alignment: Alignment.center,
color: Colors.black,
child: const Text("你好FLutter",style: TextStyle(color:
Colors.white),),
) // Container
) // Positioned
],
); // Stack
}
}
AspectRatio
AspectRatio的作用是根据设置调整子元素child的宽高比。
AspectRatio首先会在布局限制条件允许的范围内尽可能的扩展,widget的高度是由宽度和比率决定的,类似于BoxFit中的contain,按照固定比率去尽量占满区域。
如果在满足所有限制条件过后无法找到一个可行的尺寸,AspectRatio最终将会去优先适应布局限制条件,而忽略所设置的比率。
名称 | 类型 | 含义 |
---|---|---|
aspectRatio | AspectRatio | 宽高比,最终可能不会根据这个值去布局,具体则要看综合因素,外层是否允许按照这种比率进行布局,这只是一个参考值 |
child | [] | 子组件 |
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
width: 200,
color: Colors.yellow,
child: AspectRatio(
aspectRatio: 2.0/1.0,
child: Container(
color: Colors.red,
), // Container
), // AspectRatio
); // Container
}
}
class LayoutDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return AspectRatio(
aspectRatio: 3.0/1.0,
child: Container(
color: Colors.red,
), // Container
); // AspectRatio
}
}
瀑布流布局
参考:https://pub.dev/packages/flutter_staggered_grid_view
MasonryGridView.count(
// 展示几列
crossAxisCount: 2,
// 元素总个数
itemCount: controller.bestPlist.length,
// 单个子元素
itemBuilder: ((BuildContext context, int index) {
return Container(
...
);
}
),
// 纵向元素间距
mainAxisSpacing: ScreenAdapter.width(20),
// 横向元素间距
crossAxisSpacing: ScreenAdapter.height(20),
//本身不滚动,让外面的 listview 来滚动
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true, //收缩,让元素宽度自适应
)