[読書会]アセットと画像の追加

目次 index【閲覧時間3分】 click !!>>

1.概要

 アセットと画像の追加について触れています。

1-1.アセットとは何か

 アセットとは、アプリのコード以外のリソースやファイルのことです。
アセットはアプリと一緒にバンドルされてデプロイされ、実行時にアクセスできるものです。具体的には次のような種類があります。

  • 静的データ: JSONファイル等
  • 設定ファイル: アプリの動作を設定する為のファイル
  • アイコン: アプリの外観に使用される画像
  • 画像ファイル: JPEG、PNG、GIF形式画像等

1-2.Flutter での画像サポート

 Flutterは様々な画像形式に対応しています。これには次のフォーマットが含まれます。

  • 静的な画像フォーマット: JPEG、PNG、GIF、BMP、WBMP
  • アニメーションをサポートする形式: WebP、アニメーションGIF、アニメーションWebP

1-3.Flutter でのアセット管理の重要性

 アセットをFlutterアプリに組み込むことで、アプリのリソースにアクセスして、必要なときに画像やデータを表示したり、利用することができます。特に画像やアイコンはアプリのUIにおいて非常に重要な役割を果たします。

 具体的にアセットをアプリに追加する方法や設定の詳細は、pubspec.yaml ファイルにアセットを宣言する必要があり、そこから画像やその他のリソースをアプリ内で使用することが可能です。

2.アセットの指定

 Flutterでは、アプリで必要なアセット(画像やファイル)pubspec.yaml ファイル指定します
このファイルはプロジェクトのルートディレクトリにあります。

(1)指定方法

  1. pubspec.yamlファイル内で、flutterセクションassetsというキーを追加し、その下にアセットのパスをリストします。
  2. 各アセットのパスは相対パスで指定し、プロジェクト内のディレクトリ構造に応じて、ファイルやフォルダを指定します。
    (但し、指定ディレクトリ直下のみ有効で、サブディレクト配下は無効です)
(pubspec.yaml)flutterセクションにassetsというキーを追加し、その下にアセットのパスをリストする(例):
flutter:
  assets:
    //相対パスで指定します
    - assets/my_icon.png
    - assets/background.png

(2)ディレクトリ全体を指定

 ディレクトリ全体のアセット一括で指定したい場合は、ディレクトリ名の最後にスラッシュ / を付けて指定します。

 以下の例では、directory/ および directory/subdirectory/ 内の全てのファイル一括してアセットとして指定されています。

ディレクトリ( directory/ 及び directory/subdirectory/ )全体のアセットを一括で指定する(例):
flutter:
  assets:
    //ディレクトリ名の最後にスラッシュ / を付けて指定します
    - directory/
    - directory/subdirectory/

2-1.アセットのバンドル

 Flutter では、アセットのバンドルプロセスがアプリのビルド時に行われます。
このプロセスは、アセットをアプリと一緒に配布するための重要なステップです。

(1)アセットのバンドル

  • pubspec.yaml の flutterセクション内でアセットを指定することで、そのファイルはビルド時にアプリに組み込まれます。
  • 各アセットは、pubspec.yaml ファイルから見た相対パスで指定されます。
    パスさえ正しければ、アセットは適切にバンドルされます。
  • どのような順番で宣言しても問題はありません。

(2)アセットの実際の処理

 Flutter のビルドプロセス中、指定されたアセットファイルはアセットバンドルと呼ばれる特別なアーカイブに圧縮され、アプリが実行時にアクセスできるようにされます。このアセットバンドルは、アプリの一部として配布されるので、アセットファイルはビルド後にアプリの内部で簡単に参照できるようになります。

 例えば、JSONファイルや画像ファイルなど、ユーザーが使用するリソースは、このアセットバンドル内に含まれ、実行時にプログラムでアクセス可能です。

(3)重要なポイント

  • ディレクトリ名は重要ではない
     例えば、assets/ や directory/ という名前のディレクトリを使っても、どちらでも問題なく、アセットとしてバンドルできます。名前は任意です。
  • アセットは相対パスで指定する
     ファイルの正確なパスをpubspec.yaml ファイル内に正しく記述する必要があります。

2-2.ビルド時のアセットファイルの自動変換

 Flutterでは、アプリのビルド時にアセットファイルを自動的に変換する機能をサポートしています。
つまり、Dartパッケージを使用して、ビルド時にアセットファイルを特定の方法で処理(トランスフォーム)できるのです。
このプロセスは、特定の形式のファイルを変換したり、最適化したりするために役立ちます。

(1)自動変換の仕組み

  • アセットファイルを自動的に変換するには、pubspec.yaml ファイルでアセットとトランスフォーマーパッケージを指定する必要があります。
  • これにより、ビルドプロセス中に指定されたアセットがトランスフォーマーパッケージによって変換され、アプリケーションで使用する準備が整えられます。

 例えば、画像のサイズを最適化したり、特定のデータファイルをアプリ用に加工したりすることが可能です。

(2)自作のトランスフォーマーパッケージ

 Flutterでは、独自のトランスフォーマーパッケージを作成することもできます。これにより、アプリのニーズに応じたカスタマイズされたアセット変換が可能になります。たとえば、アセットファイルをエンコード/デコードしたり、特定の画像フォーマットに変換したりするパッケージを作成することが考えられます。

 詳細については、公式ドキュメント「Transforming assets at build time」が紹介されています。

(3)利点

  • 効率的なリソース管理: アセットの最適化により、アプリのパフォーマンス向上や容量の削減に役立ちます。
  • カスタム処理: 特定のフォーマットや要件に応じて、アセットをビルド時に自動変換できるため、手動でファイルを処理する必要がなくなります。

 この機能を使えば、アセットの効率的な管理が可能となり、より高度なカスタマイズが可能になります。

2-3.アプリのフレーバーに基づくアセットの条件付きバンドル

 Flutterでは、アプリのフレーバー(ビルドバリアント)機能を活用することで、特定のアセットを選択的にバンドルすることができます。フレーバーは、アプリの異なるバージョンを作成するための仕組みで、例えば開発・テスト・プロダクションのビルド環境ごとに異なる設定やアセットを含めることができます。

(1)条件付きアセットバンドル

 条件付きアセットバンドルは、特定のアプリフレーバーにのみアセットを含める方法です。これにより、例えば、あるフレーバーには高解像度の画像をバンドルし、別のフレーバーには低解像度の画像をバンドルするといった柔軟なアセット管理が可能になります。

(1-1)フレーバーに基づくアセットの構成

  • pubspec.yaml ファイルや特定のビルド設定ファイルにおいて、フレーバーごとにアセットのバンドルを設定します。
  • これにより、アプリがビルドされる際に、選択されたフレーバーに応じて異なるアセットがパッケージされ、最終的なアプリに含まれます。

(1-2)具体例

  • 開発フレーバー: 開発時に使いやすいデバッグ用のアイコンや、軽量な画像ファイルを含める。
  • プロダクションフレーバー: 高解像度の画像や最適化されたアセットを含める。

(2)利点

  1. サイズ最適化: 不要なアセットを除外することで、アプリのサイズを最小限に抑えられます。
  2. パフォーマンス向上: 環境に最適化されたアセットを使用することで、読み込み時間やメモリ消費の最適化が可能になります。
  3. 開発プロセスの効率化: フレーバーごとに異なるアセットを柔軟に管理することで、異なる環境に合わせたアプリケーションを容易に開発できます。

 この機能を利用して、フレーバーに基づくアセットバンドルの詳細な手順や設定方法については、Flutterの公式ドキュメント「Conditionally bundling assets based on flavor」をご確認下さい。

3.アセットの読込

 Flutterアプリでは、アセットをロードする為に AssetBundle オブジェクトを使用します。

(1)AssetBundle とは?

 AssetBundle は、アプリのアセットにアクセスする為のオブジェクトです。
アプリにバンドルされた静的ファイル(画像、テキスト、JSONなど)を効率的にロードするための仕組みを提供します。Flutterはアプリのビルド時に、pubspec.yaml ファイルに記載されたアセットをバンドルします。これらのアセットは、アプリ実行中に AssetBundle 経由でアクセスできます。

(2)主要メソッド

 AssetBundle には主に2つ( ① と ② )のメソッドがあります。

① loadString() テキスト形式の読込用

 (テキストファイルやJSONファイル等の)文字列をロードする為に使われます。
このメソッドは非同期で動作し、指定されたアセットを読み込み、その内容を文字列として返します

loadString() によるJSONファイルの文字列を読込し文字列として返す(例):
String data = await rootBundle.loadString('assets/data.json');

② load() バイナリ形式の読込用

 (画像やバイナリファイル等を)バイナリ形式でロードする為に使用されます。
このメソッドは指定されたパスにあるアセットをバイナリ形式で取得します。主に画像データなど、非テキストファイルに使用されます

load() による画像を読込しバイナリとして返す(例):
ByteData imageData = await rootBundle.load('assets/images/my_image.png');

(3)ロジカルキーとパス

 pubspec.yaml で指定されたアセットのパスは、ロジカルキーと呼ばれます。
このロジカルキーは、実際のファイルシステム上のパスと対応しており、Flutterはこのキーを使ってアセットをロードします。

(4)利用例

 以下は、loadString() load() を使って各々テキストファイル画像ファイルロードする例です。

① テキストファイルの読み込み

loadString() によるテキストファイルの読込(例):
import 'package:flutter/services.dart';

Future<void> loadAsset() async {
  String data = await rootBundle.loadString('assets/config.json');
  print(data);
}

② 画像ファイルの読み込み

Dart
import 'package:flutter/services.dart';
import 'dart:typed_data';

Future<void> loadImage() async {
  ByteData imageData = await rootBundle.load('assets/images/picture.png');
  Uint8List bytes = imageData.buffer.asUint8List();
  //この bytes を使って画像表示などが可能です
}

(5)アセットを利用する際の注意点

 アセットを使用する前に、必ず pubspec.yaml ファイルに正しくアセットを宣言し、それがビルド時にアプリに含まれるようにする必要があります。ファイルパスの誤りや宣言漏れがあると、アセットがロードされずエラーが発生します。

 Flutterのアセット管理に関する詳細は、公式ドキュメントの assets and images で確認できます。

3-2.テキスト(上記内容への補足)

 上記内容への補足になります。

(1)アセットの読み込み方法

  rootBundle オブジェクト(アプリのアセットに簡単にアクセスできるグローバルオブジェクト )を使うと、アプリにバンドルされたテキストアセット(たとえばJSONファイル)を簡単にロードできます

 rootBundle は、package:flutter/services.dart からインポートされるグローバルな静的オブジェクトです。
これを使用すると、アセットファイルを非同期でロードすることが可能です。
例えば、次のコードでJSONファイルを文字列として読み込みます。

Dart
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/config.json');
}

 上記のコードでは、assets/config.json ファイルをアプリのビルド時にバンドルし、それを loadString() メソッドで非同期に読み込んでいます。このメソッドは、アセットの内容を String として返します。

(補足)import 文における show キーワード について

 show は Dart の import 文におけるキーワードの一つです。このキーワードを使うことで、指定されたパッケージやライブラリから特定の要素(クラス、関数、定数など)だけをインポーすることができます。

例えば、import ‘package:flutter/services.dart’ show rootBundle; の場合、flutter/services.dart パッケージから rootBundle だけをインポートすることを意味しています。この場合、services.dart パッケージに含まれる他の要素(たとえば SystemNavigator など)はインポートされません。

show の使いどころ:
特定のシンボルだけをインポートしたい場合に便利です。不要なものまでインポートしないことで、名前の衝突を防いだり、コードの可読性を高めたりできます。

 これとは逆に、いくつかの要素をインポートしたくない場合は hide キーワードを使います

(2)DefaultAssetBundle の使用

 通常、rootBundle を使うこともできますが、より柔軟な方法として DefaultAssetBundle を使用することが推奨されています。これは、アプリがコンテキストに応じて異なるアセットバンドルをロードする場合に有用です。

DefaultAssetBundle を使うと、親ウィジェットが異なるアセットバンドルを提供できるため、例えばローカライズテストシナリオで便利です。

DefaultAssetBundle.of(context) を使って、現在の BuildContext に対応する AssetBundle を取得し、アセットを間接的にロードできます。これにより、アプリのバンドルに依存せず、異なるアセットバンドル読み込むことが可能になります。

 例として、JSONファイルを読み込むコードは次の様になります。

Dart
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: DefaultAssetBundle.of(context).loadString('assets/config.json'),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasError) {
            return Text('Error: ${snapshot.error}');
          } else {
            // JSONデータをパース
            final data = json.decode(snapshot.data!);
            return Text('Loaded JSON: $data');
          }
        } else {
          return CircularProgressIndicator();
        }
      },
    );
  }
}

 このコードは DefaultAssetBundle.of(context) を使用して、アプリの実行時にバンドルされたJSONファイルをロードし、その内容を FutureBuilder で非同期に処理しています。

(3)いつ rootBundle を使用するか?

  • ウィジェットのコンテキスト外の(∴ BuildContext が利用できない)場合は、rootBundle で直接アセットをロードします。
  • 例えば、非ウィジェット部分ビジネスロジックバックグラウンド処理のコードでアセットにアクセスする場合には、rootBundle が便利です。

 一方で、アプリ内の特定のウィジェットで、ローカライズテスト等の為に異なるアセットバンドルを使用したい場合は、DefaultAssetBundle を使うのが適しています。

 このように、rootBundle DefaultAssetBundle は、各々の用途に応じて使い分けることができます。

3-3. 画像

 画像をアセットとして読み込む場合AssetImage クラスを使用します。これは、build() メソッドの中でウィジェットとして指定できます。具体的には、Image ウィジェットとともに使用され、指定したアセットファイル(画像ファイル)をアプリのUIに表示するために使われます。

アプリに背景画像を表示する(例):
return const Image(image: AssetImage('assets/background.png'));

 上記例では、 assets/background.png という画像を読み込み、Image ウィジェットとして表示します
AssetImage は、pubspec.yaml に宣言済みのアセットファイルへのパスを指定し、画像をアプリに組み込み、使用可能にします。

(ポイント)

  • AssetImage クラスは、アプリで画像を読み込む為のクラスです。
  • Image ウィジェットを使って画像を表示します。
  • アセットパス(assets/background.png など)は、アプリの pubspec.yaml で事前に宣言しておく必要があります。

(1)解像度を考慮した画像アセット

 デバイスのピクセル比率に応じた解像度(Resolution-aware)に適した画像を読み込むことができます。
この機能により、デバイスの画面解像度に合わせた画像を自動的に選択し、最適な表示品質を提供します。

AssetImage の動作
 AssetImage は、指定された論理的な画像アセットを現在のデバイスピクセル比率最も近い解像度画像にマッピングします。(例えば、あるデバイスで画面解像度が非常に高い場合、高解像度の画像が表示されます。)

ディレクトリ構造
 画像の解像度に応じた画像アセットを正しく読み込む為には、画像ファイルを特定のディレクトリ構造に従い配置する必要があります。

ディレクトリ構造(例):
.../image.png
.../Mx/image.png
.../Nx/image.png

 上記例では、M や N は、対応する画像が意図されているデバイスピクセル比率DPR: Device Pixel Ratio)を表す数値です。これにより、異なる解像度適した画像動的に選択します

アセットの解像度ごとの例
 以下の例では、my_icon.png という画像を、複数の解像度対応するアセットとして用意しています。

my_icon.png という画像を、複数の解像度に対応するアセットとして用意している(例):
.../my_icon.png       (mdpi 基準: 1.0x)  // 1 倍
.../1.5x/my_icon.png  (hdpi: 1.5x)  // 1.5 倍
.../2.0x/my_icon.png  (xhdpi: 2.0x)  // 2.0 倍
.../3.0x/my_icon.png  (xxhdpi: 3.0x)  // 3.0 倍
.../4.0x/my_icon.png  (xxxhdpi: 4.0x)  // 4.0 倍

(例)デバイスのピクセル比率が 1.8 の場合、…/2.0x/my_icon.png が使用されます。
デバイスのピクセル比率が 2.7 の場合には、…/3.0x/my_icon.png が使用されます。

レンダリング時のスケーリング

 もし Image ウィジェットに対して画像の幅と高さが指定されていない場合、アセットの解像度に基づいて画像を自動的にスケーリングし、メインアセット同じ画面スペースを占めるように調整されます。
(例えば、もし my_icon.png の元の解像度が 72px × 72px であれば、…/3.0x/my_icon.png の解像度は 216px × 216px ですが、論理ピクセルではどちらも同じ 72px × 72px の領域に表示されます。)

 つまり、my_icon.png が72pxの解像度で作成された画像だとしても、高解像度のデバイスでは3倍の解像度(216px)を持つ画像が使われます。しかし、最終的に表示される見た目の大きさ(論理ピクセル)は、低解像度でも高解像度でも同じ72pxになります

論理ピクセルと物理ピクセル

 論理ピクセルは、デバイスに関係なく一貫したサイズとして扱われます。たとえば、72pxの論理ピクセルは、低解像度のディスプレイでも高解像度のディスプレイでも、物理的に同じ大きさで表示されます。

つまり、高解像度のデバイスでは、216px × 216pxの画像が使用されますが、Flutterはそれを自動的に縮小し、論理ピクセルでは72px × 72pxとして表示します。そのため、どのデバイスでも画像の見た目の大きさは同じになりますが、高解像度デバイスではよりシャープで鮮明な画像が得られます。

Device Pixel Ratio(デバイスピクセル比)の MediaQueryData.size への依存について

  • Device Pixel Ratio(デバイスピクセル比)
     デバイスのディスプレイにおける論理ピクセル物理ピクセル比率
    (例えば、Retinaディスプレイのように解像度が高いデバイスでは、デバイスピクセル比が1を超えることがあります。
    これにより、より高解像度の画像が必要になります。)
  • MediaQueryData.size
     MediaQuery ウィジェットを通じて取得されるデータで、デバイスの画面サイズ解像度の情報が含まれています。
    このデータを使ってFlutterはデバイスのピクセル比率を判断します。

 そのため、MaterialApp CupertinoApp を使用することで、この情報を正しく取得し、対応する解像度のアセット(画像など)を選択できるようにします。これがなければ、アセットの解像度に関する判断を適切に行えないことがあります。

 つまり、画像が正しい解像度で表示される為には、必ず MaterialApp CupertinoApp をルートウィジェットとして持っている必要があるということです。

 この仕組みのおかげで、異なるデバイスピクセル比率(例えば、1x、2x、3xなど)のデバイスに対して、各々適切な解像度の画像が自動的に選択されます。

① 解像度を考慮した画像アセットのバンドル

 デバイスの解像度に応じて適切な画像(例えば、1x、2x、3xなど)を自動で選択する仕組みがあり、開発者が個別にバンドルを意識する必要はありません。但し、メインアセットを指定し、各解像度に対応する画像を正しいフォルダ構造で配置する必要があります。

(a) pubspec.yamlファイルでの指定方法
 解像度に応じた画像を使う場合でも、pubspec.yamlで指定するのはメインアセット(通常の解像度の画像)またはその親ディレクトリのみで構いません。他の解像度用の画像(高解像度の画像)を自動的にバンドルします。
以下の例では、assets/images/ ディレクトリ内の高解像度画像(2xや3xのフォルダにあるもの)も含めてバンドルされます。

pubspec.yamlファイルでの指定(例):
flutter:
  assets:
    - assets/images/

(b) メインアセットが存在しない場合の処理
 メインのアセットが存在しない場合、最も解像度が低い画像デフォルトとして使用されます
(例えば、低解像度のデバイスでは、2xや3xの解像度用画像がない場合、最も解像度が低いバージョンが使われます。
但し、メインアセット自体が存在しなくても、pubspec.yamlファイルにはそのエントリが必要です。
これは、どの画像がメインアセットに該当するのかを判断する為です。)

(c) デフォルトのアセットバンドルで解像度感知を継承
 画像の読込時、特別な操作を行わなくても、デフォルトのアセットバンドルは自動的にデバイスの解像度に応じた最適な画像を選択します。低レベルのクラス(ImageStreamImageCacheなど)でも、解像度(スケール)に関連するパラメータを適切に処理します。

(2)パッケージ依存関係内の画像アセット

 パッケージ依存関係に含まれるアセット画像の読み込み方法について触れています。

(a) パッケージ依存関係から画像を読み込む
 アプリが他のパッケージ(例えばmy_iconsというパッケージ)に依存している場合、そのパッケージ内の画像を使用したいときには、AssetImageにpackage引数を指定する必要があります。これにより、Flutterは指定されたパッケージ内のアセットを正しく探して読み込むことができます。

例えば、my_iconsというパッケージが次の様なフォルダ構造を持っているとします:
.../pubspec.yaml
.../icons/heart.png
.../icons/1.5x/heart.png
.../icons/2.0x/heart.png
この場合、AssetImageを使って画像を読み込むには以下のように指定します:
return const AssetImage('icons/heart.png', package: 'my_icons');

(b) パッケージ自身がアセットを使用する場合もpackage引数を使う
 もしそのパッケージ自身(my_icons)がアセットを利用する場合も、同様にpackage引数を指定する必要があります。
これは、アプリの中に配置された他のアセットと、パッケージ内のアセットが競合することを防ぐ為です。
 package引数は、Flutterがアセットをどのパッケージから取得するべきかを明示的に指示するためのものです。これにより、複数のパッケージに同じ名前のアセットが含まれている場合にも、正しいアセットを使用できます。

② パッケージアセットのバンドル

 ここでは、パッケージに含まれるアセットのバンドリング方法について触れています。
主に、アプリが外部パッケージ(例えば fancy_backgrounds というパッケージ)に含まれるアセットをどのようにバンドルし、利用するかを説明しています。

(a) パッケージに指定されたアセットの自動バンドリング
 パッケージの pubspec.yaml ファイルにアセットが指定されている場合、これらのアセットは自動的にアプリにバンドルされます。特に、パッケージ自体が使用するアセットは、そのパッケージの pubspec.yaml ファイルで明示的に定義されている必要があります。

(b) lib/フォルダ内のアセット
 パッケージは lib/ フォルダにアセットを配置することができますが、その場合でも、アセットが pubspec.yaml に指定されていなければ、これらのファイルは自動的にはバンドルされません。アプリがこれらのアセットを利用するには、アプリ側の pubspec.yaml ファイルでそれらを明示的に指定する必要があります。

 例えば、fancy_backgrounds というパッケージが次のようなディレクトリ構造を持っているとします。

例えば、fancy_backgrounds というパッケージが次の様なディレクトリ構造を持っているとします:
.../lib/backgrounds/background1.png
.../lib/backgrounds/background2.png
.../lib/backgrounds/background3.png
この場合、アプリが background1.png を使用するためには、そのアプリケーションの pubspec.yaml に次のように記載します:
flutter:
  assets:
    - packages/fancy_backgrounds/backgrounds/background1.png

 上記では、lib/ の指定は不要 です。
pubspec.yaml ファイルの記述では、lib/ は暗黙的に含まれている為、アセットパスに含めるべきではありません。

(c) パッケージ開発者がアセットを利用する場合
 パッケージ開発者が自分のパッケージ内でアセットを使用したい場合、アセットはそのパッケージの pubspec.yaml ファイルで指定される必要があります。

assets/images/ ディレクトリにある画像を指定する為の指定:
flutter:
  assets:
    - assets/images/
上記のパッケージ内で画像の読込には、次の様に AssetImage を使用します:
return const AssetImage('packages/fancy_backgrounds/backgrounds/background1.png');
  • アセットのバンドリング
     アプリに含まれる画像やリソースファイルをパッケージ化して一緒に配布することを指します。
    このプロセスにより、アプリが実行されるときに必要なアセットにアクセスできます。
  • パッケージ内のアセットは、アプリとは別のパッケージに含まれている場合でも、適切に指定することで、アプリ内で簡単に利用可能です。

 上記の仕組みを使用すると、複数のパッケージに依存するアプリでも、必要なアセットを効率的に管理できます。

4.基盤プラットフォームにおけるアセットの共有

 アセットは、AndroidではAssetManagerを、iOSではNSBundleを使って、プラットフォームコードから利用できます。

4-1.Android における Flutter アセットの読込

 Android アプリケーションで Flutter のアセットにアクセスする方法について触れています。

(1)Android でのアセットアクセス

 Android アプリでは、アセットにアクセスするために、Android の AssetManager API を使用します。
この API を通じて、Flutter プロジェクトに含まれているアセットファイル(画像、JSONファイルなど)を取得できます。

(2)lookupKeyForAsset メソッドの使用

 アセットへのアクセスに使用されるキー(ファイルパス)は、lookupKeyForAsset というメソッドで取得します。

  • PluginRegistry.Registrar
     プラグインを開発している場合、この Registrar を使用してアセットキーを取得します。
  • FlutterView
     アプリで Platform View を使っている場合は、この FlutterView を使ってキーを取得します。
例えば、pubspec.yaml ファイルで次のようにアセットを指定しているとします:
flutter:
  assets:
    - icons/heart.png
この場合、アセットのディレクトリ構造は次のようになります:
.../pubspec.yaml
.../icons/heart.png
Java プラグインコードでこの icons/heart.png にアクセスするには、次のようにコードを書きます:
//AssetManager を取得:
AssetManager assetManager = registrar.context().getAssets();
//アセットキーを取得:
String key = registrar.lookupKeyForAsset("icons/heart.png");
//アセットファイルを開く:
AssetFileDescriptor fd = assetManager.openFd(key);

(3)AssetManager API

 Android の AssetManager は、アセットファイルにアクセスするためのクラスです。
openFd() メソッドを使って、指定されたアセットファイルを開くことができます。

(4)まとめ

 この手法により、Flutter のアセットファイルをネイティブの Android コードから読み込むことが可能になります。
特に、プラグイン開発やプラットフォームビューを含むアプリケーションで、ネイティブコードから Flutter のアセットを利用する必要がある場合に役立ちます。

 ネイティブコードで Flutter アセットを扱うことは、例えばカスタムプラットフォームウィジェットを作成したり、Flutter とネイティブ Android 間でリソースを共有する際に有用です。

4-2.iOS における Flutter アセットの読込

 ここでは、iOS アプリで アセットにアクセスする方法について説明しています。
具体的には、ネイティブコード(Objective-C または Swift)から、Flutter プロジェクトに含まれるアセットファイル(画像など)を読み込む方法に関する内容です。

(1)iOS でのアセットアクセス

 iOS では、Flutter のアセットファイルは mainBundle を通じてアクセスできます。NSBundle.mainBundle を使ってリソースのパスを取得し、ファイルを参照します。Android の AssetManager に相当する役割を担っています。

(2)lookupKeyForAsset メソッド

  • アセットファイルにアクセスするには、lookupKeyForAsset というメソッドを使用してアセットキーを取得します。
    このメソッドは、FlutterPluginRegistrar または FlutterViewController から呼び出され、指定したアセットに対応するキーを返します。これにより、Flutter のアセットファイルにアクセス可能になります。
  • FlutterPluginRegistrar は、プラグイン開発時使用します。
  • FlutterViewController は、Platform View 使用しているアプリで使用します。

(3)Objective-C でのアセットアクセス方法

 以下の例では、icons/heart.png というアセットファイルにアクセスしています。
lookupKeyForAsset メソッドを使ってアセットキーを取得し、pathForResource:ofType: メソッドを使ってそのキーに対応するファイルパスを取得します。

Objective-C でのアセットアクセス方法(例):
NSString* key = [registrar lookupKeyForAsset:@"icons/heart.png"];
NSString* path = [[NSBundle mainBundle] pathForResource:key ofType:nil];

(4)Swift でのアセットアクセス方法

 以下の例では、Swift でも同様に、lookupKeyForAsset メソッドを使ってアセットキーを取得し、Bundle.main.path(forResource:ofType:) を使ってファイルパスを取得します。

Swift でも同様に、lookupKeyForAsset メソッドを使ってアセットキーを取得し、Bundle.main.path(forResource:ofType:) を使ってファイルパスを取得する(例):
let key = controller.lookupKey(forAsset: "icons/heart.png")
let mainBundle = Bundle.main
let path = mainBundle.path(forResource: key, ofType: nil)

(5)ios_platform_images プラグイン

 ios_platform_images というプラグインは、このアセットアクセス処理を簡単にするための便利なラッパーを提供しています。このプラグインを使うと、アセットファイルを以下のように簡単に読み込むことができます。

Objective-C:
[UIImage flutterImageWithName:@"icons/heart.png"];
Swift:
UIImage.flutterImageNamed("icons/heart.png")

 この説明は、ネイティブコードから Flutter アセットにアクセスする際の具体的なコード例を示しています。特に iOS 向けのプラグイン開発や、Flutter プロジェクトでネイティブコードを組み込んだアプリケーションを開発する際に役立ちます。

 iOS プラットフォームでは、アセットファイルは通常の iOS アプリのリソースと同様に NSBundle.mainBundle で管理されており、Flutter でバンドルされたアセットも同じ方法で取得できるということです。また、lookupKeyForAsset メソッドを使用することで、Flutter のアセットをネイティブ iOS コードから簡単にアクセスできるようになります。

(6)Flutter における iOS 画像の読込

 既存のiOSアプリに追加してFlutterを実装する場合、iOSでホストされている画像をFlutterで使いたいかもしれない。それを実現するには、pub.devで利用可能なios_platform_imagesプラグインを使います。

5.プラットフォームアセット

 プラットフォームプロジェクトのアセットを直接扱う場面は他にもあります。
以下の2つのケースは、Flutterフレームワークがロードされ実行される前にアセットが使われます。

5-1.アプリのアイコンの更新

 Flutterアプリケーションの起動アイコンの更新は、ネイティブのAndroidやiOSアプリケーションの起動アイコンの更新と同じように動作します。

(1)Android

 Flutterプロジェクトのルートディレクトリで、…/android/app/src/main/resに移動します。
mipmap-hdpiなどのさまざまなビットマップ・リソース・フォルダには、すでにic_launcher.pngという名前のプレースホルダ画像が含まれています。Android Developer Guideで示されているように、画面密度ごとの推奨アイコンサイズを尊重して、希望のアセットに置き換えて下さい。

注意
 .pngファイルの名前を変更した場合は、AndroidManifest.xml のタグの android:icon 属性 でも対応する名前を更新する必要があります。

(2)iOS

 Flutterプロジェクトのルートディレクトリで、…/ios/Runnerに移動します。Assets.xcassets/AppIcon.appiconsetディレクトリには、すでにプレースホルダ画像が含まれています。アップル・ヒューマン・インターフェイス・ガイドラインで規定されているファイル名に従って、適切なサイズの画像に置き換えてください。 ファイル名はオリジナルのままにして下さい。

5-2.起動画面の更新

 Flutterはネイティブプラットフォームのメカニズムも使って、Flutterフレームワークがロードしている間にFlutterアプリにトランジショナルな起動画面を描画します。この起動画面はFlutterがアプリケーションの最初のフレームをレンダリングするまで持続します。

注意:
 つまり、アプリのmain()関数内でrunApp()を呼ばなければ
(もっと具体的に言えば、PlatformDispatcher.onDrawFrameに反応してFlutterView.render()を呼ばなければ)、
起動画面は永遠に続きます。

(1)Android

 Flutterアプリに起動画面(「スプラッシュ画面」とも呼ばれる)を追加するには、…/android/app/src/mainに移動します。 res/drawable/launch_background.xmlで、この layer list drawable XMLを使って起動画面の見た目をカスタマイズします。既存のテンプレートでは、コメント付きコードで白いスプラッシュ・スクリーンの中央に画像を追加する例を提供しています。コメントアウトを解除するか、他の drawable を使って意図した効果を得ることができる。

 詳細については、「Androidアプリにスプラッシュ画面を追加する」を参照して下さい。

(2)iOS

 スプラッシュ・スクリーン」の中央に画像を追加するには、…/ios/Runnerに移動します。 Assets.xcassets/LaunchImage.imagesetにLaunchImage.png、LaunchImage@2x.png、LaunchImage@3x.png。
異なるファイル名を使用する場合は、同じディレクトリのContents.jsonファイルを更新して下さい。

 また、…/ios/Runner.xcworkspaceを開いて、Xcodeで起動画面のストーリーボードを完全にカスタマイズすることもできます。プロジェクトナビゲータでRunner/Runnerに移動し、Assets.xcassetsを開いて画像をドロップするか、LaunchScreen.storyboardのInterface Builderを使ってカスタマイズして下さい。

 詳細については、iOSアプリにスプラッシュ画面を追加するを参照して下さい。

コメントを残す