超赞的 Flutter 技巧和窍门
#Day1 stless & stful。
我们可以输入 stless 和 stful,我们会得到自动补全建议,以分别生成无状态 Flutter Widget 或有状态 Flutter Widget。

#Day2 If Null 操作符 (??)。
?? 检查是否为 null。如果不为 null,则返回其自身的值,如果为 null,则返回 ?? 后面的值。
return abc??10; //如果 abc 为 null,则返回 10,否则返回其自身的值,
如果为 null,它也有简写的赋值方式。
abc??=5 //如果 abc 为 null,则将其赋值为 5

#Day3 内部函数。
我们可以在一个函数内部定义另一个函数。
这是为了将内部函数封装起来,使其不被外部的任何东西访问。

#Day4 ..级联..链式..流畅 API
我们可以链式调用方法/成员,而无需从 **方法()、getter() 和 setter()** 中返回 this,使用级联操作符 (..)
在 Dartpad 中尝试

可以替换为

#Day5 Dart 数据类
Dart 默认不支持数据类,但通过插件,我们可以轻松生成数据类(由工具实现 copyWith()、fromMap()、toMap()、命名构造函数、toString()、hashCode() 和 equals() 方法)。
?❗️注意❗️?:光标应位于要生成数据类的类内部。
下载插件
#Day6 RichText Widget
如果您想在单个文本中拥有不同样式的文本?不要犹豫或尝试用 Text() 来 hacking,而是使用 RichText() 和 TextSpan()。

#Day7 Spacer Widget
使用具有特定高度/宽度的 Container 在 Widget 之间创建响应式空间?这在一个屏幕上看起来可能不错,但在不同屏幕尺寸上看起来会不一样。
Spacer Widget 应运而生。与其使用 Container(width: / height: ),不如使用 Spacer(flex: )。
我怎么能早点不知道这个 Widget 呢?这会拯救很多生命?

#Day8
如果您一直像我一样在 ListItem 的底部添加带有 maxwidth 的 Container() 来放置分隔线,您一直都做错了。
Flutter 有 ListView.separated 就是为此目的。在使用 ListView.builder 时,除了已经传递的参数外,我们还需要提供 separatorBuilder。
奖励????:您不必检查该项是否是最后一项,以避免在最后一项后绘制分隔线。

#Day9 将函数作为参数传递
我们可以像传递变量一样,轻松地将一个 函数 作为 参数 传递。当我们要从调用函数中调用传递的函数时,只需在其末尾加上 (),如果它接受任何参数,也一起传递。

#Day10 相对导入:导入我们 lib 包中 .dart 文件的正确方法。
是否曾想过导入自己包中文件的正确方法?
优先使用相对导入而非绝对导入。
为什么?
- 它更短、更简洁。
- 我们可以轻松区分我们的文件和第三方文件。
- 这很有道理,不是吗?

#Day11 重用文本样式
厌倦了每次想要自定义 Text 时都定义 textStyle?更糟糕的是,如果您有多个主题(深色、浅色、全黑主题等)。
只需使用
Theme.of(context).textTheme.title
其中 textTheme 中还有其他样式,如 title。

#Day12 使用字面量初始化可增长集合。
如果我们要初始化可增长集合,请使用字面量初始化而不是构造函数。
// Good
var points = [];
var addresses = {};
// Bad
var points = List();
var addresses = Map();
// With type argument
// Good
var points = <Point>[];
var addresses = <String, Address>{};
// Bad
var points = List<Point>();
var addresses = Map<String, Address>();
#Day13 箭头函数
我们可以在 dart 中使用箭头 => 成员(函数、getter、setter)。
如果声明不是单行,我不会使用 =>。但几行是可以的。
void main() {
User()
..firstName = "Laxman"
..lastName = " Bhattarai"
..age = 18
..printUser();
}
class User {
String firstName;
String lastName;
DateTime birthday;
String get fullName => firstName + lastName;
set age(int age) => birthday = DateTime.now().subtract(Duration(days: age * 365));
int get age => DateTime.now().year - birthday.year;
bool get isAdult => age >= 18;
printUser() => print(fullName + " is a ${isAdult ? "Adult" : "Child"}");
}
#Day14 FractionallySizedBox
是否曾希望 Widget 的高度和宽度与其屏幕高度和宽度完全成比例?
FractionallySizedBox 正是为了这种用例而构建的。只需为其高度和宽度提供所需的比例,它就会处理所有其他事情。比例值将在 0.0 到 1.0 之间。
FractionallySizedBox(
widthFactor: 0.5,
heightFactor: 0.5,
child: Container(color: Colors.green),
)

#Day15 Flexible vs Expanded
Expanded() 实际上就是 Flexible(),但
Flexible (fit: FlexFit.tight) = Expanded()
但是,Flexible 默认使用 fit :FlexFit.loose。
FlexFit.tight = 希望紧密适应父级,尽可能多地占用空间。
FlexFit.loose = 希望松散地适应父级,为自身占用尽可能少 Thus.
flex = 从父级获取空间的因子。如果使用 flex: FlexFit.loose(即 Flexible),则通常不会完全使用。

如果您完整阅读以下图片,您将完全理解 Flexible 和 Expanded 之间的区别。

#Day16 批量声明
如果您一直单独声明每个成员,那么您可以一次声明相同类型的成员。
我不会一次声明 age 和 shoeSize,因为它们不相关。
巨大的力量伴随着巨大的责任,请明智地使用它。

#Day17 SliverAppBar / 可折叠 AppBar / 视差标题
还记得 CollapsableAppBar (Android) / ParallaxHeader (iOS) 吗?Flutter 中的 SliverAppBar 可以完全做到这一点。
要使用它,您必须有一个 CustomScrollView 作为父级。
然后添加两个 Sliver。
- SliverAppBar
- SliverFillRemaining
您可以调整 snap、floating、pinned 等值以获得期望的效果。

#Day18 键是什么?

是否曾想过为什么我们需要 GlobalKey (children : GlobalObjectKey, LabeledGlobalKey), LocalKey (children: ObjectKey, ValueKey & UniqueKey)?
它们用于在 statefulWidget 中访问或恢复状态(如果您的 Widget 树都是 Stateless Widgets,我们通常根本不需要它们)。
目的(括号内的键)
- 在有状态 Widget 中修改集合,即从列表中删除/添加/重新排序项目,例如可拖动的待办事项列表,其中已勾选的项目将被删除(ObjectKey、ValueKey 和 UniqueKey)。
- 将 Widget 从一个父级移动到另一个父级,同时保留其状态。(GlobalKey)
- 在多个屏幕上显示相同的 Widget 并保留其状态。(GlobalKey)
- 验证表单。(GlobalKey)
- 您可以在不使用任何数据的情况下给出键。(UniqueKey)
- 如果您可以使用数据的某个字段,如用户的 UUID 作为唯一键。(ValueKey)
- 如果您没有可以作为键的唯一字段,但对象本身是唯一的。(ObjectKey)
- 如果您有多个需要 GlobalKey 的表单或相同类型的多个 Widget。(GlobalObjectKey、LabeledGlobalKey,以最合适者为准,逻辑类似于 ValueKey 和 ObjectKey)。
#Day19 超赞的库 Time.dart
如果您厌倦了冗长复杂的 DateTime 和 Duration 计算,Time.dart 将为您排忧解难。
//Before
var 3dayLater = DateTime.now().add(Duration(days: 3)).day;
//After
var 3dayLater = 3.days.fromNow.day;
//Before
var duration = Duration(minutes: 10) +Duration(seconds: 15) -
Duration(minutes: 3) +Duration(hours: 2);
//After
var duration = 10.minutes + 15.seconds - 3.minutes + 2.hours;
//Before
await Future.delayed(Duration(seconds: 2))
//After
await 2.seconds.delay
试试看 https://github.com/jogboms/time.dart
#Day20 测试错误
您可以使用 expect(actual, expected) 在 dart 中轻松测试两个值是否相等。
但如果您想测试错误,请使用抛出错误的 函数闭包 作为实际值,并使用 throwsA<ErrorType> 作为期望值进行检查。
void main() {
group("Exception/Error testing", () {
test("test method that throws errors", () {
expect(_testError(fails: false), false);
expect(() => _testError(fails: true), throwsA(isA<FooError>()));
});
});
}
bool _testError({bool fails}) {
if(fails)throw FooError();
return fails;
}
class FooError extends Error {}
使用 Spread(...) 操作符将集合简洁地添加到集合中
我们通常对集合使用 addAll() 来将一个集合添加到另一个集合。
但从 dart 2.3 及更高版本开始,我们可以使用 Spread Operator (...) 将集合添加到集合中。

#Day22 可调用类
在 flutter 中,我们可以像调用方法一样调用类的实例。
您需要做的是定义一个具有任何返回类型或参数的 call() 方法。当您调用该实例时,将调用该 call() 方法。
void main() {
var member = CallableClass();
member("Flutter");
}
class CallableClass{
call(String name){
print("Name is $name");
}
}
#Day23 ListWheelScrollView
我们可以在 flutter 中使用 ListWheelScrollView 实现以下滚轮列表。
只需提供子项,它就会开始为您工作。
您可以使用 ListWheelScrollView 的构造函数参数来自定义滚轮,请尝试使用它们。
ListWheelScrollView(
children: <Widget>[
..Children Widgets
],
)

#Day24 带凹口的矩形浮动操作按钮底部应用栏。
带圆角凹口的按钮栏和浮动操作按钮很酷。
但是
是否想要带凹口的矩形浮动操作按钮?
FloatingActionButton.extended 配合 BottomAppBar 的 shape 作为 AutomaticNotchedShape,如下所示:
shape: AutomaticNotchedShape(
RoundedRectangleBorder(),
StadiumBorder(
side: BorderSide(),
),
),

#Day25 Flutter 中的 Google Fonts
使用 pub.dev 包 google_fonts,您可以直接使用任何 Google 字体,无需下载。
只需将 textStyle 设置为任何 Google 字体即可。
想设置其他 textStyles 属性?只需为字体提供 textStyle(它本身就是一个 textStyle)。
Text(
'Notched Rectangular Fab',
style: GoogleFonts.pacifico(
textStyle: TextStyle(color: Colors.red),
),)

#Day26 Hero 动画(共享元素过渡)
您是否想让您的 Widget/Image 在一个屏幕飞到另一个屏幕?
Flutter 使使用 Hero Widget 进行共享元素/Hero 动画变得超级简单。
只需在两个屏幕上为 Hero Widget 提供相同的 tag,您的 Widget 就会开始从一个屏幕飞到另一个屏幕。
注意:如果您的 UI 具有动态数据(如列表),请勿将静态字符串作为 tag,请使用对象的值(如 title、id 等)作为 tag。
//First Screen
FirstPageWidget extends StatelessWidget{
return Scaffold(
...
Hero(
tag: player.name
child: Image.network(url)
)
//Other player List
...
);
}
//Second Page
SecondPageWidget extends StatelessWidget{
return Scaffold(
...
Hero(
tag: player.name
child: Image.network(url)
)
//Player details
...
);
}
为了获得更好的体验:减小浏览器宽度

#Day27 Dart 函数/构造函数参数
有三种参数(函数参数和构造函数参数的工作方式相同)。
-
普通参数(✅✅简短 & ❌灵活)=> 必需,要求按顺序调用所有参数,最简洁(不需要参数名),最不灵活。
-
命名参数(✅简短 & ✅✅灵活)=> 可选,可以按任何顺序调用,但必须提供参数名。
-
位置参数(✅✅简短 & ✅灵活)=> 可选,但我们不能跳过左边的任何参数来提供右边的参数。不需要参数名。
void main() { normalFunction("Laxman", "Bhattarai", 26, 65); optionalFunction("Laxman", "Bhattarai"); optionalFunction("Laxman", "Bhattarai", age: 26); optionalFunction("Laxman", "Bhattarai", weight: 65); optionalFunction("Laxman", "Bhattarai", weight: 65, age: 26); positionalFunction("Laxman", "Bhattarai"); positionalFunction("Laxman", "Bhattarai", 26); positionalFunction("Laxman", "Bhattarai", 26, 65); } //Requires all arguments passed in order, i.e. no meaning of default parameters normalFunction(String firstName, String lastName, int age, int weight) { print("$firstName $lastName age: $age weight: $weight"); } //Optional, can be called in any order BUT must provide the argument name. optionalFunction(String firstName, String lastName, {int age = 18, int weight = 60}) { print("$firstName $lastName age: $age weight: $weight"); } //Optional, doesn't need argument name but cannot be skipped an argument on left to provide argument on right of it. positionalFunction(String first, String last,[int age = 18, int weight = 60]) { print("$first $last age: $age weight: $weight"); }
#Day28 AnimatedContainer
隐式动画 Widget,如 AnimatedAlign、AnimatedContainer、AnimatedPadding、AnimatedTheme 是实现动画的简便方法。
AnimatedContainer() 是最常用的之一。
您可以使用 AnimatedContainer 动画化 container 的任何属性。仅掌握此 Widget 就能让您在动画游戏中走得更远。
只需提供更改后的值,例如
height, width,padding,transform,decoration(backgroundcolor, border radius & alignment etc.
并带有曲线,AnimatedContainer 将自动为您完成动画。
以下动画仅使用 AnimatedContainer() 完成。
