网页抖动:显示网络图像之前先获取其宽度/高度形式

时间:2019-12-12 01:30:27

标签: flutter dart flutter-web

在显示图像之前,我想从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;
  }
}

结果是;  -屏幕仅显示“正在加载...”,并且未加载图像。  -print的输出如下。这意味着,FutureBuilder在加载图像之前被调用了两次,我们可以获取宽度,但是之后FutureBuilder不会被调用。

false
false
112

环境:

  • Flutter 1.13.0•频道开发(由于Fluff Web)
  • Chrome版本79.0.3945.79

3 个答案:

答案 0 :(得分:0)

根据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;
  }

答案 1 :(得分:0)

基于您提供的参考post的观察对。

  1. 您已经将ui.ImageImage小部件混在一起了。
  2. 如果您将信息逻辑与窗口小部件构建分开,那么您将无权访问“图像”窗口小部件,这意味着您必须重新创建它。相反,您可以创建一个小部件并返回它。
  3. 在您基于http的答案中,response.body.length可能不完全代表图像尺寸。您必须查看响应头是否包含有关图像的任何信息。
  4. 还请注意,根据FutureBuilderConnectionState之类的未来状态,将使用不同的waiting多次调用done的build方法。选中here

选项1: 如果您不关心Image小部件,那么您的当前代码可以进行一些稍微的修改。这与原始的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中的代码,即可将Image小部件的创建和信息提取引入到build方法中。

答案 2 :(得分:0)

您已经有了自己的自助代码。从那里,您可以使用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;
}

这种方法很糟糕,因为图像将被解码两次,但是我没有创建自己的自定义Image小部件,该小部件可以直接使用ui.Image对象,我不知道认为对此没有很多可以做的。