有时候我们需要获取到屏幕的宽度和高度还有密度进行计算。

使用Flutter时我们可以非常简单地通过类MediaQuery获取到。

1
2
MediaQueryData queryData;
queryData = MediaQuery.of(context);

获取屏幕密度/像素比例

1
queryData.devicePixelRatio

获取逻辑宽度高度

1
2
queryData.size.width
queryData.size.height

获取实际屏幕分辨率

注意上面我们获取到的只是逻辑上的宽高(Android上的dp/iOS上的pt),我们如果要取得屏幕的原始分辨率,需要乘以屏幕密度:

1
2
size.width * queryData.devicePixelRatio
size.height * queryData.devicePixelRatio

获取文字的缩放比例

1
queryData.textScaleFactor

获取硬件信息

如果要获取设备的详细的硬件信息,可以点击这里参考这篇文章

本文原创地址为:https://www.examplecode.cn/2019/04/11/flutter-screen-size-density/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

我们在进行各个系统的原生开发时,都有对应的方法获取设备信息,那么在使用Flutter时如何获取设备相关的相关信息呢?

我们本文就来介绍一个Flutter插件:
Flutter Device Info

下面我们来逐步介绍如何获取设备信息。

首先在工程的pubspec.yaml中添加依赖

1
2
dependencies:
device_info: ^0.4.0+1

下载安装这个依赖包

在工程主目录下执行:

1
flutter packages get

在代码中使用

首先我们引入device_info.dart:

1
import 'package:device_info/device_info.dart';

iOS安装cocoapods

如果需要运行在iOS上运行,如果电脑上没有安装cocoapods,此时会报出异常:

1
2
3
4
5
6
7
Warning: CocoaPods not installed. Skipping pod install.
CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
For more info, see https://flutter.io/platform-plugins
To install:
brew install cocoapods
pod setup

此时我们则需要安装并配置cocoapods(确保机器上已经安装好了brew):

1
2
brew install cocoapods
pod setup

获取Android与iOS设备信息

1
2
3
4
5
6
7
8
9
10
void getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = new DeviceInfoPlugin();
if(Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print(_readAndroidBuildData(androidInfo).toString());
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
print(_readIosDeviceInfo(iosInfo).toString());
}
}

构造设备信息的Map

为了更方便的打印和使用信息,我们将AndroidDeviceInfo和IosDeviceInfo构造成Map:

Android:

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
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'version.securityPatch': build.version.securityPatch,
'version.sdkInt': build.version.sdkInt,
'version.release': build.version.release,
'version.previewSdkInt': build.version.previewSdkInt,
'version.incremental': build.version.incremental,
'version.codename': build.version.codename,
'version.baseOS': build.version.baseOS,
'board': build.board,
'bootloader': build.bootloader,
'brand': build.brand,
'device': build.device,
'display': build.display,
'fingerprint': build.fingerprint,
'hardware': build.hardware,
'host': build.host,
'id': build.id,
'manufacturer': build.manufacturer,
'model': build.model,
'product': build.product,
'supported32BitAbis': build.supported32BitAbis,
'supported64BitAbis': build.supported64BitAbis,
'supportedAbis': build.supportedAbis,
'tags': build.tags,
'type': build.type,
'isPhysicalDevice': build.isPhysicalDevice,
'androidId': build.androidId
};
}

iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
return <String, dynamic>{
'name': data.name,
'systemName': data.systemName,
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'identifierForVendor': data.identifierForVendor,
'isPhysicalDevice': data.isPhysicalDevice,
'utsname.sysname:': data.utsname.sysname,
'utsname.nodename:': data.utsname.nodename,
'utsname.release:': data.utsname.release,
'utsname.version:': data.utsname.version,
'utsname.machine:': data.utsname.machine,
};
}

设备信息示例(基于模拟器):

Android:

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
{
version.securityPatch: 2018-09-05,
version.sdkInt: 28,
version.release: 9,
version.previewSdkInt: 0,
version.incremental: 5124027,
version.codename: REL,
version.baseOS: ,
board: goldfish_x86_64,
bootloader: unknown,
brand: google,
device: generic_x86_64,
display: PSR1.180720.075,
fingerprint: google/sdk_gphone_x86_64/generic_x86_64:9/PSR1.180720.075/5124027:user/release-keys,
hardware: ranchu,
host: abfarm730,
id: PSR1.180720.075,
manufacturer: Google,
model: Android SDK built for x86_64,
product: sdk_gphone_x86_64,
supported32BitAbis: [x86],
supported64BitAbis: [x86_64],
supportedAbis: [x86_64, x86],
tags: release-keys,
type: user,
isPhysicalDevice: false,
androidId: 998921b52c7a7b79
}

iOS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
name: iPhone XR,
systemName: iOS,
systemVersion: 12.1,
model: iPhone,
localizedModel: iPhone,
identifierForVendor: 367F5936-39E1-4DFA-8DD2-9542424256BE,
isPhysicalDevice: false,
utsname.sysname:: Darwin,
utsname.nodename:: bogon,
utsname.release:: 18.2.0,
utsname.version:: Darwin Kernel Version 18.2.0: Thu Dec 20 20:46:53 PST 2018;
root:xnu-4903.241.1~1/RELEASE_X86_64,
utsname.machine:: x86_64
}

获取屏幕宽高密度等信息

关于如何获取屏幕宽度高度和分辨率等信息,可以参考这篇文章

本文原创地址为:https://www.examplecode.cn/2019/04/10/flutter-device-info/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

当我们在开发APP时进行一些耗时操作,比如用户HTTP请求登录时,需要展示出一个模态的进度或加载对话框,防止用户重复操作。

本文我们就来介绍如何实现这样一个对话框。

实现进度对话框的话可以使用一个名叫modal_progress_hud的插件。

使用对话框插件

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
modal_progress_hud: ^0.1.3

下载插件

1
flutter packages get

示例:展示对话框

由于当前的对话框我们需要全屏展示,所以我们在整个页面之上包裹一个ModalProgressHUD,根据变量_saving的值是true还是false来控制对话框的展示与否。

1
2
3
4
5
6
7
8
9
10
11
12
...
bool _saving = false
...

@override
Widget build(BuildContext context) {
return Scaffold(
body: ModalProgressHUD(child: Container(
Form(...)
), inAsyncCall: _saving),
);
}

这里我们使用的是StatefulWidget,所以我们可以通过设置_saving的值来控制对话框的展示:

展示对话框:

1
2
3
setState(() {
_saving = true;
});

隐藏对话框:

1
2
3
setState(() {
_saving = false;
});

对话框的构造选项

我们可以定制对话框的展示,下面是我们构造对话框对象时的选项:

1
2
3
4
5
6
7
8
9
ModalProgressHUD(
@required inAsyncCall: bool,
@required child: Widget,
opacity: double,
color: Color,
progressIndicator: CircularProgressIndicator,
offset: double
dismissible: bool,
);

我们可以看到必选参数有两个:

  • inAsyncCall:控制对话框的显示状态。
  • child:子Widget。

通过其他的选项我们可以控制背景的透明度,颜色,展示样式等属性,有兴趣的同学可以逐一尝试一下。

本文原创地址为:https://www.examplecode.cn/2019/04/04/flutter-progress-dialog/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

在Android开发中,我们经常使用原生的Toast展示一些提示。现在在iOS开发过程中,Toast的使用也变得越来越流行。本文我们就来介绍如何在Flutter中使用Toast。

我们需要使用插件fluttertoast的插件。

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
fluttertoast: ^3.0.3

下载插件

1
flutter packages get

弹出Toast

1
2
3
4
5
6
7
8
9
10
11
import 'package:fluttertoast/fluttertoast.dart';

Fluttertoast.showToast(
msg: "Toast提示信息",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);

各个参数的说明

参数名 说明
msg 展示的文字内容
toastLength 时间长短Toast.LENGTH_SHORT、Toast.LENGTH_LONG
gravity 位置(上中下)ToastGravity.TOP、ToastGravity.CENTER、 ToastGravity.BOTTOM
timeInSecForIos 展示时长,仅iOS有效
bgcolor 背景颜色
textcolor 文字颜色
fontSize 文字大小

总结

我们可以自定义Toast的颜色、大小等,但是并不能深度定制,比如展示图片等。但是一般也足够我们使用了。
建议使用时位置居中ToastGravity.CENTER,因为如果位置居下,当输入法弹出时,展示效果并不友好。

本文原创地址为:https://www.examplecode.cn/2019/04/03/flutter-toast/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

在我们开发过程中我们经常使用正则表达式来进行字符串的匹配,本文我们就来介绍Dart中正则表达式的使用。

要使用正则表达式,我们需要用到RegExp类。

匹配-验证手机号

1
2
3
RegExp exp = RegExp(
r'^((13[0-9])|(14[0-9])|(15[0-9])|(16[0-9])|(17[0-9])|(18[0-9])|(19[0-9]))\d{8}$');
bool matched = exp.hasMatch(mobileTextController.text);

查找所有匹配结果

除了验证匹配之外,我们还可以查找所有的匹配,比如我们查找句子中的所有单词:

1
2
3
4
5
6
7
8
RegExp exp = new RegExp(r"(\w+)");
String str = "This is Lloyd's blog";
Iterable<Match> matches = exp.allMatches(str);

for (Match m in matches) {
String match = m.group(0);
print(match);
}

输出:

1
2
3
4
5
This
is
Lloyd
s
blog

总结

本文我们举例说明了在Dart中使用正则表达式的一些常用操作,如果需要更深入的研究,可以点击这里参考官方文档。

本文原创地址为:https://www.examplecode.cn/2019/04/02/flutter-regex/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

在我们实现某些功能时,可能会有倒计时的需求。

比如发送短信验证码,发送成功后可能要求用户一段时间内不能再次发送,这时候我们就需要进行倒计时,时间到了才允许再次操作。

如下图:
-w326

为了实现这样场景的需求,我们需要使用Timer.periodic

一、引入Timer对应的库

1
import 'dart:async';

二、定义计时变量

1
2
3
4
5
6
class _LoginPageState extends State<LoginPage> {
...
Timer _timer;
int _countdownTime = 0;
...
}

三、点击后开始倒计时
这里我们点击发送验证码文字来举例说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GestureDetector(
onTap: () {
if (_countdownTime == 0 && validateMobile()) {
//Http请求发送验证码
...
setState(() {
_countdownTime = 60;
});
//开始倒计时
startCountdownTimer();
}
},
child: Text(
_countdownTime > 0 ? '$_countdownTime后重新获取' : '获取验证码',
style: TextStyle(
fontSize: 14,
color: _countdownTime > 0
? Color.fromARGB(255, 183, 184, 195)
: Color.fromARGB(255, 17, 132, 255),
),
),
)

四、倒计时的实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void startCountdownTimer() {
const oneSec = const Duration(seconds: 1);

var callback = (timer) => {
setState(() {
if (_countdownTime < 1) {
_timer.cancel();
} else {
_countdownTime = _countdownTime - 1;
}
})
};

_timer = Timer.periodic(oneSec, callback);
}

五、最后在dispose()取消定时器

1
2
3
4
5
6
7
@override
void dispose() {
super.dispose();
if (_timer != null) {
_timer.cancel();
}
}

这样我们就实现了发送验证码的倒计时功能。除此之外,Timer还能做其他的很多事情,有兴趣的同学可以查看Timer的官方文档

本文原创地址为:https://www.examplecode.cn/2019/03/29/flutter-timer-countdown/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

如果比较熟悉材料设计(Material Design)的话,会发现里面有很多酷炫的效果,包括点击控件时,从手指的点击处向外扩散的水波效果。

Flutter也默认为我们的APP开发提供了材料设计的视图,但是会发现水波的展示有一些问题,特别是文本输入框TextField上面使用时。

下面是我遇到的问题截图:
-w370

当来回切换两个输入框时,发现有时输入框背后的水波扩散完后没有消失,导致如图的展示异常。

这是Flutter目前的一个Bug,如果你也遇到这种情况的话,有一种解决方式就是在这个输入框上禁用水波效果。

下面我们就来介绍如何禁用控件的水波效果:

创建两个类,进行主题(Theme)的自定义:

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
import 'package:flutter/material.dart';

class NoSplashFactory extends InteractiveInkFeatureFactory {
const NoSplashFactory();

@override
InteractiveInkFeature create(
{MaterialInkController controller,
RenderBox referenceBox,
Offset position,
Color color,
TextDirection textDirection,
bool containedInkWell = false,
rectCallback,
BorderRadius borderRadius,
ShapeBorder customBorder,
double radius,
onRemoved}) {
return new NoSplash(
controller: controller,
referenceBox: referenceBox,
);
}
}

class NoSplash extends InteractiveInkFeature {
NoSplash({
@required MaterialInkController controller,
@required RenderBox referenceBox,
}) : assert(controller != null),
assert(referenceBox != null),
super(
controller: controller,
referenceBox: referenceBox,
);

@override
void paintFeature(Canvas canvas, Matrix4 transform) {}
}

在需要禁用水波效果的控件上使用:

1
2
3
4
5
6
7
8
...
child: Theme(
data: ThemeData(splashFactory: const NoSplashFactory()),
child: TextField(
...
),
),
...

这也许仅是Flutter目前版本(1.2.1)的一个Bug,也许将来Flutter团队会修复这个问题。但是本篇文章也为我们自定义主题(Theme)提供了一种思路。

本文原创地址为:https://www.examplecode.cn/2019/03/28/flutter-disable-ripple/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

出现这种情况是因为在等待其他的命令执行完毕。有时其他命令的执行会卡住,所以最简单的方式是杀死其他任务:

1
killall -9 dart

本文原创地址为:https://www.examplecode.cn/2019/03/27/flutter-waiting-startup-lock/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

本文我们来记录Flutter开发的过程中如何对字符串进行MD5运算。

我们需要使用一个名叫crypto的插件。

添加依赖

要使用这个插件,首先在工程的pubspec.yaml文件中加入对这个插件的依赖:

1
2
dependencies:
crypto: ^2.0.6

下载插件

1
flutter packages get

MD5运算

1
2
3
4
5
6
7
8
9
10
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart' as crypto;

String generateMd5(String data) {
var content = new Utf8Encoder().convert(data);
var md5 = crypto.md5;
var digest = md5.convert(content);
return hex.encode(digest.bytes);
}

总结

MD5是一种常用的散列算法,现在很多人认为它是一种加密算法。事实上它并不是加密算法。但是在我们实现加密算法的过程中,经常使用到MD5。

本文原创地址为:https://www.examplecode.cn/2019/03/26/flutter-md5/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!

我们新建一个Flutter工程后,如果不进行任何设置,在iOS运行时默认的状态栏时暗色的。

但是有时我们根据UI设计风格的不同,需要将其修改为亮色风格,下面的代码可以将状态栏文字图标等信息修改成白色:

1
2
3
4
5
void main() {
//这一句将状态更改为亮色
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
runApp(MyApp());
}

本文原创地址为:https://www.examplecode.cn/2019/03/25/flutter-light-style/
转载请注明出处。

我的博客中关于Flutter的所有文章可以点击这里找到,欢迎关注!