avoid_incorrect_image_opacity
v0.4.0 Warning Fix Widget Replacement
Flags Opacity widgets that wrap an Image widget as their child. The Image widget has a dedicated opacity parameter that is more efficient than wrapping it in a separate Opacity widget.
Why use this rule
Section titled “Why use this rule”Opacity forces the compositor to paint its subtree into an offscreen buffer
(saveLayer) and blend that buffer back — expensive, and worse the larger the
image. Image takes an opacity animation of its own and applies it while
painting the pixels, with no extra layer.
The quick fix moves the value inward, wrapping it in an
AlwaysStoppedAnimation because Image.opacity takes an
Animation<double>, not a plain double.
See also: Image.opacity | Opacity
// A watermark faded behind the page content.Opacity( opacity: 0.15, child: Image.asset('assets/watermark.png'),);Image.asset( 'assets/watermark.png', opacity: const AlwaysStoppedAnimation(0.15),);Examples
Section titled “Examples”Every Image constructor counts
Section titled “Every Image constructor counts”Image.network, Image.file, Image.memory and the unnamed Image(...) all
take opacity:
// Don'tOpacity( opacity: 0.8, child: Image.network('https://example.com/avatar.png'),);
// DoImage.network( 'https://example.com/avatar.png', opacity: const AlwaysStoppedAnimation(0.8),);An animated fade goes in directly
Section titled “An animated fade goes in directly”If the value already comes from an Animation<double>, there is no wrapper to
add — pass it straight through:
// Don't — Opacity forces a layer on every frame of the fadeOpacity( opacity: fadeController.value, child: Image.asset('assets/logo.png'),);
// Do — no saveLayer, and the Image repaints without rebuildingImage.asset('assets/logo.png', opacity: fadeController);The quick fix would write AlwaysStoppedAnimation(fadeController.value) here,
which is correct but freezes the fade at the current frame. Passing the
animation itself is the better hand edit.
Anything but an Image is fine
Section titled “Anything but an Image is fine”// Not reported — Text has no opacity parameterOpacity(opacity: 0.5, child: const Text('Dimmed'));Known limitations
Section titled “Known limitations”The Image must be the direct child. Opacity > Padding > Image is not
reported, because the padding would have to move too.
A subclass of Image is reported as well. The child is matched by
assignability, so your own class CachedImage extends Image is caught — and it
does inherit opacity, so the rewrite holds.
An Image that already sets opacity is skipped by the fix. The rule still
reports the redundant Opacity, but combining two opacity sources is a
decision, so nothing is offered automatically.
Configuration
Section titled “Configuration”This rule is in the recommended preset, so it is on with
preset: recommended or preset: opinionated. Add it to preset: core with
avoid_incorrect_image_opacity: true.
To turn it off:
rules: avoid_incorrect_image_opacity: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”avoid_border_all— Use Border.fromBorderSide instead of Border.all for const support.avoid_expanded_as_spacer— Use Spacer instead of Expanded with an empty child.avoid_wrapping_in_padding— Avoid wrapping widgets that support padding in a Padding widget.prefer_align_over_container— Use Align instead of Container when only alignment is set.