Flutter 可以包含圖片、組態檔、JSON 檔等相關資源,除了在專案中包含資源之外,必須在 pubspec.yaml 加入 assets: 設定:
...
flutter:
uses-material-design: true
assets:
- images/caterpillar.png
路徑是相對於 pubspec.yaml,在上例中指定了 images/caterpillar.png,在打包資源時,其實會遞廻地將 images 中的每個 caterpillar.png 都包進去,也就是說,若實際上 images 資料夾中有 images/2.0x/caterpillar.png、images/3.0x/caterpillar.png 等,會全部打包進去,這表示若你想為不同解析度準備不同大小的影像時,不用在 pubspec.yaml 中逐一設定。
如果要指定整個資料夾中的資源,路徑最後以 / 結尾,例如 images/。
想載入圖片資源最簡單的方式,是透過 Image.asset,例如:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
'openhome.cc'
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Image.asset('images/caterpillar.png'),
),
Text('我是一隻弱小的毛毛蟲,想像有天可以成為強壯的挖土機,擁有挖掘夢想的神奇手套!')
],
),
),
)
);
這會產生以下的畫面:
Image.asset 其實是個建構式,會建立 Image 實例,被指定字串會用來建立 AssetImage 實例,它會根據 devicePixelRatio 特性,挑選 assets 中適當解析度的圖片,規則可參考〈Declaring resolution-aware image assets〉。
你也可以自行建立 Image、AssetImage:
import 'package:flutter/material.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
'openhome.cc'
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
// 自行建立 Image、AssetImage
child: Image(
image: AssetImage('images/caterpillar.png'),
width: 250,
height: 250,
),
),
Text('我是一隻弱小的毛毛蟲,想像有天可以成為強壯的挖土機,擁有挖掘夢想的神奇手套!')
],
),
),
)
);
AssetImage 實例會從 AssetBundle 取得圖片,真正管理資源的是 AssetBundle,與資源載入相關的方法有 load、loadString、loadStructuredData,這些 load 方法都是非同步,load 用來載入位元資源(傳回 Future<ByteData>),loadString 用來載入文字資源(傳回 Future<String>),loadStructuredData 基於 loadString,需要指定轉換函式,函式接受字串,執行後傳回結構化資料型態的實例(傳回型態 Future<T>)。
來看看 loadString 的簡單使用,要載入資源中的文字檔,同樣要在加入 assets: 設定:
...
flutter:
uses-material-design: true
assets:
- images/caterpillar.png
- messages/caterpillar.txt
每個 Flutter App 都會有個 rootBundle,若要使用,可以 import 'package:flutter/services.dart' 中的 rootBundle,例如:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
不過 rootBundle 代表著整個 App,通常會透過 DefaultAssetBundle,根據給定的 BuildContext 來載入資源,如此之來,父 Widget 會有機會在執行時期指定 AssetBundle,以載入不同的資源。
底下的範例,圖片下的文字來自於文字檔:
import 'package:flutter/material.dart';
void main() => runApp(
MyApp()
);
Future loadString(BuildContext context) async {
return await DefaultAssetBundle.of(context)
.loadString('messages/caterpillar.txt');
}
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _msg = '訊息載入中…';
@override
void initState() {
super.initState();
loadString(context).then((msg) {
setState(() {
_msg = msg;
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text(
'openhome.cc'
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Image.asset(
'images/caterpillar.png'
),
),
Text(_msg)
],
),
),
);
}
}
因為 loadString 方法是非同步地,為了能在資源載入後能通知 Flutter 框架,繼承了 StatefulWidget 來定義元件,Flutter 框架會在建立 State 實例後,呼叫 initState 進行狀態的初始,因此 MyApp 對應的狀態物件 _MyAppState 重新定義了 initState,在其中載入文字資源檔,並在載入後呼叫了 setState 方法,通知狀態已改變,這時就會重新建構,顯示載入後的訊息。

