🖼️

[Flutter] multi_image_picker_plus を使って画像の複数選択に選択制限かけたかったけど、全然使えなかった話

2024/03/01に公開

複数画像の選択制限かけたくて、調べてみたらmulti_image_picker_plusっていうパッケージがあったので使ってみました、問題もあったのでそれも記載していきます!
よろしくお願いします。

https://pub.dev/packages/multi_image_picker_plus/example

  multi_image_picker_plus: ^0.0.4
  image_picker: ^1.0.7

image_pickerプラグインのpickMultiImageメソッドでは選択制限をかけられないのか?

image_pickerpickMultiImageを使用して画像を複数選択する際に、選択できる画像の数を制限する直接的な方法は検索しても見つけられませんでした。

pickMultiImageメソッドは、ユーザーが選択できる画像の数に制限を設けず、ユーザーが選択操作を終了するまで任意の数の画像を選択できます。
しかし、選択された画像のリストを受け取った後に、アプリケーション側で制限を適用することで、 画像の選択制限を間接的に解決することが可能です。

今回作ったテストアプリ

全体コード
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:multi_image_picker_plus/multi_image_picker_plus.dart';

void main() => runApp(MaterialApp(
  home: const MyApp(),
  theme: ThemeData(
    colorSchemeSeed: Colors.indigo,
    brightness: Brightness.dark,
  ),
));

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

  
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Asset> images = <Asset>[];
  List<File> imagePicker = [];


  
  void initState() {
    super.initState();
  }

  Widget _buildMultiPickerGridView() {
    return GridView.count(
      crossAxisCount: 3,
      children: List.generate(images.length, (index) {
        Asset asset = images[index];
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 8.0),
          child: AssetThumb(
            asset: asset,
            width: 300,
            height: 300,
          ),
        );
      }),
    );
  }

  Widget _buildImagePickerGridView() {
    return GridView.count(
      crossAxisCount:3, // 3列のグリッドを作成
      children: List.generate(imagePicker.length, (index) {
        File file = imagePicker[index]; // ここで各画像ファイルへの参照を取得
        return Container(
          margin: const EdgeInsets.symmetric(horizontal: 5),
          child: Image.file(file, fit: BoxFit.cover), // 画像を表示
        );
      }),
    );
  }

  Future<List<File>> pickImagesUsingImagePicker() async {
    List<File> pickedFiles = [];
    final ImagePicker picker = ImagePicker();
    // ユーザーが複数の画像を選択できるようにします。
    var imageFiles = await picker.pickMultiImage();
    if (imageFiles.isNotEmpty) {
      // 選択された画像の数を3枚に制限します。
      int count = 0;
      for (final image in imageFiles) {
        if (count < 3) {
          pickedFiles.add(File(image.path));
          count++;
        } else {
          // 3枚を超える画像は無視します。
          break;
        }
      }
    }
    return pickedFiles;
  }


  void _loadImagesUsingImagePicker() async {
    imagePicker = await pickImagesUsingImagePicker() ;
    setState(() {});
  }


  Future<void> _loadImagesUsingMultiImagePicker() async {
    if (images.isNotEmpty) {
      images = <Asset>[]; // 初期化
    }
    final ColorScheme colorScheme = Theme.of(context).colorScheme;

    List<Asset> resultList = <Asset>[];

    const AlbumSetting albumSetting = AlbumSetting(
      fetchResults: {
        PHFetchResult(
          type: PHAssetCollectionType.smartAlbum,
          subtype: PHAssetCollectionSubtype.smartAlbumUserLibrary,
        ),
        PHFetchResult(
          type: PHAssetCollectionType.smartAlbum,
          subtype: PHAssetCollectionSubtype.smartAlbumFavorites,
        ),
        PHFetchResult(
          type: PHAssetCollectionType.album,
          subtype: PHAssetCollectionSubtype.albumRegular,
        ),
        PHFetchResult(
          type: PHAssetCollectionType.smartAlbum,
          subtype: PHAssetCollectionSubtype.smartAlbumSelfPortraits,
        ),
        PHFetchResult(
          type: PHAssetCollectionType.smartAlbum,
          subtype: PHAssetCollectionSubtype.smartAlbumPanoramas,
        ),
        PHFetchResult(
          type: PHAssetCollectionType.smartAlbum,
          subtype: PHAssetCollectionSubtype.smartAlbumVideos,
        ),
      },
    );
    const SelectionSetting selectionSetting = SelectionSetting(
      min: 0,
      max: 3,
      unselectOnReachingMax: true,
    );
    const DismissSetting dismissSetting = DismissSetting(
      enabled: true,
      allowSwipe: true,
    );
    final ThemeSetting themeSetting = ThemeSetting(
      backgroundColor: Colors.white,
      selectionFillColor: Colors.blue,
      selectionStrokeColor: Colors.white,
      previewSubtitleAttributes: const TitleAttribute(fontSize: 12.0),
      previewTitleAttributes: TitleAttribute(
        foregroundColor: colorScheme.primary,
      ),
      albumTitleAttributes: TitleAttribute(
        foregroundColor: colorScheme.primary,
      ),
    );
    const ListSetting listSetting = ListSetting(
      spacing: 5.0,
      cellsPerRow: 4,
    );
    final CupertinoSettings iosSettings = CupertinoSettings(
      fetch: const FetchSetting(album: albumSetting),
      theme: themeSetting,
      selection: selectionSetting,
      dismiss: dismissSetting,
      list: listSetting,
    );

    try {
      resultList = await MultiImagePicker.pickImages(
        selectedAssets: images,
        iosOptions: IOSOptions(
          doneButton:
          const UIBarButtonItem(title: '決定', tintColor: Colors.blue),
          cancelButton:
          UIBarButtonItem(title: 'キャンセル', tintColor: colorScheme.primary),
          albumButtonColor: colorScheme.primary,
          settings: iosSettings,
        ),
        androidOptions: AndroidOptions(
          actionBarColor: colorScheme.surface,
          actionBarTitleColor: colorScheme.onSurface,
          statusBarColor: colorScheme.surface,
          actionBarTitle: "Select Photo",
          allViewTitle: "All Photos",
          useDetailsView: false,
          selectCircleStrokeColor: colorScheme.primary,
          exceptMimeType: {MimeType.PNG, MimeType.JPEG},
        ),
      );
    } on Exception catch (e) {
      print(e);
    }

    if (resultList.isNotEmpty) {
      setState(() {
        images = resultList;
      });
    }


  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Image test app'),
      ),
      body: Column(
        children: <Widget>[
          ElevatedButton(
            onPressed: _loadImagesUsingMultiImagePicker,
            child: const Text("multi_image_picker_plus"),
          ),
          Expanded(
            child: _buildMultiPickerGridView(),
          ),
          ElevatedButton(
            onPressed: _loadImagesUsingImagePicker,
            child: const Text("Pick images"),
          ),
          Expanded(
            child: _buildImagePickerGridView(),
          ),
        ],
      ),
    );
  }
}

比較しやすいように画像の選択制限を3枚にして、image_pickerのpickMultiImageとmulti_image_picker_plusで比べてみました。

image_pickerのpickMultiImageの場合

画像の選択の上限はなく、Addを押した時に3枚だけ反映させないといけない

multi_image_picker_plusの場合

画像の選択は3枚まで、決定の横に選択されている数字が出ているので、わかりやすい

コード解説

画像の選択制限をかけたいのであれば、MultiImagePicker.pickImagesのiosOptionsを自分で設定しなきゃいけないみたいです

// この関数は、multi_image_picker_plusを使用して画像を非同期にロードします。
Future<void> _loadImagesUsingMultiImagePicker() async {
 // すでに画像が選択されている場合は、リストを空に初期化します。
 if (images.isNotEmpty) {
   images = <Asset>[]; 
 }

 // 現在のテーマから色スキームを取得します。
 final ColorScheme colorScheme = Theme.of(context).colorScheme;

 // 選択された画像のリストを初期化します。
 List<Asset> resultList = <Asset>[];

 // アルバムの設定を定義します。ここでは、特定のタイプのアルバムから画像をフェッチするための条件を指定しています。
 const AlbumSetting albumSetting = AlbumSetting(
   fetchResults: {
     PHFetchResult(
       type: PHAssetCollectionType.smartAlbum,
       subtype: PHAssetCollectionSubtype.smartAlbumUserLibrary,
     ),
     // 以下、異なるタイプのアルバムに対するフェッチ条件を追加
   },
 );

 // 選択可能な画像の最小数と最大数、および最大数に達した際の挙動を設定します。
 const SelectionSetting selectionSetting = SelectionSetting(
   min: 0,
   max: 3,
   unselectOnReachingMax: true,
 );

 // 画像ピッカーを閉じる際の設定を定義します。
 const DismissSetting dismissSetting = DismissSetting(
   enabled: true,
   allowSwipe: true,
 );

 // 画像ピッカーのテーマ設定を定義します。
 final ThemeSetting themeSetting = ThemeSetting(
   backgroundColor: Colors.white,
   selectionFillColor: Colors.blue,
   selectionStrokeColor: Colors.white,
   // プレビュータイトル、サブタイトル、アルバムタイトルのスタイルを設定
 );

 // 画像リストの表示設定を定義します。
 const ListSetting listSetting = ListSetting(
   spacing: 5.0,
   cellsPerRow: 4,
 );

 // iOS固有の設定をまとめます。
 final CupertinoSettings iosSettings = CupertinoSettings(
   fetch: const FetchSetting(album: albumSetting),
   theme: themeSetting,
   selection: selectionSetting,
   dismiss: dismissSetting,
   list: listSetting,
 );

 try {
   // 画像ピッカーを表示し、ユーザーによって選択された画像のリストを取得します。
   resultList = await MultiImagePicker.pickImages(
     selectedAssets: images,
     iosOptions: IOSOptions(
       // iOS固有のオプション(決定・キャンセルボタンの設定など)
     ),
     androidOptions: AndroidOptions(
       // Android固有のオプション(アクションバーの色、タイトルなど)
     ),
   );
 } on Exception catch (e) {
   print(e);
 }

 // 選択された画像がある場合、状態を更新して画像リストをUIに反映させます。
 if (resultList.isNotEmpty) {
   setState(() {
     images = resultList;
   });
 }
}

どうやらSelectionSettingが画像の選択制限に関係あるみたいなんですけど、SelectionSettingだけを設定する方法は今のところないみたいです。

multi_image_picker_plusのメリットとデメリット

メリット

  • 画像の選択制限に上限をかけれる
  • 画像の選択数のtextが表示される
  • 制限以上の画像を選択できない

デメリット

  • 画像の選択に上限をかけるにはコードの量が増える
  • 最初に画像使用のリクエストを許可した際に画像が表示されない <=これは工夫したらなんとかなるかも
  • Android端末で試したらエラーになる

まとめ

multi_image_picker_plusは現在バージョン0.0.4なので今後に期待って感じの結果になってしまいました。
画像の選択制限できるパッケージだれか作ってくれないかな🥹

最後まで読んでいただきありがとうございました!

Discussion