上篇传送门:简单项目实战flutter(布局篇)html
不一样于在原生Android中手动改变控件的属性,Flutter使用State
来管理界面状态,使用setState
方法改变state
将触发页面重绘,达到变化的目的,所以只须要维护一组能够改变的值来控制widget
究竟如何显示,而无需去关注widget
们自己,如下是一些简单的例子:android
int langType = 0; // 0 中文 1 英文
Text(
today.weekday == 5
? langType == 1 ? "YES!" : "是"
: langType == 1 ? "NO" : "不是",
style:
TextStyle(fontSize: 90, color: textColor, fontFamily: fontName),
),
String fontName = "kaiTi"; // 字体名
style: TextStyle(color: textColor, fontFamily: fontName),
Color bgColor; // 背景颜色
Color bubbleColor; // 气泡颜色
Color textColor; // 文字颜色
decoration: BoxDecoration(
color: bubbleColor, // 设置气泡(文字的背景)颜色
borderRadius: BorderRadius.circular(30.0),
),
int screenType = 0; // 全屏/ 正方形
bool showControlPanel = true; // 是否显示控制面板
showControlPanel ? _buildControlPanel() : Container(),
复制代码
在须要设置控件的可见性时,查了一些实现方式看看有什么对应于Android里面的
setVisibility
,有设置透明度为0和使用Offstage
控件两种方式,可是在查阅Offstage
的源码说明时发现,官方建议Offstage
适用于测量一些不能显示的控件大小,若是单纯只是显示和隐藏永健,只须要在整个widget
树中移除这个widget
便可。恍然大悟,从这方面说flutter对于修改整个页面结构能够说很是为所欲为,和原生的思惟很不同,不能从原生直接“翻译”过来。ios
可使用如下办法来使用肯定的颜色值:bash
Color c = const Color(0xFF42A5F5);
Color c = const Color.fromARGB(0xFF, 0x42, 0xA5, 0xF5);
Color c = const Color.fromARGB(255, 66, 165, 245);
Color c = const Color.fromRGBO(66, 165, 245, 1.0);
Color c1 = const Color(0xFFFFFF); // fully transparent white (invisible)
Color c2 = const Color(0xFFFFFFFF); // fully opaque white (visible)
复制代码
由最后两行可知,传入六位颜色值是彻底看不见的,必需要在前面加上FF构成八位。 同时flutter内置了一些经常使用的颜色,用Colors.xxx
便可,同时还能够在后面跟数值来描述颜色的深浅:Colors.xxx[100]
app
flutter使用字体也很简单,将字体文件准备好,在packages.yaml
文件中配置以后就能够在Text
的style
中使用了。 配置文件的格式以下:jsp
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
复制代码
要注意缩进async
这里踩了一个坑,我起初觉得全部的资源文件都是放在
assets
文件夹下,或者在assets
文件夹里面再新建子文件夹fonts
之类,结果字体始终读取不到,才知道fonts
文件夹应该在根目录之下。ide
使用自定义字体的语法:布局
Text(
'This is a custom font text',
style: new TextStyle(fontFamily: 'MyCustomFont'),
复制代码
我是按照《Flutter实战》这本书中的国际化部分完成的,感受相比于原生只须要配置几个xml文件,仍是复杂了不少,android中<string>xxx</string>
就能表达的东西在arb文件中须要添加更多的信息,用于给专业的翻译人员参考可能会比较有用,我的感受仍是略繁琐了,若是不是用sublime批量操做我可能要专门写个脚本把xml转成arb?post
arb格式的字符串信息:
"appName": "今天是周五吗?",
"@appName": {
"description": "Title for the Friday application",
"type": "text",
"placeholders": {}
},
复制代码
具体步骤《Flutter实战》中都有,就不大段复制了,说一下这个过程当中我遇到的坑:
intl
包,没注意设置Locales
和Delegates
这一段,致使一直不成功,仔细看了好几遍示例代码才发现问题在哪里:import 'package:flutter_localizations/flutter_localizations.dart';
new MaterialApp(
localizationsDelegates: [
// 本地化的代理类
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', 'US'), // 美国英语
const Locale('zh', 'CN'), // 中文简体
//其它Locales
],
// ...
)
复制代码
${}
来表示空位处的字符串,与普通字符串的对比:// 添加字符串时:
String get titleText {
return Intl.message(
'Text',
name: 'titleText',
desc: '',
);
}
String titleMoreColor(title) => Intl.message(
'More $title Color',
name: 'titleMoreColor',
desc: '',
args: [title],
);
复制代码
生成的原始arb文件:
"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
进行截图:
RepaintBoundary(
key: screenKey,
child: ...
),
复制代码
截图时:
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui;
import 'dart:typed_data';
import 'dart:io';
RenderRepaintBoundary boundary =
screenKey.currentContext.findRenderObject(); // 获取要截图的部分
ui.Image image = await boundary.toImage(pixelRatio: 3.0); // 用toImage转为图片
ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List(); // 图片转成字节
复制代码
这里有几个须要注意的地方:
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
import 'package:path_provider/path_provider.dart';
Future<File> _getLocalFile() async {
// 获取应用目录
Directory dir =
new Directory((await getExternalStorageDirectory()).path + "/Friday");
if (!await dir.exists()) {
dir.createSync();
}
return new File('${dir.absolute.path}/screenshot_${DateTime.now()}.png');
}
复制代码
这里getExternalStorageDirectory()
获取的是sd卡根目录,我在后面加了一个表明app根目录的路径,若是没有就建立一下,接着拼接出想要的文件名就能够了。 除了getExternalStorageDirectory()
,还能够用getTemporaryDirectory()
获取临时文件夹目录。 个人需求有保存图片也有临时保存用于设置桌面壁纸和分享,因此用不一样的type去获取不一样的文件夹下的文件,而后用writeAsBytes
方法写入字节。
File file = await (type == 0 ? _getLocalFile() : _getCacheFile());
await file.writeAsBytes(pngBytes);
复制代码
固然,要保存文件不要忘了在原生代码清单文件中添加权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
复制代码
如何把assets
里面的图片保存到手机中:
// 获取本地保存的文件位置
File file = await getLocalFile(donationImgName.substring(7));
// 使用DefaultAssetBundle加载图片文件
DefaultAssetBundle.of(context)
.load(donationImgName)
.then((data) {
// 加载的字节数据(ByteData)
Uint8List pngBytes = data.buffer.asUint8List();
// 转成Uint8List而后保存到文件
file.writeAsBytes(pngBytes);
});
复制代码
和前面同样也是保存过程基本同样,因此关键是用DefaultAssetBundle
获取到图片资源文件的数据。
同字体文件同样,图片也是在根目录新建
images
文件夹,而不是什么assets
文件夹。
在用上一节的方法保存好文件以后,我有一个设置图片为壁纸的需求,这个在Flutter里面是没办法实现的,就须要和原生交互了。 首先须要定义一个channel
,和原生代码对上暗号:
import 'package:flutter/services.dart';
static const _channel = const MethodChannel('wallpaper');
await _channel.invokeMethod('setWallpaper', file.path); // 调用setWallpaper方法,文件路径做为参数传递
复制代码
在原生代码中:
val channel = "wallpaper"
MethodChannel(flutterView, channel).setMethodCallHandler { methodCall, result ->
// 判断方法名
if (methodCall.method == "setWallpaper") {
// 设置壁纸的方法封装在setWallpaper中,methodCall.arguments as String拿到路径参数
val setWallpaperResult = setWallpaper(methodCall.arguments as String)
if (setWallpaperResult == 0) {
// 成功的回调
result.success(setWallpaperResult)
} else {
// 失败的回调
result.error("UNAVAILABLE", "", null)
}
}
}
复制代码
具体怎么设置壁纸就不说了能够看代码。
分享也用了一个包,其实搜了一下用于分享的包有好几个,看示例代码选了一个比较符合需求的esys_flutter_share: ^1.0.0
:
import 'package:esys_flutter_share/esys_flutter_share.dart';
await Share.file('Friday', 'friday.png', pngBytes, 'image/png');
复制代码
其余的使用能够参考这个包的example,具体原理仍是调用原生代码,esys_flutter_share.dart
这个源码很是简单。
这个功能用到了url_launcher: ^5.0.2
,在这里作了一个判断,若是手机上有安装某黄色app,就用scheme直接打开,若是没有就跳转腾讯应用宝下载(我真贴心):
import 'package:url_launcher/url_launcher.dart';
static const jikeUrl = "jike://xxxx.xx/topic/565ac9dd4b715411006b5ecd";
static const downJikeLink =
"http://a.app.qq.com/o/simple.jsp?pkgname=com.ruguoapp.jike&ckey=CK1411402428437";
_toJike() async {
if (await canLaunch(jikeUrl)) {
await launch(jikeUrl);
} else {
await launch(downJikeLink);
}
}
复制代码
使用了shared_preferences: ^0.5.1+2
这个包,而后简单封装了几个方法:
import 'package:shared_preferences/shared_preferences.dart';
void saveString(key, value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
}
void saveInt(key, value) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt(key, value);
}
Future<String> getString(key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
Future<int> getInt(key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getInt(key);
}
// 使用时
getString(CN_FONT_NAME).then((name) {
setState(() {
fontName = null == name ? 'kaiTi' : name;
});
});
复制代码
以上几个功能用的包都是封装了和原生的交互,其实本身实现也何尝不可,可是ios还不会写,因此不如用现成的轮子来的方便,不过设置壁纸这个功能尚未找到符合须要的包,只能本身先写了。