收集了一些日常开发中会用到技巧或者语法糖,简化代码,提升开发效率。
函数扩展
在 Dart
中,我们可以为扩展任何数据类型,而任何函数都能转换成数据类型,所以,我们也可以在 Dart
中扩展任何类型的函数,函数扩展是通过扩展方法的方式实现的,通过使用 extension
关键字。其目的就是为了提高代码的可读性和可维护性。
如下面的例子,为 String
类型添加了一个名为 capitalize()
的方法,用于将字符串的首字母大写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| extension StringExtensions on String { String capitalize() { if (isEmpty) { return this; } return substring(0, 1).toUpperCase() + substring(1); } }
class DartTipsPage extends StatefulWidget { const DartTipsPage({Key? key}) : super(key: key);
@override State<DartTipsPage> createState() => _DartTipsPageState(); }
class _DartTipsPageState extends State<DartTipsPage> { @override void initState() { super.initState(); String text = "hello"; print(text.capitalize()); } }
|
还可以直接给函数类型创建 extension
,在函数之上添加功能。如下例,为函数类型添加延迟调用的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| extension on VoidCallback { Future<void> delayedCall( Duration duration, ) => Future<void>.delayed(duration, this); }
class DartTipsPage extends StatefulWidget { const DartTipsPage({Key? key}) : super(key: key);
@override State<DartTipsPage> createState() => _DartTipsPageState(); }
class _DartTipsPageState extends State<DartTipsPage> { @override void initState() { super.initState();
_click.delayedCall(const Duration(seconds: 1)); } _click(){ print("delayedCall"); } }
|
这里的 VoidCallback
是一种已经转换为数据类型的函数类型,所以我们也可以为其添加扩展。
安全数组
在 Flutter
开发时,碰到数组越界或者访问数组中不存在的元素情况时,会引起运行时错误,如:
1 2
| List<int> numbers = [1, 2, 3, 4, 5]; print(numbers[5]);
|
尝试访问索引为 5 的元素,但数组长度只有 5 个元素,就会触发数组越界错误,Flutter
报错信息:
1
| RangeError (index): Index out of range: index should be less than 5: 5
|
当然也可以使用 try-catch
来捕获异常,但使用起来有些繁琐,很多时候问题没法儿及时发现和修复,那有没有更好的办法呢?当然有,自定义一个继承自 ListBase
的类:SafeList
,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import 'dart:collection';
class SafeList<T> extends ListBase<T> { final List<T> _list; final T defaultValue;
final T absentValue;
SafeList({ required this.defaultValue, required this.absentValue, List<T>? values, }) : _list = values ?? [];
@override T operator [](int index) => index < _list.length ? _list[index] : absentValue;
@override void operator []=(int index, T value) => _list[index] = value;
@override int get length => _list.length;
@override T get first => _list.isNotEmpty ? _list.first : absentValue;
@override T get last => _list.isNotEmpty ? _list.last : absentValue;
@override set length(int newValue) { if (newValue < _list.length) { _list.length = newValue; } else { _list.addAll(List.filled(newValue - _list.length, defaultValue)); } } }
|
调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const notFound = 'Value Not Found'; const defaultString = '';
final SafeList<String> myList = SafeList( defaultValue: defaultString, absentValue: notFound, values: ['Flutter', 'iOS', 'Android'], );
print(myList[0]); print(myList[1]); print(myList[2]); print(myList[3]);
myList.length = 5; print(myList[4]);
myList.length = 0; print(myList.first); print(myList.last);
|
获取图片宽高比
获取图片的宽高比在 Flutter
开发中是一个很常见的需求,可以帮助我们更加精准的地控制UI布局,特别是需要设置图片宽高的时候,做响应式布局的时候也需要根据图片的宽高比动态调整UI布局。
在 ImageStream
的 addListener
回调中使用的 Completer
,用来处理Future对象的完成和结果传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import 'dart:async' show Completer; import 'package:flutter/material.dart' as material show Image, ImageConfiguration, ImageStreamListener;
extension GetImageAspectRatio on material.Image { Future<double> getAspectRatio() { final completer = Completer<double>(); image.resolve(const material.ImageConfiguration()).addListener( material.ImageStreamListener( (imageInfo, synchronousCall) { final aspectRatio = imageInfo.image.width / imageInfo.image.height; imageInfo.image.dispose(); completer.complete(aspectRatio); }, ), ); return completer.future; } }
|
调用代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class _DartTipsPageState extends State<DartTipsPage> { @override void initState() { super.initState(); _getImageAspectRatio(); }
_getImageAspectRatio() async { Image wxIcon = Image.asset("images/wx_icon.png"); var aspectRatio = await wxIcon.getAspectRatio(); print(aspectRatio); }
@override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xffffffff), ); } }
|
自动添加间距
Flutter
的 Row
或者 Column
需要在元素之间添加相同间距的时候经常会有这样的代码:
1 2 3 4 5 6 7 8 9 10 11 12
| Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ _hasFirstSpacing ? SizedBox(width: 20,) : Container(), Text("Flutter"), SizedBox(width: 20,), Text("ReactNative"), SizedBox(width: 20,), Text("uni-app"), ], ),
|
而且有的情况是第一个间距是否添加还需要根据条件来判断,这样重复的代码太多,可读性也比较差,有没有一种更优雅的方式呢?来看看下面的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class RowWithSpacing extends Row { RowWithSpacing({ super.key, double spacing = 8, bool existLeadingSpace = false, super.mainAxisAlignment, super.mainAxisSize, super.crossAxisAlignment, super.textDirection, super.verticalDirection, super.textBaseline, List<Widget> children = const [], }) : super( children: [ ...existLeadingSpace ? [SizedBox(width: spacing)] : <Widget>[], ...children.expand( (w) => [ w, SizedBox(width: spacing), ], ) ], ); }
|
其实就是继承 Row
,在自定义类 RowWithSpacing
内部自动添加 spacing
,将上面的例子改成使用 RowWithSpacing
,代码是不是也清爽多了。
1 2 3 4 5 6 7 8 9 10 11
| RowWithSpacing( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, existLeadingSpace: true, spacing: 20, children: [ Text("Flutter"), Text("ReactNative"), Text("uniapp"), ], ),
|
以上这些技巧代码量都不多,简单轻便,拿来就用,日常开发中也能带来不少方便,代码和测试的 Demo
放在了老地方,不知道的先关注公众号后,回复源码就能看到。原创不易,您的关注是我更新下去最大的动力。