flutter for web:在显示之前从 NetworkImage 获取 width/height

flutter for web: Get width/height form NetworkImage before displaying it

我想在显示图像之前从 NetworkImageImage.network 获取属性值,例如宽度和高度。

我找到了以下好帖子,但是没有用。它获得了大小值,但图像未加载 FutureBuilder

我的代码如下;

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder(
          future: _getImage(),
          builder: (BuildContext context, AsyncSnapshot<Image> snapshot) {
            print(snapshot.hasData);
            if (snapshot.hasData) {
              return snapshot.data;
            } else {
              return Text('Loading...');
            }
          },
        ),
      ),
    );
  }

  Future<Image> _getImage() async {
    final Completer completer = Completer();
    final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
    final image = NetworkImage(url);

    image.resolve(ImageConfiguration())
          .addListener(ImageStreamListener((ImageInfo info, bool isSync) {
      print(info.image.width);
      completer.complete(info.image);
    }));

    return completer.future;
  }
}

结果是; - 屏幕仅显示 "Loading..." 并且未加载图像。 - print 输出如下。这应该意味着, FutureBuilder 在加载图像之前被调用了两次,我们可以得到宽度,但之后 FutureBuilder 没有被调用。

false
false
112

环境:

在Abion47的指点下,我通过http包成功获取了镜像。但是即使在获取图像后我仍然无法获取宽度和/或高度值。 或者,我使用 response.body.length 检查下载的图像是否有效。

  Future<Image> _getImage() async {
    Image _image;
    final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
    var response = await http.get(url);

    print("Response status: ${response.statusCode}"); // 200
    _image = Image.memory(response.bodyBytes);
    print(_image.width); // still null
    print(response.body.length); // this shows numbers. I'll use this.

    return _image;
  }

根据您提供的参考 post 进行的一些观察。

  1. 您混淆了 ui.ImageImage 小部件。
  2. 如果您将信息逻辑与小部件构建分开,那么您将无法访问图像小部件,这意味着您将不得不重新创建它。相反,您可以创建一个小部件并 return 它。
  3. 在您基于 http 的答案中,response.body.length 可能不完全代表图像尺寸。您必须查看响应 headers 是否包含有关图像的任何信息。
  4. 另请注意,FutureBuilder 的构建方法将根据 waitingdone 等未来状态以不同的 ConnectionState 多次调用。检查 here

选项 1: 如果您不关心 Image 小部件,那么您当前的代码只需稍作修改即可使用。这与原始 post 完全相同,但经过修改以匹配您在 post.

中定义它的方式
import 'dart:async';

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

class ImageSizeTestWidget extends StatefulWidget {
  ImageSizeTestWidget({Key key, this.title}) : super(key: key);

  final String title;

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

class _ImageSizeTestWidgetState extends State<ImageSizeTestWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: FutureBuilder<ui.Image>(
          future: _getImage(),
          builder: (BuildContext context, AsyncSnapshot<ui.Image> snapshot) {
            print(snapshot.hasData);
            if (snapshot.hasData) {
              return Text('${snapshot.data.width} X ${snapshot.data.height}');
            } else {
              return Text('Loading...');
            }
          },
        ),
      ),
    );
  }

  Future<ui.Image> _getImage() async {
    final Completer<ui.Image> completer = Completer<ui.Image>();
    final String url =
        'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
    Image image = Image.network(url);

    image.image
        .resolve(ImageConfiguration())
        .addListener(ImageStreamListener((ImageInfo info, bool isSync) {
      print(info.image.width);
      completer.complete(info.image);
    }));

    return completer.future;
  }
}

选项 2: 只需使用原始 post 中的代码,将图像小部件的创建和信息提取引入构建方法。

您已经完成了自我回答代码的一半。从那里,您可以使用 instantiateImageCodec.

将字节转换为 ui.Image 对象
Future<Image> _getImage() async {
  Image _image;
  final String url = 'http://images-jp.amazon.com/images/P/4101098018.09.MZZZZZZZ';
  var response = await http.get(url);
  print("Response status: ${response.statusCode}"); // 200, ideally

  final bytes = response.bodyBytes);
  final codec = await instantiateImageCodec(bytes);
  final frame = await codec.getNextFrame();
  final uiImage = frame.image; // a ui.Image object, not to be confused with the Image widget

  print(uiImage.width); // The width of the image in pixels
  print(uiImage.height); // The heightof the image in pixels

  _image = Image.memory(bytes);

  return _image;
}

你必须这样做有点糟糕,因为图像将被解码两次,但还没有创建你自己的可以直接获取 ui.Image 对象的自定义 Image 小部件,我认为对此无能为力。