Load image only if it's inside viewport

906

You need to use ListView.builder, because it renders its children lazily. Also you'll need to use one single list.

The problem is that you don't know the image's size before it's loaded, so all images with 0 height will be built instantly. But if you set fixed height to them, it will work as intended.

1

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],
      )
    );
  }
}
Share:
906
Anton
Author by

Anton

Updated on December 16, 2022

Comments

  • Anton
    Anton over 1 year

    I have a Scaffold with a ListView as a child to be able to scroll. There is a header container at top of it and a column with a list of images.

    I want to load this images only when they appears in viewport but all of them are starting to load at the same time.

    This is not happening when I'm adding this images to the children of ListView but I'd like to put them to a separate widget to keep my code clean. Is it possible?

    Here is a simplified example:

    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,
        );
      }
    }
    
  • Anton
    Anton over 4 years
    One more reason why I want to use category as a widget is because in my app it's using StreamBuilder to get a list of image urls so it's impossible to use this solution. I'll fix my example
  • Igor Kharakhordin
    Igor Kharakhordin over 4 years
    @Anton I don't understand how this would block you from using a single list. In that case, you could combine your multiple streams into single one and use it just once in StreamBuilder.