-
Expanded只能用在Flex组件中,比如Row、Column。在Row中,子组件会横向拓展;在Column中,子组件会纵向拓展。
-
如果需要使Flex组件中的内容统一大小,可使用Intrinsic组件。在Row中使用
IntrinsicHeight
保持高度相同;在Column中使用IntrinsicWidth
保持宽度相同。注意,使用这个组件会有额外的性能开销。 -
需要缩放组件时可以使用
Transform.scale
,但并不会真的影响布局,实际尺寸没有变;如果想要改变组件尺寸并达到缩放效果,可以使用FittedBox
并结合SizedBox
。 -
Text组件如果文本过长可以设置
overFlow
属性进行截断,但同时需要外层包裹Flexible
组件;或者也可以使用Expanded
组件,这也意味着Expanded外层需要是Flex才行。 -
文本带背景色的情况下,同时还要支持长文本自动折行,如果使用Expended组件,背景色区域会无限拉伸,不能适应文本区域。可以使用
Flexible
伸缩组件来包装文本,放在Row组件中,同时设置其属性fit
值为FlexFit.loose
。这样不论文本是否折行,背景区域始终与文本区域保持一致。 -
在Stack中使用Positioned组件可以指定位置。
-
实现文本两端对齐:如果只包含半角字符,比如英文和数字,可以使用TextAlign.justify。如果包含全角字符,比如中文,则需要根据容器大小和字号,计算每个字符间距,设置Text组件的letterSpacing参数。
-
使用permission_handler如果遇到获取的权限状态不对/没有显示授权弹框的问题,需要在podfile中配置相关的宏,pub.dev上也没明说,很坑。
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', 'PERMISSION_CAMERA=1', 'PERMISSION_PHOTOS=1', 'PERMISSION_LOCATION=1', 'PERMISSION_NOTIFICATIONS=1', 'PERMISSION_APP_TRACKING_TRANSPARENCY=1' ]
-
使用
Get.arguments
获取参数时,建议放在onInit()
或onReady()
中执行。如果过早执行(比如在logic的构造函数中)可能会获取不到。 -
滑动组件不设置scrollDirection则无法滑动,且内容会被拉伸
-
要模态显示一个页面,如果使用bottomSheet方式,页面顶部没有安全区,导航栏会和状态栏重叠;可以使用push / Get.to的方式,只是需要把过渡动画自定义成从下到上滑动就可以了
-
页面导航的一些技巧:
- Get.to / Get.toNamed,推进一个页面,类似于push
- Get.back,返回到上一个页面,类似于pop
- Get.close,返回指定数量的层级
- Get.until,返回到指定页面,类似于popTo,方法传参判断是否是首页,可以实现返回到根视图的效果,类似于popToRoot
- Get.offAllNamed,设置新的根视图,并清空之前推入的页面
-
在 iOS17 中输入框结尾字符会有选中效果,如果要去掉这个选中框,可以单独对TextField组件设置主题配色:
Theme( data: ThemeData( textSelectionTheme: const TextSelectionThemeData( selectionColor: Colorful.clear, ), ), child: textField, )
-
下载插件时,国内访问很慢,可以使用镜像
https://pub.flutter-io.cn
,需要配置~/.zshrc
文件,添加:export PUB_HOSTED_URL=https://pub.flutter-io.cn export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
另外,Cocoapods也可以替换Github源,在
~/.gitconfig
文件,添加:[url "国内的源"] insteadOf = https://github.com
-
编译安卓项目时,如果没有翻墙,gradle执行会很慢/报错。可以设置国内镜像地址就会快很多:
- 修改文件
你的flutter开发包/packages/flutter_tools/gradle/flutter.gradle
(在3.16.0上是这个文件你的flutter开发包/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
)
buildscript { repositories { // google() // mavenCentral() // 添加这些 maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/jcenter' } maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } } dependencies { ... } } // ... // 修改默认的主机地址为 private static final String DEFAULT_MAVEN_HOST = "https://storage.flutter-io.cn";
- 修改文件
你的flutter开发包/packages/flutter_tools/gradle/resolve_dependencies.gradle
// 修改默认地址为 String storageUrl = System.getenv('FLUTTER_STORAGE_BASE_URL') ?: "https://storage.flutter-io.cn" repositories { // google() // mavenCentral() maven { url "$storageUrl/download.flutter.io" } }
- 修改你项目中的
build.gradle
文件
buildscript { ... repositories { // google() // mavenCentral() maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/jcenter' } maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } } ... } allprojects { repositories { // google() // mavenCentral() maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/google' } maven { url 'https://maven.aliyun.com/repository/jcenter' } maven { url 'https://maven.aliyun.com/repository/central' } maven { url 'https://maven.aliyun.com/repository/gradle-plugin' } } }
- 修改文件
-
使用GridView组件需要指定大小,如果不指定,需要设置
shrinkWrap
参数为true
。注意,这个参数会有比较大的性能开销。 -
目前flutter中显示emoji表情符号会导致整个页面的文字被加粗,暂时没有解决办法。(在最新版3.16.0上这个问题已经消失) -
目前flutter中显示超长图片时会被压扁,图片失真。你可以先获取图片的尺寸,再强制固定图片组件的大小,同时将图片的
fit
属性设置为BoxFit.fill
。 -
升级3.16.0后:
- 设置状态栏颜色失败:如果你的iOS工程配置了
UIViewControllerBasedStatusBarAppearance
,那么需要去掉。 iOS12设备无法真机调试(3.16.5已修复这个问题)- 在playcover上可能会无法运行
- 个别插件可能存在不兼容的情况,编译会报错
PopScope在iOS上无法拦截用户返回手势
- 设置状态栏颜色失败:如果你的iOS工程配置了
-
用flutter开发iOS扩展(3.16.0+支持)可以参考官方的文档:链接。但需要注意的是,不同种类的扩展,有不同的内存限制要求,超出限制会导致扩展程序崩溃,如果你需要在扩展中嵌入flutter页面,那么引擎将会产生很大的开销。建议页面部分由原生实现,flutter只提供基础逻辑等服务。
如果要上架应用商店,可能会在上传时遇到错误,主要是因为苹果不允许在扩展中使用动态库,需要把扩展所用的
Flutter.framework
和App.framework
放在主应用包的Frameworks
下。同时需在工程中对应的Target设置EMBEDDED_CONTENT_CONTAINS_SWIFT
和ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES
为NO,LD_RUNPATH_SEARCH_PATHS
动态库搜索路径指向主应用包中@executable_path/../../Frameworks
,确保.appex的编译产物中没有Frameworks文件夹。另外,如果你的主应用也使用了flutter,那么就会和扩展中使用的flutter动态库产生命名冲突。可以通过修改扩展使用的framework名称的方式来解决问题。具体做法是将framework中
Info.plist
文件中的BundleId进行修改,使用codesign --remove-signature
命令移除原有的签名,再重新签名。这样处理后上传就没问题了。 -
使用
runtimeType()
方法时要小心,它可能不会返回预期的结果。特别是打包时开启了混淆参数后,命名会被混淆,如果你通过运行时获取类型名称,会和你代码中的类型命名不同。 -
有时候需要获取组件的实际尺寸,在渲染到屏幕上后更新。由于渲染2次,样式不同会出现闪动的问题。
小技巧是,可以在第一次渲染时使用Opacity组件将其设置为透明,此时依然能够获取到尺寸,第二次更新时再完全显示,这样就不会有闪动问题了。更好的解决方式是继承SingleChildLayoutDelegate
类自己处理布局。 -
使用parse方法需要注意,如果不是预期的格式将会直接导致崩溃,当然你也可以使用try-catch捕获异常,但这样始终不太优雅。这个方法应该废弃,建议改用更安全的
tryParse
方法。 -
有时候需要根据条件在界面上排列一些组件,可以直接在数组声明中使用if-else语句,比如:
Column(
children: [
if (show) ...{
Text("根据条件显示"),
} else ...{
Text("其他"),
},
// 也可以将list中的元素展开并添加到children中
...list,
// 或者遍历数组进行添加
for (final e in list) ...{
Text(e),
},
],
);
当然,这个语法糖也同样适用于一般的数组和字典:
final list = [
123,
if (add) ...{
456,
},
];
final map = {
"aaa": 123,
if (add) ...{
"bbb": 456,
},
};
-
同一字号显示中英文时,大小不一致,这是由于中英文字体的上下两端留白不同(leading)。可以设置
StructStyle
的forceStrutHeight
属性为true就可以了。 -
苹果系统显示的中文默认是用苹方字体,但是设置字重只有w400和w600有效。这个是flutter的问题,解决方案是设置
TextStyle
的fontFamilyFallback
或fontFamily
属性设置["PingFang SC"]
。 -
使用StatelessWidget和GetBuilder实现一个能够多实例并存、可主动刷新的组件:使用GetBuilder管理刷新,通过外部传入GetxController控制逻辑,生成随机数作为tag。如果要使用Get生命周期,还需要通过Get.put(tag:)方法注入。一个在页面中嵌入组件的例子:
class FatherLogic extends GetxController {
final son = SonLogic();
@override
void onClose() {
// 不需要显式调用son的onClose方法,Get生命周期会自动管理,在页面退出时会自行调用
super.onClose();
}
}
class FatherWidget extends StatelessWidget {
final logic = FatherLogic();
FatherWidget({super.key});
@override
Widget build(BuildContext context) {
return GetBuilder<FatherLogic>(builder: (_) => SonWidget(controller: logic.son));
}
}
class SonLogic extends GetxController {
final tag = UniqueKey().toString();
@override
void onClose() {
// 在这里释放你的资源
super.onClose();
}
}
class SonWidget extends StatelessWidget {
final SonLogic logic;
SonWidget({
super.key,
required SonLogic controller, // 通过外部持有,否则会有生命周期问题导致无法刷新
}) : logic = controller {
if (!Get.isRegistered<SonLogic>(tag: logic.tag)) {
Get.put(logic, tag: logic.tag);
}
}
@override
Widget build(BuildContext context) {
return GetBuilder<SonLogic>(tag: logic.tag, builder: (_) => Container());
}
}
如果是通过导航器navigator推入的页面,要实现多实例共存、处理被动刷新的情况,在GetBuilder的didUpdateWidget
回调方法中释放资源,一个例子:
class ExampleLogic extends GetxController {
final tag = UniqueKey().toString();
@override
void onClose() {
// 在这里释放你的资源
super.onClose();
}
}
class ExamplePage extends StatelessWidget {
final logic = ExampleLogic();
ExampleWidget({super.key}) {
if (!Get.isRegistered<ExampleLogic>(tag: logic.tag)) {
Get.put(logic, tag: logic.tag);
}
}
@override
Widget build(BuildContext context) {
return GetBuilder<ExampleLogic>(
tag: logic.tag,
builder: (_) => Container(),
didUpdateWidget: (old, _) {
// 处理被强制刷新的情况。这里判断如果新旧controller的tag是一样的,说明con-
// troller是指定的同一个实例,不需要手动释放,页面退出时会自动释放;如果不一
// 样,说名是新的实例,需要手动释放旧的。
if (Get.isRegistered<ExampleLogic>(tag: old.tag) && old.tag != logic.tag) {
Get.delete(tag: old.tag);
}
},
);
}
}
- 如果将Widget以参数形式传递给某个方法进行展示,页面退出时Widget相关资源可能不会释放,正确的做法是改为函数形式传参:
showSomeWidget(() => widgetBuilder(someParam));
千万不能这样写:
final widget = widgetBuilder(someParam);// 错误,组件提前创建了实例,仍然会导致不释放问题
showSomeWidget(() => widget);
- math库建议使用别名。如果你在遍历数组时习惯使用变量名"e",那么某些情况下可能会意外的引用math中的
e
,而且能编译通过,很容易踩坑。
import 'dart:math' as math;