简单项目实战flutter(功能篇)
通过State改变界面
不同于在原生Android中手动改变控件的属性,Flutter使用State来管理界面状态,使用setState
方法改变state
将触发页面重绘,达到变化的目的,因此只需要维护一组可以改变的值来控制widget
究竟如何显示,而无需去关注widget
们本身,以下是一些简单的例子:
1 | int langType = 0; // 0 中文 1 英文 |
在需要设置控件的可见性时,查了一些实现方式看看有什么对应于Android里面的
setVisibility
,有设置透明度为0和使用Offstage
控件两种方式,但是在查阅Offstage
的源码说明时发现,官方建议Offstage
适用于测量一些不能显示的控件大小,如果单纯只是显示和隐藏永健,只需要在整个widget
树中移除这个widget
即可。恍然大悟,从这方面说flutter对于修改整个页面结构可以说非常随心所欲,和原生的思维很不一样,不能从原生直接“翻译”过来。
颜色值使用
可以使用以下办法来使用确定的颜色值:
1 | Color c = const Color(0xFF42A5F5); |
由最后两行可知,传入六位颜色值是完全看不见的,必须要在前面加上FF构成八位。
同时flutter内置了一些常用的颜色,用Colors.xxx
即可,同时还可以在后面跟数值来描述颜色的深浅:Colors.xxx[100]
使用自定义字体
flutter使用字体也很简单,将字体文件准备好,在packages.yaml
文件中配置之后就可以在Text
的style
中使用了。
配置文件的格式如下:
1 | fonts: |
要注意缩进
这里踩了一个坑,我起初以为所有的资源文件都是放在
assets
文件夹下,或者在assets
文件夹里面再新建子文件夹fonts
之类,结果字体始终读取不到,才知道fonts
文件夹应该在根目录之下。
使用自定义字体的语法:
1 | Text( |
支持多语言
我是按照《Flutter实战》这本书中的国际化部分完成的,感觉相比于原生只需要配置几个xml文件,还是复杂了很多,android中<string>xxx</string>
就能表达的东西在arb文件中需要添加更多的信息,用于给专业的翻译人员参考可能会比较有用,个人感觉还是略繁琐了,如果不是用sublime批量操作我可能要专门写个脚本把xml转成arb?
arb格式的字符串信息:
1 | "appName": "今天是周五吗?", |
具体步骤《Flutter实战》中都有,就不大段复制了,说一下这个过程中我遇到的坑:
- 一开始我直接跳到第三节使用
intl
包,没注意设置Locales
和Delegates
这一段,导致一直不成功,仔细看了好几遍示例代码才发现问题在哪里:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import 'package:flutter_localizations/flutter_localizations.dart';
new MaterialApp(
localizationsDelegates: [
// 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'), // 美国英语
const Locale('zh', 'CN'), // 中文简体
//其它Locales
],
// ...
) - 使用占位符的字符串在flutter中是用
${}
来表示空位处的字符串,与普通字符串的对比:生成的原始arb文件:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 添加字符串时:
String get titleText {
return Intl.message(
'Text',
name: 'titleText',
desc: '',
);
}
String titleMoreColor(title) => Intl.message(
'More $title Color',
name: 'titleMoreColor',
desc: '',
args: [title],
);在手动翻译成中文时,我不小心把1
2
3
4
5
6
7
8
9
10
11
12
13
14"titleText": "Text",
"@titleText": {
"description": "",
"type": "text",
"placeholders": {}
},
"titleMoreColor": "More {title} Color",
"@titleMoreColor": {
"description": "",
"type": "text",
"placeholders": {
"title": {}
}
},%1$s
复制到了字符串中,而不是{}
这样的格式,于是在插入文字的时候就失败了,不过这种应该是小概率问题。
截图&保存图片
截图
在Flutter中对widget进行截图需要在widget外部套一层RepaintBoundary
,同时给它指定一个key,在截图时通过这个key拿到该RepaintBoundary
进行截图:
1 | RepaintBoundary( |
截图时:
1 | import 'package:flutter/rendering.dart'; |
这里有几个需要注意的地方:
- 此处用的
Image
是dart:ui
库中的Image
,与widget库中的Image重名,所以需要加以区分,先导入ui库:import 'dart:ui' as ui;
,然后用ui.Image
去引用,同样处理字节需要import 'dart:typed_data';
处理文件需要import 'dart:io';
- 转换为图片时的
pixelRatio
属性默认是1.0
,但是实测1.0
很糊,调到3.0
才是高清原图的样子。
保存图片
拿到字节之后,就是创建一个文件然后把字节写进去就可以了,这里为了获取系统目录,使用了一个path_provider
的库:path_provider: ^0.5.0
1 | import 'package:path_provider/path_provider.dart'; |
这里getExternalStorageDirectory()
获取的是sd卡根目录,我在后面加了一个代表app根目录的路径,如果没有就创建一下,接着拼接出想要的文件名就可以了。
除了getExternalStorageDirectory()
,还可以用getTemporaryDirectory()
获取临时文件夹目录。
我的需求有保存图片也有临时保存用于设置桌面壁纸和分享,所以用不同的type去获取不同的文件夹下的文件,然后用writeAsBytes
方法写入字节。
1 | File file = await (type == 0 ? _getLocalFile() : _getCacheFile()); |
当然,要保存文件不要忘了在原生代码清单文件中添加权限:
1 | <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
保存资源图片
如何把assets
里面的图片保存到手机中:
1 | // 获取本地保存的文件位置 |
和前面一样也是保存过程基本一样,所以关键是用DefaultAssetBundle
获取到图片资源文件的数据。
同字体文件一样,图片也是在根目录新建
images
文件夹,而不是什么assets
文件夹。
调用原生方法
在用上一节的方法保存好文件之后,我有一个设置图片为壁纸的需求,这个在Flutter里面是没办法实现的,就需要和原生交互了。
首先需要定义一个channel
,和原生代码对上暗号:
1 | import 'package:flutter/services.dart'; |
在原生代码中:
1 | val channel = "wallpaper" |
具体怎么设置壁纸就不说了可以看代码。
分享到外部
分享也用了一个包,其实搜了一下用于分享的包有好几个,看示例代码选了一个比较符合需求的esys_flutter_share: ^1.0.0
:
1 | import 'package:esys_flutter_share/esys_flutter_share.dart'; |
其他的使用可以参考这个包的example,具体原理还是调用原生代码,esys_flutter_share.dart
这个源码非常简单。
打开其他app
这个功能用到了url_launcher: ^5.0.2
,在这里做了一个判断,如果手机上有安装某黄色app,就用scheme直接打开,如果没有就跳转腾讯应用宝下载(我真贴心):
1 | import 'package:url_launcher/url_launcher.dart'; |
SharedPreferences存储
使用了shared_preferences: ^0.5.1+2
这个包,然后简单封装了几个方法:
1 | import 'package:shared_preferences/shared_preferences.dart'; |
以上几个功能用的包都是封装了和原生的交互,其实自己实现也未尝不可,但是ios还不会写,所以不如用现成的轮子来的方便,不过设置壁纸这个功能还没有找到符合需要的包,只能自己先写了。