🍿

【Flutter】image_pickerで複数画像を一覧表示させる

2025/01/10に公開

プロジェクト作成

こちらのドキュメントに従って進めました。
https://www.flutter-study.dev/introduction/about-flutter

image_picker

写真/動画の選択には「image_picker」ライブラリを利用しました。
https://pub.dev/packages/image_picker

できること(Examle)

final ImagePicker picker = ImagePicker();
// ギャラリーから画像を1つ選択
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
// カメラを起動して画像撮影
final XFile? photo = await picker.pickImage(source: ImageSource.camera);
// ギャラリーから動画を1つ選択
final XFile? galleryVideo = await picker.pickVideo(source: ImageSource.gallery);
// カメラを起動して動画撮影
final XFile? cameraVideo = await picker.pickVideo(source: ImageSource.camera);
// ギャラリーから画像を複数選択
final List<XFile> images = await picker.pickMultiImage();
// ギャラリーから画像(もしくは動画)を1つ選択
final XFile? media = await picker.pickMedia();
// ギャラリーから画像や動画を複数選択
final List<XFile> medias = await picker.pickMultipleMedia();

画像選択から一覧表示まで(pickMultipleMedia)

初期画面
初期画面
選択画面(未選択)
選択画面(未選択)
選択画面(選択中)
選択画面(選択中)
一覧表示
一覧表示

実装したコード

簡易的に実装してみました。(リファクタリングは特にしていません)
動画表示は「video_thumbnail」ライブラリを介してGridViewに表示しています。
https://pub.dev/packages/video_thumbnail

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:mime/mime.dart';
import 'package:image_picker/image_picker.dart';
import 'package:video_thumbnail/video_thumbnail.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<XFile>? _mediaFileList;

  dynamic _pickImageError;
  String? _retrieveDataError;
  final ImagePicker _picker = ImagePicker();

  Future _getVideoThumbnail(String filePath) async {
    return await VideoThumbnail.thumbnailFile(
      video: filePath,
      imageFormat: ImageFormat.JPEG,
      maxWidth:
          128, // specify the width of the thumbnail, let the height auto-scaled to keep the source aspect ratio
      quality: 75,
    );
  }

  // 画像をギャラリーから選ぶ関数
  Future pickImage() async {
    try {
      final List<XFile> pickedFileList = await _picker.pickMultipleMedia();
      setState(() {
        _mediaFileList = pickedFileList;
      });
    } catch (e) {
      setState(() {
        _pickImageError = e;
      });
    }
  }

  Widget _previewVideo() {
    final Text? retrieveError = _getRetrieveErrorWidget();
    if (retrieveError != null) {
      return retrieveError;
    }
    return const Text(
      'You have not yet picked a video',
      textAlign: TextAlign.center,
    );
  }

  Widget _previewImages() {
    final Text? retrieveError = _getRetrieveErrorWidget();
    if (retrieveError != null) {
      return retrieveError;
    }
    if (_mediaFileList != null) {
      return GridView.count(
          crossAxisCount: 3,
          children: _mediaFileList!.map((XFile file) {
            final String? mime = lookupMimeType(file.path);
            debugPrint('Debug message: $mime');
            return (mime == null || mime.startsWith('image/'))
                ? Image.file(
                    File(file.path),
                    fit: BoxFit.cover,
                  )
                : FutureBuilder(
                    future: _getVideoThumbnail(file.path),
                    builder: (context, snapshot) {
                      if (snapshot.hasData) {
                        return Image.file(
                          File(snapshot.data),
                          fit: BoxFit.cover,
                        );
                      }
                      return const Center(child: CircularProgressIndicator());
                    },
                  );
          }).toList());
    } else if (_pickImageError != null) {
      return Text(
        'Pick image error: $_pickImageError',
        textAlign: TextAlign.center,
      );
    } else {
      return const Text(
        'You have not yet picked an image.',
        textAlign: TextAlign.center,
      );
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
          child: _previewImages()),
      floatingActionButton: FloatingActionButton(
        onPressed: pickImage,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }

  Text? _getRetrieveErrorWidget() {
    if (_retrieveDataError != null) {
      final Text result = Text(_retrieveDataError!);
      _retrieveDataError = null;
      return result;
    }
    return null;
  }
}

これからやりたいこと

端末から選択した画像や動画を Firebase storage や AWS S3 にアップロードして、
一覧表示で画像を選択した際に端末にダウンロードできるアプリを作成したい。

Discussion