0

I'am trying to get the size of an image when it is loaded and widget is already rendered.

So, if call _getSizes() function in initState it returns width: 0.0, height: 0.0.

The same result when I call _getSizes() in frameBuilder.

For now I can get desired result from _getSizes() function if I click floatingActionButton or use Future.delayed at least with 500 millisecond delay only, and then it prints the actual container size: width: 375.0, height: 210.9375

import 'package:flutter/material.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({ Key? key }) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey _imgKey = GlobalKey();

  _getSizes() {
    final RenderBox renderBox = _imgKey.currentContext!.findRenderObject() as RenderBox;
    final boxSize = renderBox.size;
    print("width: ${boxSize.width}, height: ${boxSize.height}");
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      print("WidgetsBinding");
      _getSizes();
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test Image Load',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Image Asset Load'),
        ),
        body: Center(
          child: Container(
            key: _imgKey,
            child: Image.asset(
              'assets/images/landscape_big.jpg',
              frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
                if (wasSynchronouslyLoaded) {
                  print('wasSynchronouslyLoaded');
                }
                if (frame != null) {
                  _getSizes();
                }
                return child;
              }
            ),
          )
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _getSizes();
          },
          child: const Icon(Icons.update),
          backgroundColor: Colors.green,
        ),
      )
    );
  }
}

How to get the actual size when image asset is loaded and parent container is rendered? Is there any onLoad method or maybe controller for Image.assets widget?

mr.boris
  • 3,667
  • 8
  • 37
  • 70
  • [This](https://stackoverflow.com/questions/44665955/how-do-i-determine-the-width-and-height-of-an-image-in-flutter) what you’re looking for. – Miguel Ruivo Aug 22 '21 at 10:54
  • Thank you for the link. This approach helps to solve this issue partially. The `Completer` gives original image size but not actual size of the `Image` widget in the layout. Also at the moment when `Completer` returns original image size, `RenderBox` still returns zeros. So that Flutter does not have time to calculate actual widget dimensions even if original image dimensions are known. I don’t know why it’s like that and this is not good. At leas I can calculate height with the help of known aspect ratio from `Completer` but it will be impossible if you don't know any of the layout sizes. – mr.boris Aug 22 '21 at 16:18

1 Answers1

0

After some testing and trying multiple approaches I found working solution but it still looks like a crutch.

import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:async';
import 'package:flutter/scheduler.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({ Key? key }) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey _imgKey = GlobalKey();

  _getSizes() {
    final RenderBox renderBox = _imgKey.currentContext!.findRenderObject() as RenderBox;
    final boxSize = renderBox.size;
    print("width: ${boxSize.width}, height: ${boxSize.height}");
  }

  @override
  void initState() {
    super.initState();
    // https://medium.com/flutterworld/flutter-schedulerbinding-vs-widgetsbinding-149c71cb607f
    // Doesn't work at all
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      print("WidgetsBinding");
      // _getSizes();
    });
    SchedulerBinding.instance!.addPostFrameCallback((_) {
      print("SchedulerBinding");
      // _getSizes();
    });
  }

  @override
  Widget build(BuildContext context) {
    Image image = Image.asset('assets/images/landscape_big.jpg');
    Completer<ui.Image> completer = Completer<ui.Image>();
    image.image
      .resolve(const ImageConfiguration())
      .addListener(ImageStreamListener((ImageInfo info, bool synchronousCall) {
        completer.complete(info.image);
        print("completer: w ${info.image.width} h ${info.image.height}");
      }));

    return MaterialApp(
      title: 'Image Load',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Image Load'),
        ),
        body: Center(
          child: Container(
            key: _imgKey,
            child: Image.asset(
              'assets/images/landscape_big.jpg',
              frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
                if (wasSynchronouslyLoaded) {
                  print('wasSynchronouslyLoaded');
                }
                if (frame != null) {
                  _getSizes();
                  Future.delayed(Duration.zero, () {
                    print('zero delay');
                    _getSizes(); // <--- ONLY THIS WORKS
                  });
                }
                return child;
              }
            ),
          )
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            _getSizes();
          },
          child: const Icon(Icons.update),
          backgroundColor: Colors.green,
        ),
      )
    );
  }
}

So, as a result I get this execution sequence in my DEBUG CONSOLE:

flutter: WidgetsBinding
flutter: SchedulerBinding
flutter: completer: w 3840 h 2160
flutter: width: 0.0, height: 0.0
flutter: zero delay
flutter: width: 375.0, height: 210.9375

In the end of the day the only one approach worked for me - Future.delayed with zero duration in the frameBuilder of Image.asset widget.

I don't know how reliable is this solution and why it is so hard to implement such simple task in Flutter.

mr.boris
  • 3,667
  • 8
  • 37
  • 70