超赞的 Flutter 技巧和窍门

#Day1 stless & stful

我们可以输入 stlessstful,我们会得到自动补全建议,以分别生成无状态 Flutter Widget 或有状态 Flutter Widget。

01stlesstful

#Day2 If Null 操作符 (??)。

?? 检查是否为 null。如果不为 null,则返回其自身的值,如果为 null,则返回 ?? 后面的值。

return abc??10; //如果 abc 为 null,则返回 10,否则返回其自身的值,

如果为 null,它也有简写的赋值方式。

abc??=5 //如果 abc 为 null,则将其赋值为 5

02ifnull

#Day3 内部函数。

我们可以在一个函数内部定义另一个函数。

这是为了将内部函数封装起来,使其不被外部的任何东西访问。

03functions

#Day4 ..级联..链式..流畅 API

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

Dartpad 中尝试

04cascadebefore

可以替换为

04cascadeafter

#Day5 Dart 数据类

Dart 默认不支持数据类,但通过插件,我们可以轻松生成数据类(由工具实现 copyWith()fromMap()toMap()命名构造函数toString()hashCode()equals() 方法)。

?❗️注意❗️?光标应位于要生成数据类的类内部。

下载插件

适用于 Android Studio

适用于 VS Code

#Day6 RichText Widget

如果您想在单个文本中拥有不同样式的文本?不要犹豫或尝试用 Text() 来 hacking,而是使用 RichText()TextSpan()

在 dartpad 上尝试

观看 YouTube 演示

06richtext

#Day7 Spacer Widget

使用具有特定高度/宽度的 Container 在 Widget 之间创建响应式空间?这在一个屏幕上看起来可能不错,但在不同屏幕尺寸上看起来会不一样。

Spacer Widget 应运而生。与其使用 Container(width: / height: ),不如使用 Spacer(flex: )

我怎么能早点不知道这个 Widget 呢?这会拯救很多生命?

在 dartpad 上尝试

观看 YouTube 演示

07spacer

#Day8

如果您一直像我一样在 ListItem 的底部添加带有 maxwidthContainer() 来放置分隔线,您一直都做错了。

Flutter 有 ListView.separated 就是为此目的。在使用 ListView.builder 时,除了已经传递的参数外,我们还需要提供 separatorBuilder

奖励????:您不必检查该项是否是最后一项,以避免在最后一项后绘制分隔线。

在 dartpad 中尝试

08separatedlist

#Day9 将函数作为参数传递

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

在 dartpad 中尝试

09functionargument


#Day10 相对导入:导入我们 lib 包中 .dart 文件的正确方法。

是否曾想过导入自己包中文件的正确方法?

优先使用相对导入而非绝对导入。

为什么?

  • 它更短、更简洁。
  • 我们可以轻松区分我们的文件和第三方文件。
  • 这很有道理,不是吗?

10import

#Day11 重用文本样式

厌倦了每次想要自定义 Text 时都定义 textStyle更糟糕的是,如果您有多个主题(深色、浅色、全黑主题等)。

只需使用

Theme.of(context).textTheme.title

其中 textTheme 中还有其他样式,如 title

在带有主题示例的 dartpad 中尝试

11texttheme

#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)。

如果声明不是单行,我不会使用 =>。但几行是可以的。

在 dartpad 中尝试

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),
        )

在 codepen 上尝试

14fractionallysizedbox

#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),则通常不会完全使用。

15flexibleexpanded

如果您完整阅读以下图片,您将完全理解 FlexibleExpanded 之间的区别。
15expandedvsflexible

在 codepen 中尝试

#Day16 批量声明

如果您一直单独声明每个成员,那么您可以一次声明相同类型的成员。

我不会一次声明 ageshoeSize,因为它们不相关。

巨大的力量伴随着巨大的责任,请明智地使用它。
16singlelinedeclartion

#Day17 SliverAppBar / 可折叠 AppBar / 视差标题

还记得 CollapsableAppBar (Android) / ParallaxHeader (iOS) 吗?Flutter 中的 SliverAppBar 可以完全做到这一点。

要使用它,您必须有一个 CustomScrollView 作为父级。

然后添加两个 Sliver。

  1. SliverAppBar
  2. SliverFillRemaining

您可以调整 snap、floating、pinned 等值以获得期望的效果。

在 dartpad 中尝试

在此处查看各种类型的 SliverAppBars

17sliverappbars

#Day18 键是什么?

18keys

是否曾想过为什么我们需要 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 (...) 将集合添加到集合中。

21nullsafecollectioninsert

在 dartpad 中尝试

#Day22 可调用类

在 flutter 中,我们可以像调用方法一样调用类的实例。

您需要做的是定义一个具有任何返回类型或参数的 call() 方法。当您调用该实例时,将调用该 call() 方法。

void main() {
    var member = CallableClass();
    member("Flutter");
}

class CallableClass{
    call(String name){
        print("Name is $name");
    }
}

在 dartpad 中尝试

#Day23 ListWheelScrollView

我们可以在 flutter 中使用 ListWheelScrollView 实现以下滚轮列表。

只需提供子项,它就会开始为您工作。

您可以使用 ListWheelScrollView 的构造函数参数来自定义滚轮,请尝试使用它们。

ListWheelScrollView(
    children: <Widget>[
        ..Children Widgets
    ],
)

在 dartpad 中尝试

在 codepen 上尝试

23wheelscrollview

#Day24 带凹口的矩形浮动操作按钮底部应用栏。

带圆角凹口的按钮栏和浮动操作按钮很酷。

但是

是否想要带凹口的矩形浮动操作按钮?

FloatingActionButton.extended 配合 BottomAppBarshape 作为 AutomaticNotchedShape,如下所示:

shape: AutomaticNotchedShape(
        RoundedRectangleBorder(),
        StadiumBorder(
          side: BorderSide(),
        ),
      ),

请在您的编辑器中尝试此代码

24rectangularnotch

#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),
    ),)

在 pub.dev 上尝试

25googlefontstest

#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
    ...
    );
}

在 Codepen 上尝试

为了获得更好的体验:减小浏览器宽度

26hero

#Day27 Dart 函数/构造函数参数

有三种参数(函数参数和构造函数参数的工作方式相同)。

  1. 普通参数(✅✅简短 & ❌灵活)=> 必需,要求按顺序调用所有参数,最简洁(不需要参数名),最不灵活。

  2. 命名参数(✅简短 & ✅✅灵活)=> 可选,可以按任何顺序调用,但必须提供参数名。

  3. 位置参数(✅✅简短 & ✅灵活)=> 可选,但我们不能跳过左边的任何参数来提供右边的参数。不需要参数名。

     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");
     }
    

在 dartpad 中尝试

#Day28 AnimatedContainer

隐式动画 Widget,如 AnimatedAlign、AnimatedContainer、AnimatedPadding、AnimatedTheme 是实现动画的简便方法。

AnimatedContainer() 是最常用的之一。

您可以使用 AnimatedContainer 动画化 container 的任何属性。仅掌握此 Widget 就能让您在动画游戏中走得更远。

只需提供更改后的值,例如

height, width,padding,transform,decoration(backgroundcolor, border radius & alignment etc.

并带有曲线,AnimatedContainer 将自动为您完成动画。

以下动画仅使用 AnimatedContainer() 完成。

在 codepen 中玩转动画

28animatedcontainer