01-单子布局
单子布局即只有一个子组件,常用的有Align、Center、Padding、Container等。
- Align组件
1.常用属性: alignment: Alignment.center, // 对齐方式,默认居中对齐 widthFactor, // 宽度因子,不设置的情况,会尽可能大 heightFactor, // 高度因子,不设置的情况,会尽可能大 child // 要布局的子Widget 其中,alignment设置有效的前提是父组件得知自己的宽度和高度; 如果widthFactor和heightFactor不设置,那么Align默认尽可能大的占据自己的父组件; 设置之后的意思是Align的宽度是其子组件的widthFactor倍。 2.示例: class AlignDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Align( alignment: Alignment(1, 1), widthFactor: 5, heightFactor: 5, child: Icon(Icons.pets, size: 50) ); } } - Center组件
Center组件继承自Align,只是将alignment设置为Alignment.center。 - Padding组件(没有Margin组件)
class PaddingDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: EdgeInsets.only( bottom: 10 ), // padding: EdgeInsets.all(10) 意为全部内边距都为10 child: Text("你好啊", style: TextStyle(fontSize: 30, backgroundColor: Colors.red)), ); } } - Container组件
1.常用属性: alignment,//对齐方式 padding, //容器内补白, color, // 背景色 decoration, // 背景装饰 foregroundDecoration, //前景装饰 width,//容器的宽度 height, //容器的高度 constraints, //容器大小的限制条件 margin,//容器外补白, transform, //变换 child,//子组件 其中,容器的大小可以通过width和height属性指定,也可以通过constrains指定,如果同时存在,width和height优先级高; color和decoration的color是互斥的,只能存在一个。 2.decoration属性: 其对应的类型是Decoration抽象类,使用其实现类BoxDecoration进行实例化。对应属性为: color, // 颜色,会和Container中的color属性冲突 image, // 背景图片 border, // 边框,对应类型是Border类型,里面每一个边框使用BorderSide borderRadius, // 圆角效果 boxShadow, // 阴影效果 gradient, // 渐变效果 backgroundBlendMode, // 背景混合 shape = BoxShape.rectangle, //形变 3.示例: class HYHomeContent extends StatelessWidget { @override Widget build(BuildContext context) { return Container( // color: Colors.red, width: 200, height: 200, alignment: Alignment(0, 0), padding: EdgeInsets.all(20), margin: EdgeInsets.all(10), child: Text("Hello World"), transform: Matrix4.rotationZ(50), decoration: BoxDecoration( color: Colors.red, border: Border.all( width: 5, color: Colors.purple ), borderRadius: BorderRadius.circular(100), boxShadow: [ BoxShadow(color: Colors.orange, offset: Offset(10, 10), spreadRadius: 5, blurRadius: 10), BoxShadow(color: Colors.blue, offset: Offset(-10, 10), spreadRadius: 5, blurRadius: 10), ] ), ); } } 4.实现圆角头像(Container+BoxDecoration) class HomeContent extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: Container( width: 200, height: 200, decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), image: DecorationImage( image: NetworkImage("https://tva1.sinaimg.cn/large/006y8mN6gy1g7aa03bmfpj3069069mx8.jpg"), ) ), ), ); } }
02-多子布局
多个Widget放在一起布局,水平方向、垂直方向或者层叠,常用组件为Row、Column、Stack。
- Flex组件
介绍:Row和Column都继承自Flex,区别在于Flex的属性中direction为Axis.horizontal时是Row,为Axis.vertical时为Column。- Row组件
所有子组件排成一行,水平方向也是尽可能占据比较大的空间,垂直方向包裹内容。 1.常用属性:mainAxisAlignment = MainAxisAlignment.start, // 主轴对齐方式 mainAxisSize = MainAxisSize.max, // 水平方向尽可能大(默认), textDirection=TextDirection.ltr, // 水平方向子widget的布局顺序(默认为从左向右) crossAxisAlignment = CrossAxisAlignment.center, // 交叉轴对齐方式 verticalDirection = VerticalDirection.down, // 表示纵轴(交叉轴)的对齐方向 textBaseline, // 如果上面是baseline对齐方式,那么选择什么模式(有两种可选) children = const <Widget>[] 其中,MainAxisAlignment: start: 主轴的开始位置挨个摆放元素(默认值) end: 主轴的结束位置挨个摆放元素 center: 主轴的中心点对齐 spaceBetween: 左右两边的间距为0, 其它元素之间平分间距 spaceAround: 左右两边的间距是其它元素之间的间距的一半 spaceEvenly: 所有的间距平分空间 CrossAxisAlignment: start: 交叉轴的起始位置对齐 end: 交叉轴的结束位置对齐 center: 中心点对齐(默认值) baseline: 基线对齐(必须有文本的时候才起效果) stretch: 先Row占据交叉轴尽可能大的空间, 将所有的子Widget交叉轴的高度, 拉伸到最大 2.示例: class RowDemo1 extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 300, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, textBaseline: TextBaseline.ideographic, children: <Widget>[ Container( width: 80, height: 60, color: Colors.red, child: Text( "Hellxo", style: TextStyle(fontSize: 20), ), ), Container( width: 120, height: 100, color: Colors.green, child: Text( "Woxrld", style: TextStyle(fontSize: 30), ), ), ], ), ); } } - 特殊组件Expanded
1.使用场景:希望某个组件能够占据剩余的空间,而不是设置固定的宽度,可以使用Expanded包裹,其属性flex弹性系数会决定剩下空间的占据比例。 2.示例: class ExpandedDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 300, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.max, textBaseline: TextBaseline.ideographic, children: <Widget>[ Expanded(flex:1,child: Container(color: Colors.red)), Expanded(flex: 2,child: Container(color: Colors.green)), Container(width: 90, height: 80, color: Colors.blue), Container(width: 50, height: 120, color: Colors.orange), ], ), ); } } - Column组件
原理与Row组件基本一致 class ColumnDemo extends StatelessWidget { @override Widget build(BuildContext context) { return Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, textBaseline: TextBaseline.alphabetic, verticalDirection: VerticalDirection.down, mainAxisSize: MainAxisSize.min, children: <Widget>[ Container( width: 80, height: 60, color: Colors.red, child: Text( "Hellxo", style: TextStyle(fontSize: 20), ), ), //... ], ); } }
- Row组件
- Stack组件
1.使用场景:用于层叠布局,默认大小是包裹内容的 2.常用属性: alignment = AlignmentDirectional.topStart, textDirection, fit = StackFit.loose, overflow = Overflow.clip, children = <Widget>[], 其中,fit:此参数用于决定没有定位的子widget如何去适应Stack的大小。StackFit.loose表示使用子widget的大小,StackFit.expand表示扩伸到Stack的大小; overflow:此属性决定如何显示超出Stack显示空间的子widget,为Overflow.clip时,超出部分会被剪裁(隐藏),为Overflow.visible 时则不会。 3.示例: class _RowDemo2State extends State<RowDemo2> { bool _isFavor = false; @override Widget build(BuildContext context) { return Stack( children: <Widget>[ Image.asset("assets/images/juren.jpeg"), Positioned( left: 0, right: 0, bottom: 0, child: Container( padding: EdgeInsets.symmetric(horizontal: 8), color: Color.fromARGB(150, 0, 0, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: <Widget>[ Text( "进击的巨人挺不错的", style: TextStyle(fontSize: 20, color: Colors.white), ), IconButton( icon: Icon( Icons.favorite, color: _isFavor? Colors.red : Colors.white, ), onPressed: () { setState(() { _isFavor = !_isFavor; }); }, ) ], ), ), ) ], ); } }
03-ListView组件
介绍:滚动组件,可以垂直或者水平排列所有子Widget。
- 通过默认构造方法ListView()创建---会一次性创建出所有的子Widget
class ListViewDemo1 extends StatelessWidget { @override Widget build(BuildContext context) { return ListView( scrollDirection: Axis.horizontal, reverse: true, itemExtent: 100, children: List.generate(100, (index) { return ListTile( leading: Icon(Icons.people), trailing: Icon(Icons.delete), title: Text("联系人${index + 1}"), subtitle: Text("联系人电话号码:18866665555"), ); }), ); } } 其中,ListTile组件就是经常用来实现形如有图标、文字、副标题和结束图标的布局的; scrollDirection属性是用来设置滚动的方向; itemExtent属性即为设置某一滚动方向上每个item所占据的宽度。 - 通过ListView.builder创建---需要显示时才创建
1.属性:itemBuilder:列表创建的方法,要求传入一个函数,该函数返回什么即显示什么内容; itemCount:列表项的数量,如若为空,则为无限列表。 2.示例 class ListViewDemo2 extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( itemCount: 100, itemExtent: 60, itemBuilder: (BuildContext ctx, int index) { return Text( "Hello World: $index", style: TextStyle(fontSize: 20), ); }, ); } } - 通过 ListView.separated创建---可以生成列表之间的分割器
class ListViewDemo3 extends StatelessWidget { @override Widget build(BuildContext context) { return Container( height: 300, child: ListView.separated( itemCount: 100, itemBuilder: (BuildContext ctx, int index) { return Text( "Hello World: $index", style: TextStyle(fontSize: 20), ); }, separatorBuilder: (BuildContext ctx, int index) { return Divider( color: Colors.red, height: 30, indent: 30, endIndent: 30, thickness: 10, ); }, ), ); } } 其中,比ListView.builder多了一个separatorBuilder属性。
04-GridView组件
介绍:展示多列或者是类似网格的布局
- 通过默认构造方法GridView()实现
class GridViewDemo1 extends StatelessWidget { @override Widget build(BuildContext context) { return GridView( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 400, // 交叉轴的item宽度 crossAxisSpacing: 8, //交叉轴的间距 mainAxisSpacing: 8, //主轴的间距 childAspectRatio: 1.8 //子Widget的宽高比 ), children: List.generate(100, (index) { return Container( color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)), ); }), ); } } 其中,属性gridDelegate要求传入的类型是SliverGridDelegate,因为其是一个抽象类,故使用其子类实现。其一子类是SliverGridDelegateWithMaxCrossAxisExtent,另外一个子类是SliverGridDelegateWithFixedCrossAxisCount,其中包括属性crossAxisCount为交叉轴的item个数,剩余属性和上面的一致。 - 通过GridView.builder()实现---需要显示的时候才创建
class GridViewDemo2 extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表测试"), ), body: Padding( padding: EdgeInsets.symmetric(horizontal: 0), child: GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 8, crossAxisSpacing: 8 ), itemBuilder: (BuildContext ctx, int index) { return Container( color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)), ); } ), ), ); } } - 通过GridView.count()实现---与上面的属性基本一致
- 通过GridView.extent()实现---与上面的属性基本一致
05-CustomScrollView
- 使用场景
当视图中出现包括标题视图、列表视图、网格视图等多种视图时,做到统一的滑动效果,统一管理多个滑动视图,即使用CustomScrollView组件统一管理。 - Slivers基本使用
CustomScrollView中,每一个独立的、可滚动的Widget都被称为Sliver。 其中,CustomScrollView有slivers属性,里面放Sliver,有: SliverFixedExtentList:类似于SliverList可以设置滚动的高度; SliverGrid:类似于我们之前使用过的GridView; SliverPadding:设置Sliver的内边距,因为可能要单独给Sliver设置内边距; SliverAppBar:添加一个AppBar,通常用来作为CustomScrollView的HeaderView; SliverSafeArea:设置内容显示在安全区域(比如不让齐刘海挡住我们的内容); SliverList:类似于我们之前使用过的ListView; 示例: class CustomScrollView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( // appBar: AppBar( // title: Text("Slivers Demo"), // ), body: CustomScrollView( slivers: <Widget>[ SliverAppBar( expandedHeight: 300, pinned: true, flexibleSpace: FlexibleSpaceBar( title: Text("Hello World"), background: Image.asset("assets/images/juren.jpeg", fit: BoxFit.cover,), ), ), SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 2 ), delegate: SliverChildBuilderDelegate( (BuildContext ctx, int int) { return Container(color: Color.fromARGB(255, Random().nextInt( 256), Random().nextInt(256), Random().nextInt(256))); }, childCount: 10 ), ), SliverList( delegate: SliverChildBuilderDelegate( (BuildContext ctx, int index) { return ListTile( leading: Icon(Icons.people), title: Text("联系人$index"), ); }, childCount: 20 ), ) ], ), ); } }
06-监听滚动事件
-
通过ScrollController
controller可以设置默认值offset;可以监听滚动和滚动的位置。 class MyHomePage extends StatefulWidget { @override State<StatefulWidget> createState() => MyHomePageState(); } class MyHomePageState extends State<MyHomePage> { ScrollController _controller; bool _isShowTop = false; @override void initState() { // 初始化ScrollController _controller = ScrollController(); // 监听滚动 _controller.addListener(() { var tempSsShowTop = _controller.offset >= 1000; if (tempSsShowTop != _isShowTop) { setState(() { _isShowTop = tempSsShowTop; }); } }); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("ListView展示"), ), body: ListView.builder( itemCount: 100, itemExtent: 60, controller: _controller, itemBuilder: (BuildContext context, int index) { return ListTile(title: Text("item$index")); } ), floatingActionButton: !_isShowTop ? null : FloatingActionButton( child: Icon(Icons.arrow_upward), onPressed: () { _controller.animateTo(0, duration: Duration(milliseconds: 1000), curve: Curves.ease); }, ), ); } } -
通过NotificationListener
可以监听开始滚动和结束滚动。 Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("列表测试"), ), body: NotificationListener( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { print("开始滚动"); } else if (notification is ScrollUpdateNotification) { print("正在滚动...,总滚动距离:${notification.metrics.maxScrollExtent} 当前滚动的位置: ${notification.metrics.pixels}"); } else if (notification is ScrollEndNotification) { print("结束滚动"); } return true; }, child: ListView.builder( controller: _controller, itemCount: 100, itemBuilder: (BuildContext ctx, int index) { return ListTile( leading: Icon(Icons.people), title: Text("联系人$index"), ); } ), ), floatingActionButton: _isShowFloatingBtn? FloatingActionButton( child: Icon(Icons.arrow_upward), onPressed: () { _controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn); }, ): null, ); }