You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为PListView添加pictureUrl判空后,其他页面FittedBox报错原因解析

问题描述

在Flutter项目中给PListView组件的Item添加了pictureUrl != null的条件渲染逻辑后,原本使用相同Item Widget的其他页面出现了FittedBox渲染错误,报错信息为'package:flutter/src/rendering/box.dart'文件第320行的'width > 0.0'断言不成立。移除该判空条件后,其他页面的错误随之消失。需要解释错误产生机制,并确认是否与Widget复用有关。

相关代码片段

PListView中添加的判空逻辑

class PListView extends StatelessWidget {
  const PListView({super.key});

  @override
  Widget build(BuildContext context) {
    double w = MediaQuery.of(context).size.width - 16;
    final ScrollController controller = ScrollController();
    double offset = 0;
    bool end = false;
    bool oneTime = true;
    controller.addListener(() async {
      if (controller.position.pixels >=
              controller.position.maxScrollExtent - 100 &&
          !end &&
          oneTime) {
        offset = controller.offset;
        oneTime = false;
        end = await BlocProvider.of<PCubit>(context).addNews(2);
        oneTime = true;
        if (!end) {
          controller.jumpTo(offset);
        }
      }
    });
    return BlocBuilder<PCubit, PState>(
        buildWhen: (previous, current) => current.status.isSuccess,
        builder: (context, state) {
          if (state.status.isSuccess) {
            List<News> newsList = state.newsList;

            return RefreshIndicator(
              onRefresh: () async {
                await BlocProvider.of<PCubit>(context).fetchNews(2);
              },
              child: ListView.builder(
                  itemCount: newsList.length,
                  itemBuilder: (BuildContext context, int index) {
                    News newsItem = newsList[index];
                    String? pictureUrl = newsItem.announcePictureUrl;

                    return Container(
                      padding: const EdgeInsets.symmetric(horizontal: 8),
                      child: InkWell(
                        child: Card(
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.start,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              (pictureUrl != null)
                                  ? Hero(
                                      tag: newsItem.code,
                                      child: SizedBox(
                                        height: w * 9 / 16,
                                        width: w,
                                        child: ClipRRect(
                                          borderRadius:
                                              const BorderRadius.vertical(
                                                  top: Radius.circular(10),
                                                  bottom: Radius.circular(0)),
                                          child: FittedBox(
                                            alignment: Alignment.topCenter,
                                            clipBehavior: Clip.hardEdge,
                                            fit: BoxFit.cover,
                                            child: Picture(
                                              url: pictureUrl,
                                              radius: 0,
                                              placeholderWidth: w,
                                              placeholderHeight: w * 9 / 16,
                                            ),
                                          ),
                                        ),
                                      ),
                                    )
                                  : SizedBox(
                                      height: w * 1 / 5,
                                      width: w,
                                      child: const ClipRRect(
                                          borderRadius: BorderRadius.vertical(
                                              top: Radius.circular(10),
                                              bottom: Radius.circular(0)),
                                          child:
                                              ColoredBox(color: Colors.green)),
                                    ),                           

报错的FittedBox所在Widget(url为null时)

Widget build(BuildContext context) {
    return Hero(
      tag: code,
      child: SizedBox(
        width: w,
        child: ClipRRect(
          borderRadius: const BorderRadius.vertical(
              top: Radius.circular(10), bottom: Radius.circular(0)),
          child: FittedBox(
            alignment: Alignment.topCenter,
            clipBehavior: Clip.hardEdge,
            fit: BoxFit.fitWidth,
            child: Picture(
              url: url,
              radius: 0,
              placeholderWidth: w,
              placeholderHeight: w * 9 / 16,
            ),
          ),
        ),
      ),
    );
  }

Picture Widget代码

Widget build(BuildContext context) {
    return ClipRRect(
        borderRadius: BorderRadius.all(Radius.circular(radius)),
        child: (url != null) ? CachedNetworkImage(
          imageUrl: url!,
          errorWidget: (context, url, error) => const Icon(Icons.error),
          placeholder: (context, url) =>
            (placeholderWidth != null && placeholderHeight != null)
              ? SizedBox(
                  width: placeholderWidth,
                  height: placeholderHeight,
                  child: PLoader(
                    maxIconSize: loaderMaxSize,
                    durationMilliseconds: loaderDurationMillseconds),
                )
              : PLoader(
                  maxIconSize: loaderMaxSize,
                  durationMilliseconds: loaderDurationMillseconds),
          width: width,
          height: height,
          fit: fit,
        )
        :
        Image.asset('assets/default.jpg'),
        );
  }

错误日志

════════ Exception caught by rendering library ═════════════════════════════════
The following assertion was thrown during performLayout():
'package:flutter/src/rendering/box.dart': Failed assertion: line 320 pos 12: 'width > 0.0': is not true.

Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.

The relevant error-causing widget was:
    FittedBox FittedBox:file:.../lib/view/article.dart:88:18
错误产生机制及Widget复用的影响

1. 核心原因:Element复用导致布局状态异常

这个错误确实和Flutter的Element复用直接相关。ListView.builder为了性能优化,会复用Item对应的Element对象——当滚动时,离开视口的Item Element不会被销毁,而是会被用来渲染新进入视口的Item。

当你在PListView的Item中添加条件渲染逻辑后,同一个Item位置可能会渲染两种完全不同的Widget树:

  • 当pictureUrl != null时:渲染带Hero+FittedBox+Picture的结构,父SizedBox指定了宽高(width:w,height:w*9/16)
  • 当pictureUrl == null时:渲染带ColoredBox的SizedBox,父SizedBox的高度是w*1/5

当其他页面使用相同的Item Widget(带FittedBox的结构)时,ListView可能会复用之前PListView中渲染过的、对应pictureUrl == null状态的Element。此时Element会保留之前的布局状态(比如高度为w*1/5),但新的Widget需要的是w*9/16的高度,这种不匹配会导致FittedBox在布局计算时,可用宽度被压缩至0,触发width>0.0的断言。

2. 触发断言的直接布局问题

再看报错的FittedBox所在Widget:它的父SizedBox只指定了width:w,没有明确设置高度。当Element复用后,父容器的高度被错误地设置为w*1/5,而FittedBox使用BoxFit.fitWidth模式,需要子Widget(Picture返回的Image.asset)有确定的尺寸。但Image.asset在未指定宽高时会使用原始尺寸,如果原始图片的宽度为0(或布局计算时被压缩),就会导致FittedBox计算自身宽度时出现0值,触发断言。

解决方案建议
  1. 给所有FittedBox的父容器明确指定宽高
    在报错的Widget中,给SizedBox同时添加高度参数,避免布局尺寸异常:

    child: SizedBox(
      width: w,
      height: w * 9 / 16, // 补充明确高度
      child: ClipRRect(
        // ... 其余代码不变
      ),
    )
    
  2. 给Picture的默认图片指定宽高
    修改Picture Widget中Image.asset的代码,强制指定宽高,避免依赖原始图片尺寸:

    : Image.asset(
        'assets/default.jpg',
        width: width ?? placeholderWidth,
        height: height ?? placeholderHeight,
        fit: fit ?? BoxFit.cover,
      ),
    
  3. 给条件渲染的Widget添加唯一Key,阻止Element复用
    在PListView的条件渲染分支中,给两个不同的Widget添加不同的Key,让Flutter知道这是两种不同的Widget,不会复用Element:

    (pictureUrl != null)
        ? Hero(
            tag: newsItem.code,
            key: const Key('news-item-with-image'), // 添加唯一Key
            child: SizedBox(/* ... */),
          )
        : SizedBox(
            key: const Key('news-item-no-image'), // 添加唯一Key
            height: w * 1 / 5,
            width: w,
            child: const ClipRRect(/* ... */),
          ),
    

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 07:40:06