imageflutterflutter-desktopflutter-image

White flash when image is repainted [flutter]


I'm trying to build a grid of images with a given width and height, wrapping them inside Containers and using fit: BoxFit.fill to be able to set a border if the image is selected (i don't care to keep the image aspect ratio, i want to have the same total width and height for each container).

The problem is that i notice a white flash when the image gets repainted after it has been tapped on. This seems not to happen when there are few images, but with 15+ images it is noisy.

I tried to add gaplessPlayback: true on the image widget, as i found here, but this did not solve my issue.

Here's a gif that shows my issue (i used 16 images, size is 1920x1080):

EDIT:

I forgot to point out that this is just an example, i used a border in the code but in my case i want also to add a padding to the container to make the image smaller (like in android photo gallery), this means that the tapped image should repaint every time.

enter image description here

And this is my code:

import 'dart:io';

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

void main() {
  // See https://github.com/flutter/flutter/wiki/Desktop-shells#target-platform-override
  debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia;
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(body: ImageGridView()),
    );
  }
}

class ImageGridView extends StatelessWidget {
  List<File> _fileList = [];
  ImageGridView(){
    _fileList = [
        File('C:/flutter/img/1.jpg'),
        File('C:/flutter/img/3.jpg'),
        File('C:/flutter/img/4.jpg'),
        File('C:/flutter/img/5.jpg'),
        File('C:/flutter/img/6.jpg'),
        File('C:/flutter/img/7.jpg'),
        File('C:/flutter/img/8.jpg'),
        File('C:/flutter/img/9.jpg'),
        File('C:/flutter/img/10.jpg'),
        File('C:/flutter/img/11.jpg'),
        File('C:/flutter/img/12.jpg'),
        File('C:/flutter/img/13.jpg'),
        File('C:/flutter/img/14.jpg'),
        File('C:/flutter/img/15.jpg'),
        File('C:/flutter/img/16.jpg'),
      ];
    }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      child: Wrap(
        children: _fileList.map((file) {
          return WindowsAsset(file);
        }).toList(),
      ),
    );
  }
}

class WindowsAsset extends StatefulWidget {
  final File _file;

  WindowsAsset(this._file);

  @override
  State<StatefulWidget> createState() => WindowsAssetState();
}

class WindowsAssetState extends State<WindowsAsset> {
  bool selected = false;

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width / 2;
    final height = width * 1080 / 1920;
    return Container(
      width: width,
      height: height,
      child: Container(
        child: Container(
          constraints: BoxConstraints.expand(),
          decoration: !selected
              ? null
              : BoxDecoration(
                  border: Border.all(
                    color: Color.fromRGBO(153, 209, 255, 1),
                    width: 4
                  ),
                ),
          child: Container(
            child: GestureDetector(
              child: Image.file(
                widget._file,
                filterQuality: FilterQuality.medium,
                fit: BoxFit.fill,
                gaplessPlayback: true,
              ),
              onTap: () => setState(() {
                selected = !selected;
              }),
            ),
          ),
        ),
      ),
    );
  }
}

How i can solve it? Thank you!


Solution

  • This is probably because of a mix of 1. ImageCache constraints and 2. Nesting all your images within Wrap

    The ImageCache has a maximum number of images that it can cache in memory, as well as a maximum number of total bytes. When that limit is reached, old images are evicted to make space for new ones. When trying to display an image that was previously evicted from cache, it will take some time to load it into memory again, therefore the white flash. You can change the limits of the cache as per How do I change or replace the ImageCache in Flutter?

    The default image cache limits are 1000 different images and a total of 100MiB, which are usually enough given an optimal layout where off-screen widgets are not built.

    Wrap might be the main reason of your problem - I believe it internally builds all of its children in order to figure out how to wrap them. In your case, that would be all of the images. It may be that because of the Wrap, all your images are considered on-screen and therefore not evicted from cache - this would explain why some of the images in the GIF are flashing even though you are not rebuilding the entire widget tree - because those images are not cached because the cache is full and none of the images can be evicted.

    You might want to replace Wrap with a GridView.builder, which will only build the on-screen widgets.

    You could have the same issue if the images are simply too large to fit as many in the cache as you are displaying at once. If you display a 50MB image in a 10x10 container, that still takes 50MB of in-memory cache.