ネットワーク画像の読み込みが遅い、エラーが発生するといった問題はありませんか? そんなときに役立つのが multi_network_image
パッケージです。複数の画像URLを指定して、最適な画像を自動選択してくれる便利なパッケージです。
1. multi_network_imageとは?
multi_network_image
は、アダプティブ画像を簡単に扱うことができるパッケージです。複数の画像URLを提供し、キャッシュされた画像があればそれを優先表示し、メイン画像の読み込みに失敗した場合は自動的にフォールバック画像に切り替えます。
主な特徴:
- 複数のURL指定による自動フォールバック機能
- キャッシュされた画像の優先表示
- 低品質画像をプレースホルダーとして活用
- 高品質・低品質画像の使い分けが可能
2. インストール方法
pubspec.yaml
に以下を追加します:
dependencies:
multi_network_image: ^0.1.3
インストール後に実行:
flutter pub get
3. 基本的な使い方
MultiNetworkImageをImageのsourceとして使用します:
import 'package:flutter/material.dart';
import 'package:multi_network_image/multi_network_image.dart';
class ImageDisplayWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Image(
image: MultiNetworkImage([
// メイン画像(高品質)
'https://picsum.photos/2000',
// フォールバック画像(中品質)
'https://picsum.photos/200',
// プレースホルダー画像(低品質)
'https://picsum.photos/20',
]),
fit: BoxFit.cover,
width: 300,
height: 200,
);
}
}
4. 動作の仕組み
MultiNetworkImageは、リストで提供され、キャッシュされている最初の画像を初期ソースとして使用します。その後、リストの最初の画像の取得を試み、失敗した場合は次の画像を試します。
graph TD
A[MultiNetworkImage開始] --> B{キャッシュ画像あり?}
B -->|Yes| C[キャッシュ画像を表示]
B -->|No| D[1番目のURL取得試行]
D --> E{取得成功?}
E -->|Yes| F[高品質画像を表示]
E -->|No| G[2番目のURL取得試行]
G --> H{取得成功?}
H -->|Yes| I[フォールバック画像を表示]
H -->|No| J[3番目のURL取得試行]
5. 実用的な使用例
プロフィール画像での活用
class ProfileImageWidget extends StatelessWidget {
final String userId;
ProfileImageWidget({required this.userId});
@override
Widget build(BuildContext context) {
return Container(
width: 80,
height: 80,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.grey.shade300, width: 2),
),
child: ClipOval(
child: Image(
image: MultiNetworkImage([
// 高解像度のプロフィール画像
'https://api.example.com/users/$userId/profile/high',
// 標準解像度のプロフィール画像
'https://api.example.com/users/$userId/profile/medium',
// デフォルトのアバター画像
'https://api.example.com/default-avatar.png',
]),
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(Icons.person, size: 40, color: Colors.grey);
},
),
),
);
}
}
ギャラリー表示での活用
class GalleryImageWidget extends StatelessWidget {
final String imageId;
GalleryImageWidget({required this.imageId});
@override
Widget build(BuildContext context) {
return Card(
child: Column(
children: [
Expanded(
child: Image(
image: MultiNetworkImage([
// 4K高画質画像
'https://cdn.example.com/images/$imageId/4k.jpg',
// HD画質画像
'https://cdn.example.com/images/$imageId/hd.jpg',
// サムネイル画像
'https://cdn.example.com/images/$imageId/thumb.jpg',
]),
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
),
),
Padding(
padding: EdgeInsets.all(8.0),
child: Text('画像 $imageId'),
),
],
),
);
}
}
6. 異なる解像度での使い分け
class ResponsiveImageWidget extends StatelessWidget {
final String baseUrl;
ResponsiveImageWidget({required this.baseUrl});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
// 画面サイズに応じて適切な解像度を選択
List<String> imageUrls = [];
if (constraints.maxWidth > 800) {
// デスクトップ: 高解像度
imageUrls.addAll([
'$baseUrl@3x.jpg', // 高解像度
'$baseUrl@2x.jpg', // 中解像度
'$baseUrl@1x.jpg', // 標準解像度
]);
} else if (constraints.maxWidth > 400) {
// タブレット: 中解像度
imageUrls.addAll([
'$baseUrl@2x.jpg', // 中解像度
'$baseUrl@1x.jpg', // 標準解像度
'$baseUrl@0.5x.jpg', // 低解像度
]);
} else {
// モバイル: 標準解像度
imageUrls.addAll([
'$baseUrl@1x.jpg', // 標準解像度
'$baseUrl@0.5x.jpg', // 低解像度
'$baseUrl@0.25x.jpg', // 超低解像度
]);
}
return Image(
image: MultiNetworkImage(imageUrls),
fit: BoxFit.cover,
);
},
);
}
}
7. エラーハンドリングとの組み合わせ
class RobustImageWidget extends StatelessWidget {
final List<String> imageUrls;
final Widget? placeholder;
RobustImageWidget({
required this.imageUrls,
this.placeholder,
});
@override
Widget build(BuildContext context) {
return Image(
image: MultiNetworkImage(imageUrls),
fit: BoxFit.cover,
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded || frame != null) {
return child;
}
return placeholder ??
Container(
color: Colors.grey.shade200,
child: Center(
child: CircularProgressIndicator(),
),
);
},
errorBuilder: (context, error, stackTrace) {
return Container(
color: Colors.grey.shade100,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.broken_image, size: 50, color: Colors.grey),
SizedBox(height: 8),
Text('画像を読み込めませんでした',
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
}
}
8. パフォーマンス最適化のコツ
項目 | 推奨事項 | 理由 |
---|---|---|
画像サイズの順序 | 高品質 → 中品質 → 低品質 | 最高品質を優先しつつ、フォールバック確保 |
プレースホルダー | 最後に超低品質画像を配置 | 即座に表示可能な軽量画像 |
CDN活用 | 複数のCDNを使い分け | 冗長性とパフォーマンス向上 |
キャッシュ戦略 | 適切なキャッシュヘッダー設定 | 初期表示の高速化 |
9. 注意点とベストプラクティス
- Flutter 3.27.0以上が必要
- 画像URLは品質の高い順に配置することを推奨
- ネットワーク状況に応じて適切な解像度を選択
- エラーハンドリングを必ず実装する
- キャッシュを活用してパフォーマンスを向上
// ❌ 悪い例: 順序が不適切
MultiNetworkImage([
'https://example.com/low-quality.jpg', // 低品質が最初
'https://example.com/high-quality.jpg', // 高品質が後
])
// ✅ 良い例: 適切な順序
MultiNetworkImage([
'https://example.com/high-quality.jpg', // 高品質を最初
'https://example.com/medium-quality.jpg', // 中品質でフォールバック
'https://example.com/low-quality.jpg', // 低品質で確実な表示
])
まとめ
特徴 | 内容 |
---|---|
自動フォールバック | 複数URL指定で読み込み失敗時の自動切り替え |
キャッシュ最適化 | キャッシュされた画像の優先表示 |
パフォーマンス向上 | ネットワーク状況に応じた最適な画像選択 |
簡単実装 | 既存のImageウィジェットにそのまま適用可能 |
おわりに
multi_network_image
パッケージを活用することで、ユーザー体験を大幅に向上させることができます。 ネットワーク環境が不安定な場合でも、適切なフォールバック機能により画像を確実に表示し、アプリの信頼性を高めます。
参考リンク