如何在 Flutter 中实现这个

问题描述 投票:0回答:1

我有一个由 CachedNetworkImage 和博客文本组成的 UI,它的想法是当文本超过图像的高度时使文本位于容器的开头。

我已经寻找了解决方案并实现了一些代码 该解决方案会产生一些问题:(如果您有更好的方法,只需评论它,您不必阅读问题)

  1. 每次我滚动到父小部件时,它都会重新计算图像的大小。
  2. 这会导致卷轴突然弹跳。
  3. 当我想显示图像时,我希望它动态地适合其容器,这不仅会产生另一个问题,因为我搜索的解决方案不符合此条件。
  4. 继续第三点,我试图将图像动态地放入其容器内,这给了我一个奇怪的异常(错误状态:未来已经完成),我必须热启动应用程序才能成功加载图像。

我正在尝试实施的解决方案:

import 'dart:math';

import 'package:flutter/material.dart';

class DropCapText extends StatelessWidget {
  final Widget dropCap;
  final String text;
  final TextStyle textStyle;
  final EdgeInsets dropCapPadding;

  const DropCapText({
    super.key,
    required this.dropCap,
    required this.text,
    required this.textStyle,
    this.dropCapPadding = EdgeInsets.zero,
  });

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        //get the drop cap size
        // final dropCapSpan = WidgetSpan(child: dropCap);
        // final dropCapPainter = TextPainter(
        //     text: dropCapSpan, textDirection: Directionality.of(context));

        // dropCapPainter.layout(maxWidth: constraints.maxWidth);

        //get the position of the last bit of text next to the dropcap
        final textSpan = TextSpan(text: text, style: textStyle);
        final textPainter = TextPainter(
            text: textSpan, textDirection: Directionality.of(context));
        textPainter.layout(
            maxWidth: max(constraints.minWidth,
                constraints.maxWidth - dropCapPadding.horizontal - 150));
        final lastPosition =
            textPainter.getPositionForOffset(Offset(textPainter.width, 100));

        return Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: dropCapPadding,
                  child: dropCap,
                ),
                Expanded(
                  child: Text(
                    text.substring(0, lastPosition.offset),
                    style: textStyle,
                    softWrap: true,
                  ),
                ),
              ],
            ),
            Text(text.substring(lastPosition.offset), style: textStyle),
          ],
        );
      },
    );
  }
}
import 'dart:async';
import 'dart:developer';

import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

class BlogImage extends StatefulWidget {
  const BlogImage({super.key, required this.imageUrl});
  final String imageUrl;

  @override
  State<BlogImage> createState() => _BlogImageState();
}

class _BlogImageState extends State<BlogImage> {
Size? imageSize;

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

  Future<void> _fetchImageSize() async {
    try {
final       Size size = await _calculateImageDimension(widget.imageUrl);
      log("Fetched size: $size");
      setState(() {
        imageSize = size; // Update the state with the fetched size
      });
    } catch (e) {
      log("Error fetching image size: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    if (imageSize == null) {
     
      return const Center(child: CircularProgressIndicator());
    }

    return SizedBox(
      height: 150,
      width: 150,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(10),
        child: CachedNetworkImage(
          placeholderFadeInDuration: const Duration(milliseconds: 500),
          fit: imageSize!.height > imageSize!.width
              ? BoxFit.fitWidth
              : BoxFit.fitHeight,
          imageUrl: widget.imageUrl,
          placeholder: (context, url) =>
              const Center(child: CircularProgressIndicator()),
          errorWidget: (context, url, error) =>
              const Icon(Icons.image_not_supported),
        ),
      ),
    );
  }
}

Future<Size> _calculateImageDimension(String imageUrl) async {
  Completer<Size> completer = Completer();
  Image image = Image(image: CachedNetworkImageProvider(imageUrl));
  image.image.resolve(const ImageConfiguration()).addListener(
        ImageStreamListener(
          (ImageInfo info, bool synchronousCall) {
            var myImage = info.image;
            Size size =
                Size(myImage.width.toDouble(), myImage.height.toDouble());
            completer.complete(size);
          },
          onError: (error, stackTrace) {
            completer.completeError(error);
          },
        ),
      );
  return completer.future;
}

flutter
1个回答
0
投票

我建议对课程进行一些细微的改变

BlogImage
。请注意,小部件状态变量
size
现在是
Future<Size>
并在
initState
中分配。 不再需要调用
setState
,构建方法现在返回
FutureBuilder

原则上,您可以组合功能

_calculateImageSize
_fetchImageSize
。您可能想在记录后重新抛出在
_fetchImageSize
中捕获的错误。

import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';

class BlogImage extends StatefulWidget {
  const BlogImage({super.key, required this.imageUrl});
  final String imageUrl;

  @override
  State<BlogImage> createState() => _BlogImageState();
}

class _BlogImageState extends State<BlogImage> {
  late Future<Size> size;

  @override
  void initState() {
    super.initState();
    size = _fetchImageSize();
  }

  Future<Size> _fetchImageSize() async {
    try {
      final Size size = await _calculateImageSize(widget.imageUrl);
      log("Fetched size: $size");
    } catch (e) {
      log("Error fetching image size: $e");
      // rethrow; or return a default size 
    }
    return Size(200, 200);
  }

  Future<Size> _calculateImageSize(String imageUrl) {
    ...
    return completer.future;
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: size,
      builder: (context, snapshot) => switch (snapshot.connectionState) {
        ConnectionState.waiting => CircularProgressIndicator(),
        ConnectionState.none => const Icon(Icons.image_not_supported),
        ConnectionState.active || ConnectionState.done => SizedBox(
            height: snapshot.data!.height,
            width: snapshot.data!.width,
            child: ClipRRect(
              borderRadius: BorderRadius.circular(10),
              child: CachedNetworkImage(
                placeholderFadeInDuration: const Duration(milliseconds: 500),
                fit: snapshot.data!.height > snapshot.data!.width
                    ? BoxFit.fitWidth
                    : BoxFit.fitHeight,
                imageUrl: widget.imageUrl,
                placeholder: (context, url) =>
                    const Center(child: CircularProgressIndicator()),
                errorWidget: (context, url, error) =>
                    const Icon(Icons.image_not_supported),
              ),
            ),
          ),
      },
    );
  }
}

通过这些更改,我在滚动时没有观察到任何奇怪的行为。如果您使用

ListView
,当图像进入视图并调用
initState
时,将重新计算图像大小。

© www.soinside.com 2019 - 2024. All rights reserved.