【问题标题】:How to use future inside a list in Flutter?如何在 Flutter 的列表中使用未来?
【发布时间】:2019-12-28 12:19:07
【问题描述】:

我正在尝试从网络加载图像,然后将其传递给 ExtendedRawImage 作为已完成的未来 Image 进行裁剪。

问题在于,每张新图像都使用(我认为)同一个未来实例来检查图像是否已加载,这就是为什么它在每张新图像上显示CircularProgressIndicator

尝试过没有解决我的问题:

  • 为未来添加钥匙,
  • 将功能移至全新的StatefulWidget 小部件。

我现在正在使用的代码和 gif:

AnimatedList(
  key: _sListKey,
  physics: BouncingScrollPhysics(),
  controller: _sScrollCtrl,
  padding: EdgeInsets.all(5),
  initialItemCount: sData.length,
  itemBuilder: (context, index, animation) {
    final vehicle = sData[index];
    final color = defaultRegions.contains(vehicle.region)
        ? Colors.grey
        : Colors.blue;
    return _dataContainer(
        vehicle, animation);
  },
)
class CropImage extends StatefulWidget {
  final Vehicle vehicle;

  CropImage({Key key, this.vehicle}) : super(key: key);

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

class _CropImageState extends State<CropImage> {
  Future<ImageInfo> _imageInfo;

  @override
  void initState() {
    super.initState();

    NetworkImage image = NetworkImage(widget.vehicle.image);
    Completer<ImageInfo> completer = Completer();
    image
        .resolve(new ImageConfiguration())
        .addListener(ImageStreamListener((ImageInfo info, bool _) {
      completer.complete(info);
    }));

    _imageInfo = completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: widget.vehicle.vehicleRegion.width /
          widget.vehicle.vehicleRegion.height,
      child: FutureBuilder(
        // key: UniqueKey(),
        future: _imageInfo,
        builder: (context, AsyncSnapshot<ImageInfo> snapshot) {
          if (snapshot.hasData) {
            return ExtendedRawImage(
              image: snapshot.data.image,
              fit: BoxFit.cover,
              soucreRect: Rect.fromLTWH(
                widget.vehicle.vehicleRegion.x,
                widget.vehicle.vehicleRegion.y,
                widget.vehicle.vehicleRegion.width,
                widget.vehicle.vehicleRegion.height,
              ),
            );
          } else {
            return Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

UPDATE_1_dataContainer() 包含CropImage 小部件,我用常规容器包装了它。另外作为旁注,我正在使用 websocket 将数据传递给 AnimatedList,方法是使用 _sListKey.currentState.insertItem(0) 将其插入列表顶部。

UPDATE_2 我想我已经缩小了一个问题的范围。如果我在AnimatedList 中使用任何类型的FutureBuilder(如下所示),则会产生同样的问题(添加“密钥”或移动到另一个Statefull 小部件并不能解决问题)。

FutureBuilder(
  future: http.get(vehicle.image),
  builder: (context, AsyncSnapshot<http.Response> snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      return Image.memory(snapshot.data.bodyBytes);
    } else {
      return CircularProgressIndicator();
    }
  },
),

UPDATE_3:似乎核心问题来自setState() 调用。因为我使用weboskcet 将数据传递给视图,所以每次有新数据进来时,都会调用setState() 来通知AnimatedListListview 重新加载视图,这会强制FutureBuilder 刷新.但是知道这并不能解决我的问题。

@override
void initState() {
  super.initState();

  ws.dataCallback = (vehicle, status) {
    switch (status) {
      case 0:
        setState(() {});
        break;
    }
  };
}

注意:作为请求,创建了我的问题的示例存储库 (link to repo)。

【问题讨论】:

  • 您使用的是最新版本的插件吗?你试过其他的裁剪插件吗?
  • @MikhailPonkin 我也尝试使用Image 包来裁剪图像,但它也产生了同样的问题。
  • 你会考虑分享一个示例项目吗?遵循代码片段有点困难。
  • @GazihanAlankus 我用我的问题的示例存储库更新了问题。

标签: flutter dart


【解决方案1】:

查看 FutureBuilder 的文档:https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html

未来一定是更早获得的,例如在 State.initState、State.didUpdateConfig 或 State.didChangeDependencies 期间。在构造 FutureBuilder 时,不能在 State.build 或 StatelessWidget.build 方法调用期间创建它。如果future与FutureBuilder同时创建,那么每次FutureBuilder的parent重建时,异步任务都会重新启动。

您可以通过将 FutureBuilder 包装在您自己的 StatefulWidget 中并在例如调用 fetchImage 来解决此问题。 initState 并将其分配给一个字段。

编辑:如果所有图像仍然重新加载,即使加载是在 initState 方法中完成的,这一定意味着 State 对象正在重新初始化。鉴于您提到您将项目添加到开头,您的问题可能与以下问题有关https://github.com/flutter/flutter/issues/21023#issuecomment-510950338。提到 ListView.builder 不支持列表中的更改,因为它使用索引进行跟踪。虽然问题是关于 ListView.builder,但 AnimatedList 文档提到:

此小部件类似于 [ListView.builder] 创建的小部件。

如果您唯一的要求是将项目插入顶部,您可以考虑将项目添加到末尾并使用反向 shrinkWrap AnimatedList。一个最小的例子:

import 'dart:math';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Example(),
    );
  }
}

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  List<Vehicle> vehicles = new List();
  GlobalKey<AnimatedListState> listKey = GlobalKey<AnimatedListState>();
  Random rng = new Random();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: AnimatedList(
        shrinkWrap: true,
        reverse: true,
        key: listKey,
        initialItemCount: vehicles.length,
        itemBuilder: (context, index, animation) => SizeTransition(
          axis: Axis.vertical,
          sizeFactor: animation,
          child: ListTile(
            title: WidgetWithFutureBuilder('${vehicles[index].name}'),
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            int id = rng.nextInt(5000);
            vehicles.add(new Vehicle(name: "Vehicle $id"));
            listKey.currentState.insertItem(vehicles.length - 1);
          });
        },
      ),
    );
  }
}

class WidgetWithFutureBuilder extends StatefulWidget {
  final String name;

  WidgetWithFutureBuilder(this.name);

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

class _WidgetWithFutureBuilderState extends State<WidgetWithFutureBuilder> {
  Future<String> future;

  @override
  void initState() {
    super.initState();
    this.future = Future.delayed(Duration(seconds: 1), () => widget.name);
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: future,
      builder: (context, state) {
        if (state.hasData) {
          return ListTile(
            title: Text(state.data),
          );
        }
        return Center(child: CircularProgressIndicator());
      },
    );
  }
}

class Vehicle {
  Vehicle({this.name});

  String name;
}

【讨论】:

  • 我之前试过,结果完全一样。
  • 您能否将示例代码编辑为显示问题的最小可运行代码?可能问题不在你提供的代码 sn-ps 上,而是在周围的代码上。
  • 我更新了代码,以便您更好地理解我的代码结构。作为说明,我使用 weboscket 通过将数据插入列表顶部来将数据传递给 AnimatedList
  • @MārtiņšCiekurs 我已根据您的更新编辑了我的答案,希望对您的情况有所帮助!
  • 感谢您的回答。它对我有用。只有一件事,你将如何改变你的代码,以便在新数据上它会保持在顶部而不是需要滚动到顶部。
【解决方案2】:

好的,我不知道如何创建 Future 的唯一实例,但我可能知道如何解决您的问题。

您可以使用 FadeInImage 来解决这个问题。 FadeInImage 放置一个占位符图像,并在加载后替换为实际的网络图像。占位符图像可以是 gif 加载指示符。

Widget _imageView(BuildContext context, String imageUrl) {
    return FadeInImage.assetNetwork(
        placeholder:'assets/giphy.gif' ,
        image: imageUrl,
    );

}

更多信息请见herehere

【讨论】:

  • 问题是我需要使用`ExtendedRawImage`裁剪图像或将base64转换为图像。只有当我可以同时裁剪它时,我才能将图像作为网络 url 传递。对于我做了多少测试,我认为这是一个错误,因为如果不使用任何类型的列表刷新所有其他图像,就无法从内存或ExtendedRawImage 传递图像。
  • 我认为如果 FadeInImage 接受本地路径作为 URL,它应该仍然可行。我们将不得不检查。在这种情况下,您可以将图像参数用作 - image : getProcessedImageUrl(imageUrl).
猜你喜欢
  • 2021-06-13
  • 2020-04-16
  • 2021-07-01
  • 2021-12-11
  • 2020-07-21
  • 2020-11-06
  • 2021-09-09
  • 2021-05-06
  • 2022-09-27
相关资源
最近更新 更多