Assets 管理


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('我是一隻弱小的毛毛蟲,想像有天可以成為強壯的挖土機,擁有挖掘夢想的神奇手套!')
        ],
      ),
    ),
  )
);

這會產生以下的畫面:

Assets 管理

Image.asset 其實是個建構式,會建立 Image 實例,被指定字串會用來建立 AssetImage 實例,它會根據 devicePixelRatio 特性,挑選 assets 中適當解析度的圖片,規則可參考〈Declaring resolution-aware image assets〉。

你也可以自行建立 ImageAssetImage

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,與資源載入相關的方法有 loadloadStringloadStructuredData,這些 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 方法,通知狀態已改變,這時就會重新建構,顯示載入後的訊息。