从Android切换到Flutter开发也有一段时间了,记录下使用过程的遇到的布局问题,时间长了是真的会忘。
flutter widget分类
flutter 的widget很多,但是掌握常用的这些widget,就能完成80%的UI布局。
- 基础widget: - Text、- Button、- Image、- Icon、- TextField
- 布局类widget: - Row、- Column、- Expanded、- Flex、- Stack、- Positioned、- Align、- Center、- Listview
- 容器类widget: - Container、- Scaffold、- Padding、- Center
基础的widget,对应Android的View类相关的控件,布局类widget好比Android里的ViewGroup类相关的控件,Row/Column可以实现横向和纵向的线性布局,再复杂的布局都可以拆解为Row/Column实现。
Widget常用技巧
Container
Container是一个最常用的Widget,可以通过它设置宽高、背景颜色、圆角、阴影、边框、外边距、内边距,child的对齐方式等。它本身是一个组合widget,外边距、内边距、居中等都是通过嵌套对应的widget实现。
Container的组成
- 最里层是child元素
- 然后会被Align包裹
- 接着被Padding包裹,设置内边距
- 然后再被ColoredBox包裹
- 接着又是背景decoration包裹
- 再就是被前景foregroundDecoration包裹
- ConstrainedBox包裹
- 再被Padding包裹,设置margin外边距
- 最后被Transform包裹
Container 尺寸确定
- 没有子节点时,Container会尝试去变得足够大,在父节点 的约束范围内 
- 带子节点的Container,会根据子节点尺寸调整自身尺寸,但如果Container设置了width、height以及constraints,则会按照这些参数来进行尺寸的调节。 
Container布局行为
Container继承了StatelessWidget,由于组合了一系列的Widget,这些Widget都有自己的布局行为,因此导致Container的布局行为在不同widget组合下呈现比较复杂的布局行为。
- 如果没有子节点、没有设置width、height以及constraints,并且父节点没有设置unbounded的限制,Container会将自身调整到足够小。
- 如果没有子节点、对齐方式(alignment),但是提供了width、height或者constraints,那么Container会根据自身以及父节点的限制,将自身调节到足够小。
- 如果没有子节点、width、height、constraints以及alignment,但是父节点提供了bounded限制,那么Container会按照父节点的限制,将自身调整到足够大。
- 如果有alignment,父节点提供了unbounded限制,那么Container将会调节自身尺寸来包住child;
- 如果有alignment,并且父节点提供了bounded限制,那么Container会将自身调整的足够大(在父节点的范围内),然后将child根据alignment调整位置;
- 含有child,但是没有width、height、constraints以及alignment,Container会将父节点的constraints传递给child,并且根据child调整自身。
另外,margin以及padding属性也会影响到布局。
设置宽高不起作用
给Container设置宽高不起作用,通常是Container的父widget给的constraint约束是紧约束,可以通过打断这种约束传递,达到设置宽高的目的。
- 通过UnconstrainedBox解除约束,让自身的约束变为无约束。
- 通过Center、Align、Row、Column等组件放松约束,让自身的约束变为松约束。
bottomSheet 的高度
高度为自适应内容大小
通常情况下bottomSheet的高度为半屏,想要其高度为包裹内容的大小的话,可以指定Column child的
| 1 | mainAxisSize: MainAxisSize.min | 
高度为指定大小
可以给root child套个Container,指定Container的 minHeight
| 1 | constraints: minHeight != null ? BoxConstraints.tightForFinite(height: minHeight!) : null, | 
bottomSheet适配双端
假如bottomSheet以及从贴着屏幕底部的dialog,其内部在底部有操作按钮的情况下,需要特殊处理一下,否则ios端可能存在小黑条遮在按钮上的情况。
| 1 | var result = await Get.bottomSheet( | 
Widget设置不重绘
PageView切换、或者长表单中上下滑动时,child widget会被回收重新绘制,期望不重绘的话,可以给child widget的state mix AutomaticKeepAliveClientMixin 同时重写 wantKeepAlive 方法,这样widget就不会重绘了。
| 1 | class _TioFormFilePickerState extends State<TioFormFilePicker> with AutomaticKeepAliveClientMixin { | 
GestureDetector点击空白处不响应点击事件
GestureDetector默认行为是不响应空白位置的点击事件,会忽略不可见的元素,如果需要触发的话,可以修改behavior
| 1 | GestureDetector( | 
Text 行间距
Text的TextStyle 没有提供对应的设置文本行间距的方法,但是可以通过height属性设置行高,height默认为1,表示行高等于文字高度。假设将height设置为1.5,则表示行高为1.5倍的文字高度,因此行间距也会变高。
TextFiled焦点处理
表单中有很多个TextFiled时,需要在这些TextFiled中切换焦点时,为每个TextFiled设置FocusNode,设置 FocusNode获取焦点,这样移动焦点的方式就比较繁琐,需要为每个TextFiled都要写一些模板代码。简洁一点的方式是可以为TextFiled设置textInputAction:TextInputAction.next,这样输入焦点就会顺着表单中的TextFiled自动移动。
ListView 滑动隐藏键盘
通过keyboardDismissBehavior属性:
| 1 | keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag | 
StatefulBuilder
StatelessWidget 组件内部 局部刷新,比如,RadioGroup ,Switch的切换,其内部的setState触发的刷新范围仅为widget内
| 1 | StatefulBuilder( | 
Vertical(Horizontal) viewport was given unbounded height.错误
ListView没有被赋予一个确定的纵向约束,该错误通常是因为在Column(Row、ListView)中嵌套ListView时中出现,一般情况下Column给ListView传递的约束是loose constraint。有三种解决办法。
- 在ListView外层嵌套一个SizedBox并设置tight constraint。
- 在ListView外层嵌套一个Expand,撑开Column剩余可用空间。
- 指定ShrinkWrap属性为true,让ListView能根据自身高度进行自适应,listview会收缩,缩小到其child的高度。
混合开发
FlutterEngineGroup 传参
flutter 混合工程中 原生侧的Flutter engine使用FlutterEngineGroup管理,跳转到Flutter的某个页面并且携带参数。tag1处对应flutter侧的tag2
原生侧
| 1 | dartEntrypoint = DartExecutor.DartEntrypoint( | 
flutter侧
| 1 | ('vm:entry-point')//必需的,每个main方法都需要 | 
多入口flutter页面的返回按钮处理
一个flutter表单详情页面,跳转的它的入口有两类,一类是原生侧直接跳转,此时该flutter页面会被当做根路由;第二类是是正常的flutter路由跳转,对于这两种情况返回按钮的处理逻辑是不同的。第一种的情况下,点击返回按钮要求关闭flutter该页面;第二种情况下,点击返回按钮要求返回到上一个Flutter页面。可以判断当前路由能否被关闭,能的话关闭当前页面返回上一个,不能的话就是根路由然后使用MethodChannel通知原生侧关闭FlutterActivity容器。
| 1 | ModalRoute.of(context)?.canPop ?? false | 
dart常用语法
Factory单例
| 1 | class SingletonTest{ | 
使用SingletonTest()  ,getx中的依赖依赖注入相关的GetInstance就是这种写法。
常用回调
- 无参数写法: VoidCallback
- 有一个参数,可以使用泛型:final ValueChanged<T> onSelectCallback
- 参数大于多个:typedef OnCardItemTap = void Function(int index, List<XxxModle> modle);