爱网站黄,网站怎么做支付接口,免费咨询律师在线一对一问答,wordpress输出自定义文章类型内容#x1f4da; 目录
介绍布局原理和约束盒模型布局 约束容器ConstrainedBox非约束容器UnconstrainedBox 线性布局 行row列column 弹性布局流式布局 WrapFlow 层叠布局对齐和相对定位布局构建回调 LayoutBuilder布局过程中AfterLayout布局完成后执行 本文学习和引用自《Flutte… 目录
介绍布局原理和约束盒模型布局 约束容器ConstrainedBox非约束容器UnconstrainedBox 线性布局 行row列column 弹性布局流式布局 WrapFlow 层叠布局对齐和相对定位布局构建回调 LayoutBuilder布局过程中AfterLayout布局完成后执行 本文学习和引用自《Flutter实战·第二版》作者杜文 1. 介绍
布局类组件都会包含一个或多个子组件不同的布局类组件对子组件排列layout方式不同。如下
属性说明用途LeafRenderObjectWidget非容器类组件基类Widget树的叶子节点用于没有子节点的widget通常基础组件都属于这一类比如ImageSingleChildRenderObjectWidget单子组件基类包含一个子Widget如ConstrainedBox、DecoratedBox等MultiChildRenderObjectWidget多子组件基类包含多个子Widget一般都有一个children参数接受一个Widget数组。如Row、Column、Stack等
2. 布局原理和约束
Flutter 中有两种布局模型分别是基于 RenderBox 的盒模型布局、基于 Sliver ( RenderSliver ) 按需加载列表布局。布局流程如下
上层组件向下层组件传递约束constraints条件。下层组件确定自己的大小然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。上层组件确定下层组件相对于自身的偏移和确定自身的大小大多数情况下会根据子组件的大小来确定自身的大小。
3. 盒模型布局
盒模型布局是 Flutter 中最常用的布局方式它基于 RenderBox 类所有的布局组件都继承自该类。在布局过程中父级传递给子级的约束信息由 BoxConstraints 描述包含最大宽高信息子组件大小需要在约束的范围内。
3-1. 约束容器ConstrainedBox
如下例子。虽然将Container的高度设置为5像素但是最终却是50像素这正是ConstrainedBox的最小高度限制生效了
ConstrainedBox(constraints: BoxConstraints(// 宽度尽可能大minWidth: double.infinity,// 最小高度为50像素minHeight: 50.0),child: Container(height: 5.0, child: DecoratedBox(decoration: BoxDecoration(color: Colors.red),),),
)当存在有多重限制时取父子中相应数值较大的。实际上只有这样才能保证父限制与子限制不冲突。
3-2. 非约束容器UnconstrainedBox
有时候想要去掉约束则可以使用UnconstrainedBox只不过UnconstrainedBox对父组件限制的“去除”并非是真正的去除UnconstrainedBox其本身还是会收到约束只是UnconstrainedBox它内部的元素不会被约束了。因为任何时候子组件都必须遵守其父组件的约束
ConstrainedBox(// 父constraints: BoxConstraints(minWidth: 60.0, minHeight: 100.0),// 去除父级限制child: UnconstrainedBox(child: ConstrainedBox(// 子constraints: BoxConstraints(minWidth: 90.0, minHeight: 20.0),child: redBox,),)
)4. 线性布局
所谓线性布局即指沿水平或垂直方向排列子组件。Flutter 中通过Row和Column来实现线性布局类似于Android 中的LinearLayout控件。Row和Column都继承自Flex。
4-1. 行Row
Row可以沿水平方向排列其子widget也就是横向排列Row的高度等于子组件中最高的子元素高度。
属性说明默认值textDirection水平方向子组件的布局顺序是从左往右还是从右往左系统当前Locale环境的文本方向mainAxisSizeRow在主轴(水平)方向占用的空间MainAxisSize.max 尽可能多的占用水平方向的空间verticalDirectionRow纵轴垂直的对齐方向VerticalDirection.down 从上到下mainAxisAlignment子组件在Row所占用的空间内水平对齐方式-crossAxisAlignment子组件在Row所占用空间内垂直方向的对齐方式-children子组件数组-
例子
override
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),),body: Row(// 水平方向居中对齐mainAxisAlignment: MainAxisAlignment.center,// 垂直方向居中对齐crossAxisAlignment: CrossAxisAlignment.center,children: [const Text(111),const Text(222),const Text(333)],),);
}4-2. 列Column
Column可以在垂直方向排列其子组件也就是竖向排列。
属性说明默认值textDirection水平方向子组件的布局顺序是从左往右还是从右往左系统当前Locale环境的文本方向mainAxisSizeColumn在主轴(水平)方向占用的空间MainAxisSize.max 尽可能多的占用水平方向的空间verticalDirectionColumn纵轴垂直的对齐方向VerticalDirection.down 从上到下mainAxisAlignment子组件在Column所占用的空间内水平对齐方式-crossAxisAlignment子组件在Column所占用空间内垂直方向的对齐方式-children子组件数组-
例子
override
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),),body: Column(// 水平方向居中对齐mainAxisAlignment: MainAxisAlignment.center,// 垂直方向居中对齐crossAxisAlignment: CrossAxisAlignment.center,children: [const Text(111),const Text(222),const Text(333)],),);
}5. 弹性布局
弹性布局允许子组件按照一定比例来分配父容器空间。Flutter 中的弹性布局主要通过Flex和Expanded来配合实现。Flex组件可以沿着水平或垂直方向排列子组件如果你知道主轴方向使用Row或Column会方便一些因为Row和Column都继承自Flex属性基本相同。而Expanded 只能作为 Flex 的子组件。因为 Row和Column 都继承自 Flex所以 Expanded 也可以作为它们的子组件
如下代码最终效果是一个左右各占一半的双色长方形
import package:flutter/material.dart;class AboutPage extends StatefulWidget {final String userName;const AboutPage({super.key, required this.userName});overrideStateAboutPage createState() AboutPageState();
}class AboutPageState extends StateAboutPage {final title About Page;var count 0;void handleAdd() {count;debugPrint(Count$count);}overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: ConstrainedBox(constraints: const BoxConstraints(minWidth: double.infinity),child: Column(children: [Flex(direction: Axis.horizontal,children: [Expanded(flex: 2,child: Container(height: 30.0,color: Colors.blue,child: const Text(A),),),Expanded(flex: 2,child: Container(height: 30.0,color: Colors.yellow,child: const Text(B),),),],)],),),);}
}6. 流式布局
Row中如果子 widget 超出屏幕范围则会报溢出错误这是因为Row默认只有一行如果超出屏幕不会折行。我们把超出屏幕显示范围会自动折行的布局称为流式布局Flutter中通过Wrap和Flow来支持流式布局。
6-1. Wrap
Wrap 是一个流式布局的容器它能够根据子组件的大小自动换行。
属性说明默认值textDirection水平方向子组件的布局顺序是从左往右还是从右往左系统当前Locale环境的文本方向mainAxisSizeColumn在主轴(水平)方向占用的空间MainAxisSize.max 尽可能多的占用水平方向的空间verticalDirectionColumn纵轴垂直的对齐方向VerticalDirection.down 从上到下mainAxisAlignment子组件在Column所占用的空间内水平对齐方式-crossAxisAlignment子组件在Column所占用空间内垂直方向的对齐方式-children主轴方向子widget的间距-spacing子组件数组-runSpacing纵轴方向的间距-runAlignment纵轴方向的对齐方式-
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: ConstrainedBox(constraints: const BoxConstraints(minWidth: double.infinity),child: const Wrap(spacing: 8.0, // 主轴(水平)方向间距runSpacing: 4.0, // 纵轴垂直方向间距alignment: WrapAlignment.center, //沿主轴方向居中children: Widget[Chip(avatar:CircleAvatar(backgroundColor: Colors.blue, child: Text(A)),label: Text(Hamilton),),Chip(avatar:CircleAvatar(backgroundColor: Colors.blue, child: Text(M)),label: Text(Lafayette),),Chip(avatar:CircleAvatar(backgroundColor: Colors.blue, child: Text(H)),label: Text(Mulligan),),Chip(avatar:CircleAvatar(backgroundColor: Colors.blue, child: Text(J)),label: Text(Laurens),),],),),);
}6-2. Flow
一般很少会使用Flow因为其过于复杂需要自己实现子 widget 的位置转换在很多场景下首先要考虑的是Wrap是否满足需求。Flow主要用于一些需要自定义布局策略或性能要求较高(如动画中)的场景。
Flow的优点 灵活由于我们需要自己实现FlowDelegate的paintChildren()方法所以我们需要自己计算每一个组件的位置因此可以自定义布局策略。性能好Flow是一个对子组件尺寸以及位置调整非常高效的控件在对子组件进行位置调整的时候进行了优化。Flow用转换矩阵在对子组件进行位置调整的时候进行了优化在Flow定位过后如果子组件的尺寸或者位置发生了变化在FlowDelegate中的paintChildren()方法中调用context.paintChild 进行重绘而context.paintChild在重绘时使用了转换矩阵并没有实际调整组件位置。 Flow的缺点 复杂需要自己实现子 widget 的位置转换。大小固定不能自适应子组件大小必须通过指定父容器大小或自己实现TestFlowDelegate的getSize返回固定大小。
import package:flutter/material.dart;class AboutPage extends StatefulWidget {final String userName;const AboutPage({super.key, required this.userName});overrideStateAboutPage createState() AboutPageState();
}class TestFlowDelegate extends FlowDelegate {EdgeInsets margin;TestFlowDelegate({this.margin EdgeInsets.zero});double width 0;double height 0;overridevoid paintChildren(FlowPaintingContext context) {var x margin.left;var y margin.top;//计算每一个子widget的位置for (int i 0; i context.childCount; i) {var w context.getChildSize(i)!.width x margin.right;if (w context.size.width) {context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));x w margin.left;} else {x margin.left;y context.getChildSize(i)!.height margin.top margin.bottom;//绘制子widget(有优化)context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));x context.getChildSize(i)!.width margin.left margin.right;}}}overrideSize getSize(BoxConstraints constraints) {// 指定Flow的大小简单起见我们让宽度尽可能大但高度指定为200// 实际开发中我们需要根据子元素所占用的具体宽高来设置Flow大小return const Size(double.infinity, 200.0);}overridebool shouldRepaint(FlowDelegate oldDelegate) {return oldDelegate ! this;}
}class AboutPageState extends StateAboutPage {final title About Page;overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: Flow(delegate: TestFlowDelegate(margin: const EdgeInsets.all(10.0)),children: Widget[Container(width: 80.0, height:80.0, color: Colors.red,),Container(width: 80.0, height:80.0, color: Colors.green,),Container(width: 80.0, height:80.0, color: Colors.blue,),Container(width: 80.0, height:80.0, color: Colors.yellow,),Container(width: 80.0, height:80.0, color: Colors.brown,),Container(width: 80.0, height:80.0, color: Colors.purple,),],),);}
}7. 层叠布局
层叠布局和Web中的绝对定位是相似的子组件可以根据距父容器四个角的位置来确定自身的位置。层叠布局允许子组件按照代码中声明的顺序堆叠起来。Flutter中使用Stack和Positioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠而Positioned用于根据Stack的四个角来确定子组件的位置。
Stack
属性描述alignment决定如何去对齐没有定位没有使用Positioned或部分定位的子组件textDirection确定alignment对齐的参考系fit用于确定没有定位的子组件如何去适应Stack的大小clipBehavior决定对超出Stack显示空间的部分如何剪裁
Positioned
属性描述top离Stack上边的距离left离Stack左边的距离right离Stack右边的距离bottom离Stack下边的距离width宽度height高度
例子
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: ConstrainedBox(constraints: const BoxConstraints.expand(),child: Stack(fit: StackFit.expand,alignment: Alignment.center,children: [Container(color: Colors.blue,child: const Text(111),),const Positioned(left: 18.0,child: Text(222),),const Positioned(top: 18.0,child: Text(333),)],),),);
}8. 对齐和相对定位
如果我们只想简单的调整一个子元素在父元素中的位置的话使用Align组件会比Stack和Positioned更简单一些。Align只能有一个子元素不存在堆叠。
Align
属性描述alignment一个AlignmentGeometry类型的值表示子组件在父组件中的起始位置widthFactor是个缩放因子会乘以子元素的宽最终的结果就是Align 组件的宽heightFactor是个缩放因子会乘以子元素的高最终的结果就是Align 组件的高
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: Container(width: double.infinity,height: 120.0,color: Colors.blue,child: const Align(// 右上角对齐alignment: Alignment.topRight,// 如果不指定宽高 则这个就生效// widthFactor: 2,// heightFactor: 2,child: FlutterLogo(size: 60,),),),);
}Center
Center继承自Align它比Align只少了一个alignment 参数因为Center默认是居中对齐的。
Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: Container(color: Colors.blue,child: const Center(widthFactor: 2,heightFactor: 2,child: FlutterLogo(size: 60,),),),);
}9. 布局构建回调
在Flutter中我们可以使用LayoutBuilder和AfterLayout来处理布局构建后的回调。
9-1. LayoutBuilder布局过程中
通过 LayoutBuilder我们可以在布局过程中拿到父组件传递的约束信息然后我们可以根据约束信息动态的构建不同的布局。
import package:flutter/material.dart;
import package:flutter/rendering.dart;class AboutPage extends StatefulWidget {final String userName;const AboutPage({super.key, required this.userName});overrideStateAboutPage createState() AboutPageState();
}
// 处理排列函数
class ResponsiveColumn extends StatelessWidget {const ResponsiveColumn({Key? key, required this.children}) : super(key: key);final ListWidget children;overrideWidget build(BuildContext context) {// 通过 LayoutBuilder 拿到父组件传递的约束然后判断 maxWidth 是否小于200return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {if (constraints.maxWidth 200) {// 最大宽度小于200显示单列return Column(mainAxisSize: MainAxisSize.min,children: children,);} else {// 大于200显示双列var myChildren Widget[];for (var i 0; i children.length; i 2) {if (i 1 children.length) {myChildren.add(Row(mainAxisSize: MainAxisSize.min,children: [children[i], children[i 1]],));} else {myChildren.add(children[i]);}}return Column(mainAxisSize: MainAxisSize.min,children: myChildren,);}},);}
}// 打印布局时的约束信息
class LayoutLogPrintT extends StatelessWidget {const LayoutLogPrint({Key? key,this.tag,required this.child,}) : super(key: key);final Widget child;final T? tag; //指定日志tagoverrideWidget build(BuildContext context) {return LayoutBuilder(builder: (_, constraints) {// assert在编译release版本时会被去除assert(() {debugPrint(${tag ?? key ?? child}: $constraints);return true;}());return child;});}
}// 布局
class AboutPageState extends StateAboutPage {final title About Page;var count 0;void handleAdd() {count;debugPrint(Count$count);}overrideWidget build(BuildContext context) {var myChildren List.filled(6, const Text(正));return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: Column(children: [SizedBox(width: 240,child: ResponsiveColumn(children: myChildren,),),ResponsiveColumn(children: myChildren),// 打印布局时的约束信息 宽高const LayoutLogPrint(child:Text(xxx))],),);}
}9-2. AfterLayout布局完成后执行
AfterLayout不是一个widget而是一个Mixin可以用于在widget布局完成后执行代码。这对于在布局构建完成后需要进行一些计算或调整的场景非常有用。在项目中可以使用开源插件传送门
先安装包
flutter pub add after_layout示例
import package:flutter/material.dart;
import package:after_layout/after_layout.dart;class AboutPage extends StatefulWidget {final String userName;const AboutPage({super.key, required this.userName});overrideStateAboutPage createState() AboutPageState();
}class AboutPageState extends StateAboutPage with AfterLayoutMixinAboutPage {final title About Page;overridevoid afterFirstLayout(BuildContext context) {// 获取渲染的组件final RenderBox renderBox context.findRenderObject() as RenderBox;// 获取组件大小final size renderBox.size;// 获取组件偏移量final offset renderBox.localToGlobal(Offset.zero);debugPrint(size.toString());debugPrint(offset.toString());}overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(title),backgroundColor: Theme.of(context).colorScheme.inversePrimary,),body: Container(color: Colors.blue,child: const Center(widthFactor: 2,heightFactor: 2,child: FlutterLogo(size: 60,),),),);}
}本次分享就到这儿啦我是鹏多多如果您看了觉得有帮助欢迎评论关注点赞转发我们下次见~
往期文章
手把手教你搭建规范的团队vue项目包含commitlinteslintprettierhuskycommitizen等等Web Woeker和Shared Worker的使用以及案例Vue2全家桶Element搭建的PC端在线音乐网站vue3element-plus配置cdn助你上手Vue3全家桶之Vue3教程助你上手Vue3全家桶之VueX4教程助你上手Vue3全家桶之Vue-Router4教程超详细Vue的九种通信方式超详细Vuex手把手教程使用nvm管理node.js版本以及更换npm淘宝镜像源vue中利用.env文件存储全局环境变量以及配置vue启动和打包命令超详细Vue-Router手把手教程
个人主页
CSDNGitHub简书博客园掘金