仅在视口内加载图像

Load image only if it's inside viewport

我有一个带有 ListView 的脚手架作为 child 可以滚动。 它的顶部有一个 header 容器和一个包含图像列表的列。

我只想在这些图像出现在视口中时加载这些图像,但所有图像都同时开始加载。

当我将这些图像添加到 ListView 的 children 时并没有发生这种情况,但我想将它们放到一个单独的小部件中以保持代码整洁。可能吗?

这是一个简化的例子:

const List<String> images = [
  'https://cataas.com/cat?hash=1',
  'https://cataas.com/cat?hash=2',
  'https://cataas.com/cat?hash=3',
  'https://cataas.com/cat?hash=4',
  'https://cataas.com/cat?hash=5',
  'https://cataas.com/cat?hash=6',
  'https://cataas.com/cat?hash=7',
  'https://cataas.com/cat?hash=8',
];

// Scaffold child
class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      children: [
        // Header
        Placeholder(
          fallbackHeight: 300,
        ),
        Category(),
        SizedBox(height: 16),
        Category(),
      ],
    );
  }
}

class Category extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('Build category');
    return Column(
      children: [
        Padding(
          padding: EdgeInsets.all(32),
          child: Text('CATEGORY TITLE'),
        ),
        ...images.map((image) => CategoryItem(image)),
      ],
    );
  }
}

class CategoryItem extends StatelessWidget {
  final String imageUrl;

  CategoryItem(this.imageUrl);

  @override
  Widget build(BuildContext context) {
    print('Build image: $imageUrl');
    return FadeInImage.memoryNetwork(
      fit: BoxFit.cover,
      placeholder: kTransparentImage,
      image: imageUrl,
    );
  }
}

您需要使用 ListView.builder,因为它会延迟渲染其子项。此外,您还需要使用一个列表。

问题是在加载之前您不知道图像的大小,因此所有高度为 0 的图像都会立即构建。但是,如果您为它们设置固定高度,它将按预期工作。

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final children = [
      // Header
      Placeholder(
        fallbackHeight: 300,
      ),
      Padding(
        padding: EdgeInsets.all(32),
        child: Text('CATEGORY TITLE'),
      ),
      ...images.map((image) => CategoryItem(image)),
      SizedBox(height: 16),
      Padding(
        padding: EdgeInsets.all(32),
        child: Text('CATEGORY TITLE'),
      ),
      ...images.map((image) => CategoryItem(image)),
    ];

    return Scaffold(
      body: ListView.builder(
        itemCount: children.length,
        itemBuilder: (context, i) => children[i],
      )
    );
  }
}