30+ Flutter & Dart 技巧合集[翻译]

302 阅读5分钟

原文:medium.com/flutter-com…

作者:Mo Malaka

声明:本文对原文部分内容进行了精简(原文共 104 条,本文约 30 条)并做了简单翻译。为便于快速定位原文位置,保留了原文的编号。不足之处还请指正,感兴趣的可以阅读原文。 :)


你好读者,

这篇文章列出了我在过去几个月发布的 100 多个(原文 100+)技巧。如果你是 Flutter/Dart 世界的新手,其中一些可能会对你有所帮助。

3- 使用 Future.wait 等待多个 Future 的结果完成

class someAPI {
  Future<int> getThings() => Future.value(3000);
  Future<int> getItems() => Future.value(300);
  Future<int> getStuff() => Future.value(30);
}

...

final api = someAPI();
final values = await Future.wait([
  api.getThings(),
  api.getItems(),
  api.getStuff(),
]);
print(values);

6- 使用 flutter/services.dart SystemChrome 锁定设备方向

import 'package:flutter/services.dart';

...

void main() async {
  await SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
  ]);
  runApp(App());
}

7- 你可以使用 dart:io 库编写特定平台的代码

import 'dart:io' show Platform;

...

if (Platform.isIOS) {
  doSomethingforIOS();
}

if (Platform.isAndroid) {
  doSomethingforAndroid();
}

8- 将任何 Color 转换为 Material Color

const Map<int, Color> color = {
  50: Color.fromRGBO(255, 207, 68, .1),
  100: Color.fromRGBO(255, 207, 68, .2),
  200: Color.fromRGBO(255, 207, 68, .3),
  300: Color.fromRGBO(255, 207, 68, .4),
  400: Color.fromRGBO(255, 207, 68, .5),
  500: Color.fromRGBO(255, 207, 68, .6),
  600: Color.fromRGBO(255, 207, 68, .7),
  700: Color.fromRGBO(255, 207, 68, .8),
  800: Color.fromRGBO(255, 207, 68, .9),
  900: Color.fromRGBO(255, 207, 68, 1),
};

const MaterialColor custom_color = MaterialColor(0xFFFFCF44, color);

9- 使用 flutter/services.dart SystemChrome 隐藏状态栏

import 'package:flutter/services.dart';

void main() {
  SystemChrome.setEnabledSystemUIOverlays([]);
  /// 上面接口已废弃,最新方式为:
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
}

11- 使用 FadeInImagetransparent_image 库来显示一个透明的占位图,等待图像加载完成时淡入显示

import 'package:transparent_image/transparent_image.dart';

...

FadeInImage.memoryNetwork(
  placeholder: kTransparentImage, 
  image: someImageUrl,
  fit: BoxFit.fill,
),

13- 定制 TextField 光标样式

image.png

TextField(
  cursorColor: Colors.purple,
  cursorRadius: Radius.circular(8.0),
  cursorWidth: 8.0,
)

15- 使用 Expanded 调整子 widget 大小,以适应 RowColumn

Row(
  crossAxisAlignment: CrossAxisAlignment.center,
  children: [
    Expanded(
      child: Image.asset('images/pic1.jpg'),
    ),
    Expanded(
      child: Image.asset('images/pic2.jpg'),
    ),
    Expanded(
      child: Image.asset('images/pic3.jpg'),
    ),
  ],
);

16- 使用 Hero 实现以动画的方式,将 widget 从一个页面移动到下一个页面

// Page 1

Hero(
  tag: "FlyingHero",
  child: Icon(
    Icons.party_mode_outlined,
    size: 50.0,
  ),
),

...

  // Page 2

  Hero(
  tag: "FlyingHero",
  child: Icon(
    Icons.party_mode_outlined,
    size: 150.0,
  ),
),

18- 使用 ThemeData 定义整个应用程序的颜色、字体、形状、设计样式。可以这种方式在一个地方集中设置你所需要的风格。

MaterialApp(
  title: appName,
  theme: ThemeData(
    // The default brightness and colors.
    brightness: Brightness.light,
    primaryColor: Colors.lightGreen[500],
    accentColor: Colors.amber[600], /// 该字段已过时
    // The default font family.
    fontFamily: 'Georgia',
    // TextTheme & text styling
    textTheme: TextTheme(
      headline1: TextStyle(fontSize: 64.0, fontWeight: FontWeight.bold),
      headline6: TextStyle(fontSize: 36.0, fontStyle: FontStyle.italic),
      bodyText2: TextStyle(fontSize: 14.0, fontFamily: 'Hind'),
    ),
  ),
  home: MyHomePage(
    title: appName,
  ),
);

19- 使用 package:flutter/foundation.dart 中的常量 kReleaseMode 在 debug 或 release 模式下执行代码

import 'package:flutter/foundation.dart';
if (kReleaseMode) {
  // Release Mode
  print('release mode');
} else {
  print('debug mode');
}

23- 使用 padLeftpadRight 分别在字符串左侧和右侧填充字符。

String s = "fun";
print(s.padLeft(10, ',.*')); // ',.*,.*,.*,.*fun'
print(s.padRight(10, ',.*')); // 'fun,.*,.*,.*,.*'

25- 将 debugShowCheckedModeBanner 设置为 false 可以删除右上角调试横幅标记

MaterialApp(
  debugShowCheckedModeBanner: false,
  // other arguments
)

28- 要编写任何 Dart 程序——无论是脚本还是 Flutter 应用程序——都必须定义 main() 函数,它是每个应用程序的入口点

void main() {
  var list = ['London', 'Dublin', 'Paris'];
  list.forEach((item) {
    print('${list.indexOf(item)}: $item');
  });
}

29- 在按下按钮时更改按钮的文本颜色

TextButton(
  onPressed: () {},
  style: ButtonStyle(
    foregroundColor: MaterialStateProperty.resolveWith<Color>(
      (Set<MaterialState> states) {
        if (states.contains(MaterialState.pressed)) return Colors.pink;
        return Colors.black; // Defer to the widget's default.
      }),
  ),
  child: Text(
    'Change My Color',
    style: TextStyle(fontSize: 30),
  ),
),

32- Dart 允许创建一个可调用的类,可以将类的实例作为函数调用

class WelcomeToTheCity {
  // Defining call method
  String call(String a, String b, String c) => 'Welcome to $a$b$c';
}

void main() {
  var welcome_to_city = WelcomeToTheCity();

  // Calling the class through its instance
  var welcome_msg = welcome_to_city('SanDiego ', 'CA ', 'USA');

  print(welcome_msg);
}

33- 使用 InkWell 创建水波纹效果,产生触摸响应的视觉体验

InkWell(
  splashColor: Colors.red,
  onTap: () {},
  child: Card(
    elevation: 5.0,
    child: Text(
      ' Click This ',
      style: Theme.of(context).textTheme.headline2,
    ),
  ),
),

38- 使用 FittedBox 来缩放调整内部的子部件。它可以限制其子部件的大小避免超过限制,也可以根据可用的大小重新缩放它们。

image.png

Center(
  child: Row(children: [
    Expanded(
      child: FittedBox(
        child: Text(
          "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout.",
          maxLines: 1,
          style: TextStyle(fontSize: 23),
        ),
      ),
    ),
  ]))

41- Mixins 可以实现不通过创建子类的方式插入代码块

mixin Fluttering {
  void flutter() {
    print('fluttering');
  }
}



abstract class Bird with Fluttering {
  void flap() {
    print('flap flap');
  }
}

class Hawk extends Bird {
  void doHawkThing() {
    flap();
    flutter();
    print('Hawk is Flying');
  }
}

main() {
  var hawk = new Hawk();
  hawk.doHawkThing();

}

42- 可以将一个函数作为参数传递给另一个函数

void main() {
  const list = ['London', 'Dublin', 'Belfast'];
  list.forEach(
    (item) => print('City Of $item'));
}

43- Getter 和 Setter 是提供对象属性读写访问权限的特定方法

class Rectangle {
  double left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  // Define two calculated properties: right and bottom.
  double get right => left + width;
  set right(double value) => left = value - width;
  double get bottom => top + height;
  set bottom(double value) => top = value - height;
}

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  print(rect.left);
  rect.right = 12;
  print(rect.left);
}

44- 有四种不同的方式来追加或连接字符串

void main() {

  //1- Using ‘+’ operator.
  String str1 = "Dart";
  String str2 = "Is";
  String str3 = "Fun";

  print(str1 + str2 + str3);

  //2- String interpolation.
  print('$str1 $str2 $str3');

  //3- Directly writing string literals.
  print('Dart' 'Is' 'Fun');

  //4- Strings.join() method.

  // Creating List of strings
  List<String> str = ["Dart", "Is", "Fun"];

  String _thestr = str.join();
  print(_thestr);

  _thestr = str.join(" ");
  print(_thestr);
}

49- 使用 barrierDismissible 属性来防止点击 Dialog 外部区域时关闭 AlertDialog

...

  showDialog<String>(
  context: context,
  barrierDismissible: false,
  builder: (BuildContext context) => AlertDialog(
    title: const Text('This is a title'),
    content: const Text('This is a description'),
    actions: <Widget>[
      TextButton(
        onPressed: () => Navigator.pop(context, 'Cancel'),
        child: const Text('Cancel'),
      ),
      TextButton(
        onPressed: () => Navigator.pop(context, 'OK'),
        child: const Text('OK'),
      ),
    ],
  ),
),

...

51- 使用 ShaderMask 为子节点设置渐变外观

image.png

Center(
  child: ShaderMask(
    blendMode: BlendMode.srcIn,
    shaderCallback: (Rect bounds) {
      return LinearGradient(
        colors: [Colors.red, Colors.green],
        tileMode: TileMode.clamp,
      ).createShader(bounds);
    },
    child:
    const Text('This is a colorful Text', style: TextStyle(fontSize: 36)),
  ),
)

54- 使用 ColorFiltered 对其子节点设置颜色过滤器

image.png

Column(
  children: [
    // The original image
    Image.network('https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d18e23b15fb427293c63c269c8a6847~tplv-k3u1fbpfcp-no-mark:480:480:0:0.awebp?'),

    // The black-and-white image
    ColorFiltered(
      colorFilter: ColorFilter.mode(Colors.grey, BlendMode.saturation),
      child: Image.network('https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1d18e23b15fb427293c63c269c8a6847~tplv-k3u1fbpfcp-no-mark:480:480:0:0.awebp?'),
    ),
  ],
)

57- 可以使用 LayoutBuilder 获取父 Widget 大小,并根据父 Widget 大小创建子 Widget 树

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth >= 750) {
      return Container(
        color: Colors.green,
        height: 100,
        width: 100,
      );
    } else {
      return Container(
        color: Colors.yellow,
        height: 100,
        width: 100,
      );
    }
  },
)

75- 使用 GestureDetector 检测手势,如点击、双击、按下、长按、平移、拖动、缩放等

Container(
  color: _color,
  height: 200.0,
  width: 200.0,
  child: GestureDetector(
    onTap: () {
      setState(() {
        _color = Colors.yellow;
      });
    },
  ),
)

77- 使用 Future.delayed() 将程序暂停一段时间

await Future.delayed(const Duration(seconds: 1));

82- 使用 Flexible 修复 Text Overflow 错误

Flexible(
  child: Text(
    'Flutter Text Overflow while adding long text. how to wrap text in flutter ',
    style: TextStyle(fontSize: 20),
  ),
)

86- 使用 PhysicalModel 添加自定义阴影效果并自定义其颜色和形状

image.png

PhysicalModel(
  elevation: 6.0,
  shape: BoxShape.circle,
  shadowColor: Colors.black,
  color: Colors.white,
  child: Container(
    height: 120.0,
    width: 120.0,
    alignment: Alignment.center,
    child: Text('Content'),
  ),
)

91- 使用 Tooltip 帮助解释按钮或其他用户界面操作的功能

image.png

Tooltip(
    message: 'I am a Tooltip',
    triggerMode: TooltipTriggerMode.tap,
    child: Text('Hover over the text to show a tooltip.'),
)

92- SelectableText 允许用户选择/复制 UI 上的内容

image.png

const SelectableText(
  'Hello! How are you?',
  textAlign: TextAlign.center,
  style: TextStyle(fontWeight: FontWeight.bold),
)

100- Baseline 可以根据其 child 的基线定位 child 的位置

Center(
  child: Container(
    color: Colors.blue,
    height: 120.0,
    width: 120.0,
    child: Baseline(
      child: Container(
        color: Colors.red,
        height: 60.0,
        width: 60.0,
      ),
      baseline: 20.0,
      baselineType: TextBaseline.alphabetic,
    ),
  ),
)

101- Baner 会在另一个Widget的角落上方显示一条对角线消息

image.png

Center(
  child: Container(
    margin: const EdgeInsets.all(10.0),
    color: Colors.yellow,
    height: 100,
    child: ClipRect(
      child: Banner(
        message: "hello",
        location: BannerLocation.topEnd,
        color: Colors.red,
        child: Container(
          color: Colors.yellow,
          height: 100,
          child: Center(
            child: Text("Hello, banner!"),
          ),
        ),
      ),
    ),
  ),
)

103/104 - IntrinsicWidth/IntrinsicHeight 可以将它的子 widget 的宽度/高度调整其本身实际的宽度/高度

...

IntrinsicWidth(
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      Container(
        width: 200,
        height: 100,
        color: Colors.teal,
      ),
      Expanded(
        child: Container(
          width: 100,
          height: 100,
          color: Colors.red,
        ),
      ),
    ],
  ),
)

...
  
IntrinsicHeight(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      Container(
        width: 150,
        height: 100,
        color: Colors.teal,
      ),
      Container(
        width: 150,
        height: 200,
        color: Colors.red,
      )
    ],
  ),
)
​
...