😺

[Flutter] 画像の縦横幅を取得してUIに反映 (ネット画像)

2025/02/10に公開

画像の横幅を反映

flutterのアプリ開発業務にて画像の表示を行っていました。
その際にAPI先の画像を取得し画像毎にサイズが違う事象が発生。

それぞれ縦横幅が合う様にアプリ側で表示したい。
実際に実装したので、備忘録でかきこ。


 /// 画像サイズ保持
    final imageSize = useState<Size?>(null);

/// 画像のサイズを取得
    Future<Size> getSize(String ImageUrl) {
      final completer = Completer<Size>();

      // 画像の解像度を取得するための ImageStream を生成
      final imageStream =
          NetworkImage(ImageUrl).resolve(ImageConfiguration.empty);

      ImageStreamListener? listener;
      listener = ImageStreamListener(
        (ImageInfo info, bool synchronousCall) {
          // 画像の実際のサイズを取得
          final image = info.image;
          final size = Size(
            image.width.toDouble(),
            image.height.toDouble(),
          );

          // 取得完了後、リスナーを解除し、Completer を完了させる
          imageStream.removeListener(listener!);
          completer.complete(size);
        },
        onError: (dynamic exception, StackTrace? stackTrace) {
          debugPrint('画像読み込みに失敗しました: $exception');
          imageStream.removeListener(listener!);
        },
      );

      // リスナー登録
      imageStream.addListener(listener);

      // Future<Size> を返す(画像読み込み完了まで待つ)
      return completer.future;
    }

////////////////////////////// ui側 

/// Widgetで関数呼び出しの場合
   Widget _getImage(
    BuildContext context,
    String imageUrl,
    Size? size,
  ) {
    /// 画面に合わせた縦横比
    final screenWidth = MediaQuery.of(context).size.width;
    final height = screenWidth * (size!.height / size.width);

    return Container(
      width: screenWidth,
      height: height,
      decoration: BoxDecoration(
        image: DecorationImage(
          image: NetworkImage(imageUrl),
          fit: BoxFit.contain,
        ),
      ),
    );
  }

Completer と Future の基本

Future

非同期処理の結果を「後で」返す約束です。たとえば「今すぐには計算結果がわからなくても、あとで教えてくれる」感じです。

Completer

Future を自分で「いつ結果を出すか」を決められるための仕組みです。

使い方のイメージとして、Completer を作ると、その中に「将来の結果」を入れるための Future が用意されます。後で、結果が出たときに completer.complete(結果) とすることで、Future が完了(解決)し、待っていた処理に結果が渡されます。

final completer = Completer<Size>();

上記とすることで、「Size 型の Future」を作って、あとで画像サイズが分かったときにその値を返すようにしています。

NetworkImage と resolve

NetworkImage(boardImageUrl)

これは、指定した URL の画像を表すオブジェクトです。
※ ただの「画像の情報」です。

resolve(ImageConfiguration.empty)

画像を実際に読み込むために、画像プロバイダ(ここでは NetworkImage)に対して、どのような設定で画像を取得するか(ここでは ImageConfiguration.empty =特に設定なし)を渡して、ImageStream を生成します。

ImageStream

画像の読み込みの進行状況を通知してくれる「ストリーム」(連続して情報が流れる仕組み)です。

final imageStream = NetworkImage(boardImageUrl).resolve(ImageConfiguration.empty);

上記は、指定した URL の画像を読み込むための「ストリーム」を作る処理です。

ImageStreamListener とリスナーの登録

ImageStreamListener

ImageStream が「画像が読み込まれたよ!」という情報を送ってくれるときに、その通知を受け取るための「リスナー(監視者)」です。

ImageStreamListener? listener;
listener = ImageStreamListener(
  (ImageInfo info, bool synchronousCall) {
    // 画像が読み込まれた時に呼ばれる部分
    final image = info.image;
    final size = Size(
      image.width.toDouble(),
      image.height.toDouble(),
    );

    // ここで画像サイズをCompleterにセットして、Futureを完了させる
    imageStream.removeListener(listener!);
    completer.complete(size);
  },
  onError: (dynamic exception, StackTrace? stackTrace) {
    // エラーが発生したときの処理(ここではエラーメッセージを出力)
    debugPrint('画像読み込みに失敗しました: $exception');
    imageStream.removeListener(listener!);
  },
);

(ImageInfo info, bool synchronousCall)

これは「画像が正常に読み込まれたとき」のコールバックです。

info.image から画像の幅 (width) や高さ (height) を取得し、Size 型に変換します。
その後、completer.complete(size); として Future を完了させます。
また、 imageStream.removeListener(listener!) で、リスナー登録を解除し、無駄な通知を受けないようにしています。

onError

画像読み込みに失敗した場合の処理です。エラーをログに出力し、リスナーの解除を行っています。

リスナーの登録と Future の返却

最後に、リスナーをストリームに登録します。

imageStream.addListener(listener);

これで、画像が読み込まれると先ほどのリスナーが呼ばれるようになります。

return completer.future;

上記で、画像の読み込みが完了するまで待つ Future(約束された結果)を返します。

この Future は、リスナーが画像のサイズを取得して completer.complete(size) を呼ぶと完了し、その時点で呼び出し元にサイズが渡されます。

全体の流れのまとめ

Completer を作る
→ 「あとで結果(画像のサイズ)を教えてくれる約束の箱」を用意。

NetworkImage から ImageStream を生成する
→ 画像読み込みの進行状況を監視するストリームを作る。

ImageStreamListener を作成し、リスナーとして登録
→ 画像が読み込まれたら、画像情報(サイズなど)を取得して、Completer にその値をセットする。

Future(completer.future)を返す
→ 呼び出し側は、この Future が完了するまで待ち、画像サイズが取得されたらそのサイズが得られる。

以上!!!

Discussion