上篇傳送門:簡單項目實戰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還不會寫,因此不如用現成的輪子來的方便,不過設置壁紙這個功能尚未找到符合須要的包,只能本身先寫了。