flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond

Home Page:https://flutter.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Antialiasing behaviour when same-colour

radzish opened this issue · comments

Latest status update: #14288 (comment); some work around suggestions: #14288 (comment)


Steps to Reproduce

Following source code:

import 'package:flutter/material.dart';

const Color color = const Color.fromARGB(255, 100, 100, 100);

void main() =>
    runApp(
      new Container(
        color: const Color.fromARGB(255, 0, 0, 0),
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.end,
          textDirection: TextDirection.ltr,
          children: [
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
          ],
        ),
      ),
    );

produces following result:

Looks like background of the container is popping out and we see vertical lines. That should not be the case as all children of the row are Expanded and thus should fill the whole area.
If we remove one child lines are gone.

Logs

Launching lib/main.dart on Android SDK built for x86 in debug mode...
Initializing gradle...
Resolving dependencies...
Running 'gradlew assembleDebug'...
Built build/app/outputs/apk/app-debug.apk (22.4MB).
I/FlutterActivityDelegate( 8398): onResume setting current activity to this
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa057c5b0)
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
D/        ( 8398): HostConnection::get() New Host Connection established 0xa31a3640, tid 8416
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa3183790)
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c760: ver 3 1 (tinfo 0xa057cc10)
Syncing files to device Android SDK built for x86...

Flutter Doctor

[✓] Flutter (on Linux, locale en_US.UTF-8, channel master)
    • Flutter version unknown at <path_to_flutter>
    • Framework revision 5ae770345a (3 days ago), 2018-01-23 13:46:14 -0800
    • Engine revision 171d032f86
    • Tools Dart version 2.0.0-dev.16.0
    • Engine Dart version 2.0.0-edge.93d8c9fe2a2c22dc95ec85866af108cfab71ad06

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    • Android SDK at <path_to_android>
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-27, build-tools 27.0.3
    • ANDROID_HOME = <path_to_android>
    • Java binary at: <path_to_android-studio>/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Android Studio (version 3.0)
    • Android Studio at <path_to_android-studio>
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Connected devices
    • Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)

Another example (I believe it is somehow related: )

import 'package:flutter/material.dart';

const Color grey = const Color.fromARGB(255, 100, 100, 100);
const Color black = const Color.fromARGB(255, 0, 0, 0);

void main() =>
    runApp(
      new Container(
        color: grey,
        child: new Center(
          child: new Container(
            width: 151.0,
            height: 151.0,
            color: black,
            child: new Container(
              color: grey,
            ),
          ),
        ),
      ),
    );

We should not see border here. If widht/height are changed to 150.0, square is gone.

This is normal behaviour. What's happening is that the boxes are not quite aligned with pixel boundaries, so there's some anti-aliasing happening on the boundaries, which involves transparency, which means that for those pixels the two grays are overlapping and looking darker.

As a general rule when doing anti-aliasing you want to avoid putting identically-coloured boxes adjacent or over each other unless you can guarantee physical pixel alignment.

Alternatively, you can use saveLayer (or RepaintBoundary) to cause a bunch of paint operations to get merged into one and composited as one. Not sure that that would help in these cases specifically but it is a tool that can be useful in this kind of situation.

This is not boxes overlapping, but rather spare space between boxes, so color of background is popping up. I was changing background to different color and this color was popping out.

Root cause is that boxes can not be aligned with physical pixels. I would not call it "normal", I would rather call it "expected".
On android similar (semantically) case is handled properly:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="#000000"
    tools:context="com.radzish.android_lines_bug.MainActivity">

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

</LinearLayout>

So I think flutter should improve in this case.
The only workaround I found for me at the moment is sizing children manually like this:

    int CHILDREN_COUNT = 7;
    List<Widget> children = new List(CHILDREN_COUNT);

    MediaQueryData mediaQueryData = MediaQuery.of(context);
    int physicalWidth = (mediaQueryData.size.width * mediaQueryData.devicePixelRatio).floor();

    for (int i = 0, pixelsLeft = physicalWidth; i < CHILDREN_COUNT; i++) {
      int columnWidth = (pixelsLeft / (CHILDREN_COUNT - i)).floor();

      children[i] = new Container(
        width: columnWidth / mediaQueryData.devicePixelRatio,
        color: color,
      );

      pixelsLeft -= columnWidth;
    }

@radzish what is Android doing to avoid the problem?

These comments have suggestions for things to put in documentation:
#14288 (comment)
#17084 (comment)
#15035 (comment)

Any updates here? Because this is very annoying((
Or any examples how to use saveLayer or RepaintBoundary?

Any updates here?

Any updates here?

I have the problem too :(

Hi @blackraven96 the easiest workaround to this problem is to avoid using the same background color on adjacent elements, but use a background color for the whole list (or multi-child widget).

import 'package:flutter/material.dart';

void main() =>
    runApp(
        Container(
            color: Colors.white,
            child: Column(
                children: <Widget>[
                    Container(
                        child: Text("Item 1"),
                    ),
                    Container(
                        child: Text("Item 2"),
                    ),
                ],
            ),
        ),
    );

I hope this problem can be solved soon

I'm new to Flutter and I faced really quickly with this issue. I need to render a NxN grid of squares and the gaps between cells is quite annoying. Until there is a fix or a better workaround I'm using this delegate for CustomSingleChildLayout that removes the minimum number of pixels to avoid the gaps.

class GridFix extends SingleChildLayoutDelegate {
  GridFix(this.size, this.mediaQueryData);

  final int size;
  final MediaQueryData mediaQueryData;

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    var dpr = mediaQueryData.devicePixelRatio;
    var width = (((constraints.maxWidth * dpr) ~/ size) * size) / dpr;
    var height = (((constraints.maxHeight * dpr) ~/ size) * size) / dpr;
    return BoxConstraints.tightFor(width: width, height: height);
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    return Offset(
      ((size.width - childSize.width) / 2).floorToDouble(),
      ((size.height - childSize.height) / 2).floorToDouble(),
    );
  }

  @override
  bool shouldRelayout(GridFix oldDelegate) {
    return oldDelegate.size != size;
  }
}

Then within the layout I use it like this:

return AspectRatio(
  aspectRatio: 1,
  child: CustomSingleChildLayout(
    delegate: GridFix(size, MediaQuery.of(context)),
    child: /* insert content here */
  ),
);

Of course this solution is specific for this case of a square grid and it works because I can spare some pixels around without affecting the layout too much. Again, I'm new to Flutter so I'm open to improvements or suggestions.

At the end I think Flutter layouts could always (or optionally) round the calculated size and offsets to the nearest physical pixel to to avoid this issue.

Please also check some workaround discussions in #25009

testing the code above
the issue still occurs with the latest master

screenshot

image

doctor
[✓] Flutter (Channel master, 1.21.0-6.0.pre.38, on Mac OS X 10.15.5 19F101, locale en-GB)
    • Flutter version 1.21.0-6.0.pre.38 at /Users/nevercode/development/flutter_master
    • Framework revision 8e0eee9008 (7 hours ago), 2020-07-26 23:38:01 -0700
    • Engine revision 626244a72c
    • Dart version 2.9.0 (build 2.9.0-21.0.dev a3815b6590)


[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    • Android SDK at /Users/nevercode/Library/Android/sdk
    • Platform android-29, build-tools 29.0.2
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.5)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.5, Build version 11E608c
    • CocoaPods version 1.9.0

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.0)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 46.0.2
    • Dart plugin version 193.7361
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] VS Code
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.12.2

[✓] Connected device (3 available)
    • macOS (desktop)  • macos      • darwin-x64     • Mac OS X 10.15.5 19F101
    • Web Server (web) • web-server • web-javascript • Flutter Tools
    • Chrome (web)     • chrome     • web-javascript • Google Chrome 84.0.4147.89

• No issues found!

In the meantime, I found a very simple workaround. It has a downside, though: it doesn't work 100% time of scrolling... :/

Just add this to each tile Container in the GridView/SliverGrid:

decoration: BoxDecoration(
              color: Colors.black,
              borderRadius:
                  BorderRadius.only(topRight: Radius.circular(1)),
            ),

Before (without the rounded corner)

Captura de pantalla 2020-08-04 a las 14 57 21

After

Captura de pantalla 2020-08-04 a las 14 53 03

Never mind my previous workaround since it doesn't work 100%.

@dhawalmehta22 found a fix (see original post: #25009) that works most of the time, although this doesn't work at all time, either :(

Just wrap the problematic container with another container and add this to the parenting container:

decoration: BoxDecoration(
             color: Colors.black, //the color of the main container
             border: Border.all(
               //apply border to only that side where the line is appearing i.e. top | bottom | right | left.
               width: 2.0, //depends on the width of the unintended line
               color: Colors.black,
             ),
           ),

Is there an official solution to this problem ?

Let me add that this is not a problem of same color. I get the same result when trying to align multiple Positioned in a stack with different colors the moment the tile size isn't a full pixelsize you get this here:

grafik

Altough the y values of top+hight is the same as the next top value at some of these bounderies you have a gap where the black backgound is shining though.

this is the code:

class CheckerBoard extends StatelessWidget {
  const CheckerBoard({
    Key key,
    @required this.tileSize,
  }) : super(key: key);

  final double tileSize;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        for (var x = 0; x < 20; x++)
          for (var y = 0; y < 20; y++)
            Positioned(
              top: y * tileSize,
              left: x * tileSize,
              width: tileSize,
              height: tileSize,
              child: ColoredTile(
                indexX: x,
                indexY: y,
                top: y * tileSize,
                height: tileSize,
              ),
            )
      ],
    );
  }
}

class ColoredTile extends StatelessWidget {
  const ColoredTile({
    Key key,
    @required this.indexX,
    @required this.indexY,
    this.top,
    this.height,
  }) : super(key: key);

  final int indexX;
  final int indexY;
  final double top;
  final double height;

  @override
  Widget build(BuildContext context) {
    Color color;
    if (indexX.isEven && indexY.isEven) {
      color = Colors.blue;
    }
    if (indexX.isEven && indexY.isOdd) {
      color = Colors.red;
    }
    if (indexX.isOdd && indexY.isEven) {
      color = Colors.green;
    }
    if (indexX.isOdd && indexY.isOdd) {
      color = Colors.orange;
    }

    final m = MediaQuery.of(context);
    return Container(
      // clipBehavior: Clip.hardEdge,
      decoration: BoxDecoration(
        color: color,
      ),
//      child: Image.network('https://picsum.photos/256'),
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Stack(
          children: [
            Align(
              alignment: Alignment.topCenter,
              child: Text(
                'Top: ${top * m.devicePixelRatio} \n Height: ${height * m.devicePixelRatio}',
                style: TextStyle(color: Colors.black, fontSize: 12),
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Text(
                'Top+Hight: ${(top + height) * m.devicePixelRatio}',
                style: TextStyle(color: Colors.black, fontSize: 12),
              ),
            ),
            Align(
              alignment: Alignment.center,
              child: Text(
                '$indexX/$indexY',
                style: TextStyle(color: Colors.black),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

this happens on Windows and Android. with latests master 1.26.0-2.0.pre.222

Never mind my previous workaround since it doesn't work 100%.

@dhawalmehta22 found a fix (see original post: #25009) that works most of the time, although this doesn't work at all time, either :(

Just wrap the problematic container with another container and add this to the parenting container:

decoration: BoxDecoration(
             color: Colors.black, //the color of the main container
             border: Border.all(
               //apply border to only that side where the line is appearing i.e. top | bottom | right | left.
               width: 2.0, //depends on the width of the unintended line
               color: Colors.black,
             ),
           ),

Tried my solution in several projects and it worked perfectly.! Well, Working around is the only way till flutter resolves this bug.

@dhawalmehta22 It may have worked on certain resolutions. As I say it does work in some cases; however, I can assure you it doesn't work on all resolutions. If I remember correctly, I was figured it out it by changing the window size of a browser on my macOS—that way you can see when the bug happens.

Reproduces on various platforms - Android, Web Windiws, Desktop Windows

code sample
import 'package:flutter/material.dart';

Future<void> main() async {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: CustomPaint(
          painter: MyCustomPainter(),
          child: SizedBox.expand(),
        ),
      ),
    ),
  );
}

class MyCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    int tilesInWidth = 20;
    int tilesInHeight = 25;
    double tileSize = 16.0;

    canvas.translate(1, 1); // if you remove this it looks fine

    for (int x = 0; x < tilesInWidth; x++) {
      for (int y = 0; y < tilesInHeight; y++) {
        canvas.drawRect(
          Rect.fromLTWH(x * tileSize, y * tileSize, tileSize, tileSize),
          Paint()..color = Colors.blue,
        );
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
flutter doctor -v
[✓] Flutter (Channel stable, 1.22.5, on macOS 11.1 20C69 darwin-x64, locale en-GB)
    • Flutter version 1.22.5 at /Users/tahatesser/Code/flutter_stable
    • Framework revision 7891006299 (6 weeks ago), 2020-12-10 11:54:40 -0800
    • Engine revision ae90085a84
    • Dart version 2.10.4

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Volumes/Extreme/SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = /Volumes/Extreme/SDK
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
    • Xcode at /Volumes/Extreme/Xcode.app/Contents/Developer
    • Xcode 12.3, Build version 12C33
    • CocoaPods version 1.10.1

[!] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.52.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.18.1

[✓] Connected device (1 available)
    • Taha’s iPhone (mobile) • 00008020-001059882212002E • ios • iOS 14.3

! Doctor found issues in 1 category.
[✓] Flutter (Channel beta, 1.25.0-8.3.pre, on macOS 11.1 20C69 darwin-x64, locale en-GB)
    • Flutter version 1.25.0-8.3.pre at /Users/tahatesser/Code/flutter_beta
    • Framework revision 5d36f2e7f5 (3 days ago), 2021-01-14 15:57:49 -0800
    • Engine revision 7a8f8ca02c
    • Dart version 2.12.0 (build 2.12.0-133.7.beta)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Volumes/Extreme/SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = /Volumes/Extreme/SDK
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
    • Xcode at /Volumes/Extreme/Xcode.app/Contents/Developer
    • Xcode 12.3, Build version 12C33
    • CocoaPods version 1.10.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.52.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.18.1

[✓] Connected device (2 available)
    • Taha’s iPhone (mobile) • 00008020-001059882212002E • ios            • iOS 14.3
    • Chrome (web)           • chrome                    • web-javascript • Google Chrome 87.0.4280.141

• No issues found!
[✓] Flutter (Channel dev, 1.26.0-8.0.pre, on macOS 11.1 20C69 darwin-x64, locale en-GB)
    • Flutter version 1.26.0-8.0.pre at /Users/tahatesser/Code/flutter_dev
    • Framework revision b9d06fffb2 (10 days ago), 2021-01-07 18:36:48 -0800
    • Engine revision 42a8d4c681
    • Dart version 2.12.0 (build 2.12.0-179.0.dev)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Volumes/Extreme/SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = /Volumes/Extreme/SDK
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 12.3)
    • Xcode at /Volumes/Extreme/Xcode.app/Contents/Developer
    • Xcode 12.3, Build version 12C33
    • CocoaPods version 1.10.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.52.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.18.1

[✓] Connected device (3 available)
    • Taha’s iPhone (mobile) • 00008020-001059882212002E • ios            • iOS 14.3
    • macOS (desktop)        • macos                     • darwin-x64     • macOS 11.1 20C69 darwin-x64
    • Chrome (web)           • chrome                    • web-javascript • Google Chrome 87.0.4280.141

• No issues found!
[✓] Flutter (Channel master, 1.26.0-2.0.pre.402, on macOS 11.1 20C69 darwin-x64, locale en-GB)
    • Flutter version 1.26.0-2.0.pre.402 at /Users/tahatesser/Code/flutter_master
    • Framework revision 2a188eeca3 (8 hours ago), 2021-01-17 19:27:00 -0800
    • Engine revision 609036f2bf
    • Dart version 2.12.0 (build 2.12.0-236.0.dev)

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /Volumes/Extreme/SDK
    • Platform android-30, build-tools 30.0.3
    • ANDROID_HOME = /Volumes/Extreme/SDK
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS
    • Xcode at /Volumes/Extreme/Xcode.app/Contents/Developer
    • Xcode 12.3, Build version 12C33
    • CocoaPods version 1.10.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)

[✓] VS Code (version 1.52.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.18.1

[✓] Connected device (3 available)
    • Taha’s iPhone (mobile) • 00008020-001059882212002E • ios            • iOS 14.3
    • macOS (desktop)        • macos                     • darwin-x64     • macOS 11.1 20C69 darwin-x64
    • Chrome (web)           • chrome                    • web-javascript • Google Chrome 87.0.4280.141

• No issues found!

Having the same issue, 2 containers in column with same color showing a really fine line between them, manually setting border widths didn't help.

I had same issue some time ago with Row, I also was impelementing calendar for range picking. I found out that main problem was that width of container was not divisible by number of columns. So I did a small trick: added padding in outer container, so that width of container would be divisible by number of columns. Here is the example:

final numberOfColumns = 7;
final screenWidth = MediaQuery.of(context).size.width; // In my case row was full width of scree

final remainder = screenWidth % numberOfColumns;
var padded = screenWidth - remainder; // padded is width that is divisible by numberOfColumns

// This while is needed for padding to be not less than 30 pixels (by design). You may not doing this, if you don't need extra padding
while (screenWidth - padded < 30) {
  padded -= numberOfColumns; // subtract number of columns to keep padded divisible by columns
}

final containerPadding = screenWidth - padded; // compute padding

return Padding(
  padding: EdgeInsets.symmetrical(horizontal: containerPadding / 2), // MAIN LINE!
  child: Row(
    children: List.generate(numberOfColumns, (index) => Expanded(child: Container(color: Colors.red))),
  ),
);
commented

I have another simple example
item height: 20.0 -> no problem
item height: 20.1, 20.2, 20.3 -> gap without scroll + moving gap with scroll
item height: 20.01, 20.0001 -> no gap without scroll + moving gap with scroll

Screen_Recording_20210306-013814.mp4
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        backgroundColor: Colors.white,
        body: SafeArea(
          child: SingleChildScrollView(
            child: Column(
              children: [
                buildListView(20),
                buildListView(20.1),
                buildListView(20.2),
                buildListView(20.3),
                buildListView(20.01),
                buildListView(20.001),
              ],
            ),
          ),
        ),
      ),
    );
  }

  ListView buildListView(double itemHeight) {
    return ListView.builder(
      shrinkWrap: true,
      padding: EdgeInsets.symmetric(vertical: 10),
      physics: NeverScrollableScrollPhysics(),
      itemCount: 10,
      itemBuilder: (context, index) => Container(
        height: itemHeight,
        color: Color(0xFF888888),
        child: Text('$itemHeight - $index'),
      )
    );
  }
}

Any update on this 2 years old issue? Ran into similar weirdness too:
Code example: https://gist.github.com/Moonspeaker/0e8573ff6620a7e00b8f7b04937b51a1
This is how it looks on any flutter branch: https://www.youtube.com/watch?v=DDxu1NTkaMA&feature=youtu.be

Reproducible on 2.0.4 #80330 (comment)

Are there any plans to fix this or provide some recommended workarounds?

I have another simple example
item height: 20.0 -> no problem
item height: 20.1, 20.2, 20.3 -> gap without scroll + moving gap with scroll
item height: 20.01, 20.0001 -> no gap without scroll + moving gap with scroll

Screen_Recording_20210306-013814.mp4

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      home: Scaffold(
        backgroundColor: Colors.white,
        body: SafeArea(
          child: SingleChildScrollView(
            child: Column(
              children: [
                buildListView(20),
                buildListView(20.1),
                buildListView(20.2),
                buildListView(20.3),
                buildListView(20.01),
                buildListView(20.001),
              ],
            ),
          ),
        ),
      ),
    );
  }

  ListView buildListView(double itemHeight) {
    return ListView.builder(
      shrinkWrap: true,
      padding: EdgeInsets.symmetric(vertical: 10),
      physics: NeverScrollableScrollPhysics(),
      itemCount: 10,
      itemBuilder: (context, index) => Container(
        height: itemHeight,
        color: Color(0xFF888888),
        child: Text('$itemHeight - $index'),
      )
    );
  }
}

计算高度的时候使用以下代码可以解决这个问题
(itemHeight * WidgetsBinding.instance.window.devicePixelRatio).round() / WidgetsBinding.instance.window.devicePixelRatio

commented

@WangYng Thanks for workaround. This looks good for fixed height or known height. But my actual case is dynamic height items in listview. Additionally, I use package https://pub.dev/packages/responsive_framework which make entire screen scales (decimal point). It still looks that needs fundamental fix on flutter level.

While I understand why this is happening (antialiasing due to pixel alignment), I disagree that this is not a bug.

If I butt two containers up against each other I should not have to be concerned with pixel boundaries. I know this is 'expected' behaviour due to the rendering techniques used in Flutter, but it's a really fundamental rendering issue if you can see a gap between boxes that do not actually have any space between them.

The video above from @wonpyohong demonstrates very simply that the rendering system is failing to render objects next to each other, it doesn't matter that there's an expected reason for it.

While I understand why this is happening (antialiasing due to pixel alignment), I disagree that this is not a bug.

If I butt two containers up against each other I should not have to be concerned with pixel boundaries. I know this is 'expected' behaviour due to the rendering techniques used in Flutter, but it's a really fundamental rendering issue if you can see a gap between boxes that do not actually have any space between them.

The video above from @wonpyohong demonstrates very simply that the rendering system is failing to render objects next to each other, it doesn't matter that there's an expected reason for it.

Of course it is a bug.
When can we expect a solution after more than 3 years?

It's not just a bug. Unfortunately, it is also an impediment to adopting the technology in many scenarios.

So, basically we have this non-sensical bug (normal behavior but not expected by anyone who place solid color objects next or in front of others) for 3 years and with no expectation to be solved or given any reasonable workaround?

Nice. I am afraid @caduandrade is right.

Any fix?

While waiting for Google to fix this issue, you can try using the solution @paulosigales provided.
#37578 (comment)

commented

Having the same problem. I get that this is not a simple thing to fix, but how about a flutter-team-endoresed work around that works for the general case where I can't be bothered to count the pixels on the screen to prevent a framework/rendering-engine level problem from messing up the apprearince of my app? Also, why is this not a problem when putting white containers next to one another?

commented

So first let me answer my own question: I didn't have any problems with white contaiers because that was the default background color of the parent widget. This also leads us to a not-great-but-servisable-under-certain-circumstances work around option, which is to set the background color to the color of the containers. In my case this just involved setting the background color of the scaffold to that of my child containers, but its still a little sucky because in my case the container colors (for some containers) are dynamic. I really think a medium article aknowlaging this, explaining why its not work fixing/changing and outligning best practices to avoid it for different use cases would go a long way doward lowaring frustration levels for all the users that managed to read to here...

Good point @eduardkieser ! That's exactly why I created a proposal here: #62818

Closing #37578

I will repost my example & info here again.

Using slivers results in a 1-pixel gap between slivers.

You can see the 1px gap in the image below. I got this problem in every app I wrote with flutter. It is very annoying. And should really be fixed. Or maybe I am doing something wrong?

Could be related:
letsar/flutter_sticky_header#8
#14288

This is the code used to get reproduce this issue:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black12,
      body: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: Container(
              height: 150,
            ),
          ),
          SliverToBoxAdapter(
            child: Container(
              decoration: BoxDecoration(
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(ThemeDimens.largeBorderRadius),
                  topRight: Radius.circular(ThemeDimens.largeBorderRadius),
                ),
                color: Colors.white,
              ),
              child: Padding(
                padding: const EdgeInsets.all(2),
                child: Container(
                  height: 16,
                ),
              ),
            ),
          ),
          SliverToBoxAdapter(
            child: Container(
              color: Colors.white,
              height: 5000,
            ),
          ),
        ],
      ),
    );
  }

This bug heavily affects the usability of Tiled in Flame.

tiled.mp4

It's a pity that the flutter team has not implemented at least this proposal since it would be a pretty good workaround in the meantime they fix the root of the issue.

Just to be clear, is that a skia issue or a flutter issue?

If it's a flutter issue we could try to identify where exactly it comes from and try to solve it ourselves.

Having a similar issue,
in code, it's a Container with an image as a child, and a LinearGradient as foregroundDecoration:

Container(
  foregroundDecoration: BoxDecoration(
    gradient: buildGradient(), // LinearGradient
  ),
  child: image,
);

This container grows/shrinks using a drag gesture,
when idle, the gradient covers everything just nicely, but while dragging, there are rendering artefacts: the gradient seems to sometimes be smaller than the image: (sometimes on the left, sometimes at the bottom, sometimes both sides)

image

the idea using stack overflow clip
https://pub.dev/packages/space_fixer
maybe helpful :)

Hi @Hixie what's the official workaround for this problem or is there a chance it will get fixed?

Regarding workarounds, it really depends on the specific cause. If it's something like the OP's case, where you have adjacent boxes that aren't quite pixel aligned (e.g. because they're in a scrolling list being animated by a curve, or because they're rotated, or some such) then the solution is to find a way to avoid having adjacent boxes the same color, but to instead merge the boxes into one. (We do something similar in the MergeableMaterial widget.)

Another solution is to composite all the boxes together first (e.g. using a RepaintBoundary) and then have that widget paint onto the background.

Another solution, which works only really when you have tight control over the positioning (so likely not when doing scrolling, for instance) is to make sure the boxes are precisely aligned on physical pixel boundaries.

Regarding a fix, fundamentally this isn't really a bug per se, it's intentional (it's actually what you want most of the time, because most of the time you don't have these identically-coloured boxes that touch, but you do want the edges anti-aliased against the background). Obviously, though, it's not a pleasant developer experience, which is why the bug is still open. Exactly how to "fix" it is not obvious, which is why we haven't made any progress on it.

One thing we should do is update the documentation. These comments have suggestions for things we should say:

commented

workaround: use CustomPaint for each item.
CustomPaint can draw outside box.
If you have a horizontal divider gap between item,

Widget buildItemPaddedGap() {
  return CustomPaint(
    foregroundPainter: GapPainter(color)
    child: buildItemForListView()
  );
}

class GapPainter extends CustomPainter {
  ...
  @override
  void paint(Canvas canvas, Size size) {
    final rect = Rect.fromLTRB(0, -1, size.width, 0);    // -1 means outside. or you can use (size.height) and (size.height+1)
    canvas.drawRect(rect, gapPaint);    // of course painter should have color
  }
  ...
}

You can resolve vertical divider gap using -1 similarly.
The color should not have alpha value

Why not give the developers an API that can close the antialiasing?

@Hixie Would it be technically feasible to make a widget basically delay antialiasing?

From a developper/designer point of view that would be the expected behavior for any "list" widget such as Row, Column and such (not sure if it would apply to Stack tho).

When using that type of widget, we expect the children to be at the same depth, by that I mean we expect that the column is like a photoshop layer, we layout the children on it, on top of each other if needed, and then we render the entire layer at once. In that case the antialiasing should happen when the entire layer is laid out and not when each individual element is added.

I think it could be a great addition as right now from a layout standpoint, unless you use a CustomPainter, it's impossible to really have multiple widgets rendered as one.

I'm not 100% sure about the current rendering process, so don't hesitate to tell me if I'm wrong ;)

Here is the behavior for a simple case

Column(
    children: [
        Primitive1(),
        Primitive2(),
    ],
)

Currently I'm assuming the layout will be somewhat as follow (very approximative pseudocode)

Column.Layout()
|   Primitive1.Layout()
|   Primitive2.Layout()
...
Column.Render()
|   Primitive1.Render()
|   |    shape.Draw()
|   |    antialiasing()
|   Primitive2.Render
|   |    shape.Draw()
|   |    antialiasing()

The new behavior would be:

Column.Layout()
|   Layer.AddAll([
|       Primitive1.Layout(),
|       Primitive2.Layout(),
|   ])
...
Column.Render()
|   Layer.Render()
|   |    shape[0].Draw()
|   |    shape[1].Draw()
|   |    antialiasing()

Note: I'm assuming that the Draw() is done accurately (fractions of pixels) which the antialiasing then uses to blend the pixels together.

If you put the shapes into the same layer (without a background) and then composite that entire layer at once (e.g. using a repaint boundary) then that might do that, as discussed in the earlier #14288 (comment). But in some cases it still won't work because fundamentally the shapes are being antialiased into the same pixel so by definition at some point there's a shape being antialiased into a pixel that already has some color. The real solution if you don't want seams is to not have adjacent identically-coloured shapes with subpixel alignment (again, see the earlier comment for details). The same can happen in other frameworks, it's not Flutter-specific.

@Hixie Did the new impeller engine fix this problem/expected behavior?

flutter run

flutter run --enable-impeller

We believe this is caused by Impeller using MSAA, which essentially means we don't do traditional style antialiasing at all. It's more expensive (you have to do more calculations per pixel for edge pixels), but it may indeed resolve this particular issue in some cases. We're still experimenting.

Thanks for the explanation! I hope the Flutter Team takes this issue into account when designing the new impeller engine. Even if this is expected behavior is not desired. Good behaved frameworks like Android and iOS native development do not suffer from this.

commented

Just out of interest, I think this may be related (as the problem goes away if I turn off anti-aliasing on some adjoining path/polygons ) but unclear as yet. I'm not expecting any action as it's quite a complex use case. But someone may have some ideas on how to get around it (other than my own experiments below), or it may help someone else with current workarounds.

https://youtube.com/shorts/3Gqm6H4rbeM

If you watch after a few seconds, you will see bad "corrupt" artifacts.

Basically it's some map tile positioned widgets, and on top a CustomPainter with some adjoining same color drawn polygons (so it looks like one poly with a couple of holes, but it's actually two adjoined polygons).

If I turn on antialiasing on the polygons, the problem goes away...however I get the hairline problem between the join of the 2 polys.
With antialiasing off, the bad corrupt artifacts appear, but the hairlines vanish.

There is also opacity on the background map tiles. If I turn off the opacity, the problem also goes away.

It doesn't seem to happen on the web, just some android versions so far.

If I can break it down into a simple test case, I'll post the code up.

As per #111037, this issue is still reproducible. Updating labels to reflect the same.

Will this be solved by Impeller?

This should be p3. For some apps where this appears as critical focus of the app this breaks immersion, which for all intents and purposes is a severe degradation in UX.

Interestingly enough, when using Flutter 3.3.1, this issue goes away. But comes back in every other version (at least the ones I tried).

@Hixie @TahaTesser Do you think it would be feasible to add an API/flag to enable/disable whatever commit broke the fix from 3.3.1?

I have no idea what could have changed the behaviour in 3.3.1, that's surprising. Maybe there was a slight change to the alignment transforms, or to antialiasing, or something similar, that regressed other behaviour but happened to by chance make this effect go away in certain cases.

This issue is unlikely to ever go away with our current graphics backend; it's core to the approach used. It is looking likely that our antialiasing approach in our new graphics backend will cause this issue to go away, though. No current ETA, but hopefully we'll start shipping it by default on some platforms sometime next year. No promises, and to be clear, this is one of those things where there will likely be trade-offs, so while this issue may go away, we may get new kinds of issues instead as a result of the new approach. :-)

I have no idea what could have changed the behaviour in 3.3.1, that's surprising. Maybe there was a slight change to the alignment transforms, or to antialiasing, or something similar, that regressed other behaviour but happened to by chance make this effect go away in certain cases.

This issue is unlikely to ever go away with our current graphics backend; it's core to the approach used. It is looking likely that our antialiasing approach in our new graphics backend will cause this issue to go away, though. No current ETA, but hopefully we'll start shipping it by default on some platforms sometime next year. No promises, and to be clear, this is one of those things where there will likely be trade-offs, so while this issue may go away, we may get new kinds of issues instead as a result of the new approach. :-)

Ah, I was hoping to have a simple on/off toggle for whatever got rid of the fix in 3.3.2.

Impeller is unstable, so we can't use that. And client has been suffering from this issue and complaining about it for the past year, so I guess they can wait another year. 😅

I even tried all sorts of hacks (like repaint boundary). My favorite solution was to stack the same widget behind itself with 1px y axis offset, but that increased the memory usage 2x. And since the widget is extremely heavy and taxing, it's not a viable solution according to the client. 🤷

Thanks for the clarification, Hixie. Love ya. 🥰

In most cases the solution is "simply" to not put two boxes with the same background next to each other, but to wrap those widgets with a single box that paints the background all at once. Sometimes that's tricky, granted.

In most cases the solution is "simply" to not put two boxes with the same background next to each other, but to wrap those widgets with a single box that paints the background all at once. Sometimes that's tricky, granted.

Unfortunately, in my case, sometimes they have to be the same background. They also have to be wrapped in an interactive viewer which further complicates things.

would this be part of the same issue ? image in container has slight border on left
image

If you have this problem give pixel_snap a go. It still pretty experimental but I tested it on the examples here and it fixed all of them.

flutter pub add pixel_snap

And then replace import 'package:flutter/material.dart' with import 'package:pixel_snap/material.dart'.

As per #14288 (comment), I think this can be categorized as a workaround. Adding the relevant label

This bug should have way higher priority.

There is a lot of app designs that require for example network images to be in a listview next to each other.
Imagine items with fullscreen photos next to each other in a scrollable list.
You have no control over what color the images will have, so you cannot simply choose a single color background because the images could have inverse colors.

And when you want to show how easily you can create scrollable list of items in flutter, it is one of the first bugs you encounter, because if you use container with background color as items, the scaffold will have white color as default, so there will be subpixel white gaps everywhere...

This is not something people should be doing workaround for.

Latest status is that we believe Impeller fixes this for some cases. ETA is sometime this year for iOS, sometime in the next 18 months for Android, sometime in the next 2 or 3 years for other platforms, probably? But don't quote me on that, this is highly experimental work being built by people from various companies and by volunteers so scheduling is non-trivial. For cases not fixed by Impeller this will probably never be fixed because it is a fundamental artifact of how computer graphics work. We could create APIs that help you work around this (e.g. IIRC Flash had a way of specifying an "outer" color for a line so that it would antialias with the "right" color), but those would not be automatic and if one is willing to adjust one's code to avoid this problem then it is already possible to do so by refactoring the code to not have the seams in the first place.

This bug should have way higher priority.

There is a lot of app designs that require for example network images to be in a listview next to each other. Imagine items with fullscreen photos next to each other in a scrollable list. You have no control over what color the images will have, so you cannot simply choose a single color background because the images could have inverse colors.

And when you want to show how easily you can create scrollable list of items in flutter, it is one of the first bugs you encounter, because if you use container with background color as items, the scaffold will have white color as default, so there will be subpixel white gaps everywhere...

This is not something people should be doing workaround for.

Agree...

In many way flutter is awe inspiring in what it can do and with easy to learn and use dart.

But in other ways, its mind boggling that some of the basics have obviously visible flaws that make an app look careless and buggy. This being one of those cases

Luckily I've been using above mentioned pixelsnap and I have to say its so far solved all my problems. Will try out impeller when it hits the stable channel to see if it also fixes the issues 👍

Since this issue has been around since 2018, the most realistic thing to do is use pixel snap and just hope the maintainer keeps it going through the future flutter updates

Any updates?

commented

Same with me while using multi slivers, any updates 👀 ?
image

Same issue when using an ImageShader with a repeating pattern:

ImageShader(
    textureImage!,
    TileMode.repeated,
    TileMode.repeated,
    Float64List.fromList(transformValues),
    filterQuality: FilterQuality.high);
)

faded lines appear between the tiles

I see the same issue in a Column where children have the same background color. This should be a very high priority bug for the Flutter team! The only code workarounds are awful anti-patterns. Please fix this!!

Latest non-update update: #14288 (comment); some work around suggestions: #14288 (comment)

Steps to Reproduce

Following source code:

import 'package:flutter/material.dart';

const Color color = const Color.fromARGB(255, 100, 100, 100);

void main() =>
    runApp(
      new Container(
        color: const Color.fromARGB(255, 0, 0, 0),
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.end,
          textDirection: TextDirection.ltr,
          children: [
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
          ],
        ),
      ),
    );

produces following result:

Looks like background of the container is popping out and we see vertical lines. That should not be the case as all children of the row are Expanded and thus should fill the whole area. If we remove one child lines are gone.

Logs

Launching lib/main.dart on Android SDK built for x86 in debug mode...
Initializing gradle...
Resolving dependencies...
Running 'gradlew assembleDebug'...
Built build/app/outputs/apk/app-debug.apk (22.4MB).
I/FlutterActivityDelegate( 8398): onResume setting current activity to this
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa057c5b0)
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
D/        ( 8398): HostConnection::get() New Host Connection established 0xa31a3640, tid 8416
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa3183790)
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c760: ver 3 1 (tinfo 0xa057cc10)
Syncing files to device Android SDK built for x86...

Flutter Doctor

[✓] Flutter (on Linux, locale en_US.UTF-8, channel master)
    • Flutter version unknown at <path_to_flutter>
    • Framework revision 5ae770345a (3 days ago), 2018-01-23 13:46:14 -0800
    • Engine revision 171d032f86
    • Tools Dart version 2.0.0-dev.16.0
    • Engine Dart version 2.0.0-edge.93d8c9fe2a2c22dc95ec85866af108cfab71ad06

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    • Android SDK at <path_to_android>
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-27, build-tools 27.0.3
    • ANDROID_HOME = <path_to_android>
    • Java binary at: <path_to_android-studio>/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Android Studio (version 3.0)
    • Android Studio at <path_to_android-studio>
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Connected devices
    • Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)

Seems to be fixed in Flutter 3.10 with Impeller !! :D

Simulator Screenshot - iPhone 14 - 2023-05-10 at 18 51 47

MSAA in this case seem to be working pretty well indeed! Though I'm a bit concerned about MSAA support on older Android devices.

I'm on the latest 3.10.1. and it happens on web if scaling isn't 100%

gnome-shell-screenshot-J5VW41

This is a sliverappbar with a rounded bottom, but obviously it happens without the curved bottom if app bar and content are the same color. very annoying! I can't ship this.

@sgehrman, the reason why this happens is that the scroll position directly affect the sliverappbar size and the position is not rounded to physical pixels. It would not be present with impeller since these artifacts don't happen with MSAA, but we're probably pretty far from impeller on web. PixelSnap in latest version now has PixelSnapScrollController, which you can use with any scrollable view to ensure that the scroll position is always snapped to physical pixels. That should fix your issue on all platforms (although there might be other widgets in that need pixel snapping as well).

I had this problem in my SliverAppBar and my workaround was hiding the line by using a shadow of the same color:

SliverAppBar(
  ...
  bottom: PreferredSize(
    preferredSize: const Size.fromHeight(2),
    child: Container(
        height: 2,
        decoration: BoxDecoration(boxShadow: [
          BoxShadow(
            color: Colors.blue, # or whatever the color of your AppBar is
            offset: const Offset(0, 1),
          ),
        ]))),
  ...
)
                      

Unfortunately for me, the workaround where we're supposed to put all children in one container with the same background color is not ok, because I have a column with three children, the middle child has a special ClipPath and it's not possible for me to clip the parent container so that it matches that child. Hopefully this gets resolved sometime soon.

Edit: Also I don't think that this has anything to do with the same color as it is said in the title of the issue. I tried using different colors and the issue persists.

commented

This issue happens to me when I apply Zoom Drawer library. Any idea for this?
Screenshot_20230926_093912

Is there an update? I'm still experiencing errors on version 3.13.4.

Same issue on 3.13.7

Update: I found a solution, below SizedBox is a item in ListView
SizedBox(height: h, child: images[index]); White Gap
SizedBox(height: h.toInt().toDouble(), child: images[index]); No Gap

I don't know why, but it works well for me

image
3.16.3 版本仍然存在, 这种问题什么时候能解决,是不打算修复了? 导致项目体验非常差

Any update on this issue? I tried all above workaround and none did the trick for me. I still get the white line between the app bar and body.

Latest status is that we believe Impeller fixes this for most cases. Impeller is enabled by default on iOS, and we hope it will be enabled this year in Android, and sometime in the next 2 or 3 years for other platforms, though there's some question about exactly what we should do on desktop. (The schedule is not set in stone; this is experimental work being built by people from various companies and by volunteers so scheduling is non-trivial.)

For those cases not fixed by Impeller this will probably never be fixed because it is a fundamental artifact of how computer graphics work, but if there are specific cases you are running into that still have this artifact please file specific bugs with test cases demonstrating the problem so we can make sure we understand them fully before saying "no" for good. As I noted in an earlier comment, while we could create APIs that help you work around this (e.g. Flash had a way of specifying an "outer" color for a line so that it would antialias with the "right" color), those would not be automatic and if one is willing to adjust one's code to avoid this problem then it is already possible to do so by refactoring the code to not have the seams in the first place.

... because it is a fundamental artifact of how computer graphics work,

@Hixie sorry, but how native android view works without got this issue?

I somewhat disagree with @Hixie here. Yes, MSAA (impeller) will fix some of the cases, but fundamentally the issue here is that nothing in Flutter is snapped to physical pixels. Unlike web browsers for example, which snap both layout and dimensions - i.e. 1px solid black in browser will always be 1, 2 or 3 physical pixels exactly, even with fraction pixel ratio like 175%. Other frameworks, such as WPF have ways to snap layout to physical pixels as well (SnapsToDevicePixels).

We discussed it here but ultimately I failed to convince @Hixie that this is the way to for Flutter. It is also possible the change would be too disruptive at this stage anyway.

So instead I made pixel snap. It should solve pretty much all the issues in this thread, and doesn't require any changes in Flutter. The downside is that for pixel snapping to work, all widgets that affect layout in the application must be pixel snapped. Depending on application that might require some extra work.

@knopp Thank you for creating pixel_snap. Can you help show an example on how to use it with CustomScrollView with Slivers? (Related to: #37578). I tried something like the following, but the thin line still exists between my list of slivers:

import 'package:pixel_snap/pixel_snap.dart';
import 'package:pixel_snap/material.dart';

...

CustomScrollView(
  controller: PixelSnapScrollController(),
  slivers: [
    SliverAppBar(
        pinned: true,
        expandedHeight: ps(MediaQuery.of(context).size.height * 2 / 3),
        ...
    ),
    SliverPadding(
        padding: const EdgeInsets.all(16.0).pixelSnap(ps),
        sliver: SliverToBoxAdapter(child: Text(...)),
    ),
  ],
)

@knopp Thank you for creating pixel_snap. Can you help show an example on how to use it with CustomScrollView with Slivers? (Related to: #37578). I tried something like the following, but the thin line still exists between my list of slivers:

import 'package:pixel_snap/pixel_snap.dart';
import 'package:pixel_snap/material.dart';

...

CustomScrollView(
  controller: PixelSnapScrollController(),
  slivers: [
    SliverAppBar(
        pinned: true,
        expandedHeight: ps(MediaQuery.of(context).size.height * 2 / 3),
        ...
    ),
    SliverPadding(
        padding: const EdgeInsets.all(16.0).pixelSnap(ps),
        sliver: SliverToBoxAdapter(child: Text(...)),
    ),
  ],
)

If you can provide a reproducible example please create issue in pixel_snap repository and I'll take a look. The snippet that you have posted here looks good, but it is possible that the CustomScrollView itself is not pixel snapped. I don't know what widgets are around it that affect layout that's why a complete example is needed.

commented

There are many cases where adjacent boxes dont have the same color, and can not be merged.
(E.g. list items with selection state, or some highlighted items, or items with different heights)

Example with alternating color, and a timer that auto scrolls to show the gaps:

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: const Scaffold(
        body: MyWidget(),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late ScrollController _controller;
  late Timer _timer;
  double _scrollOffset = 0.0;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _timer = Timer.periodic(const Duration(seconds: 1), _scroll);
  }

  @override
  void dispose() {
    _timer.cancel();
    _controller.dispose();
    super.dispose();
  }

  void _scroll(Timer timer) {
    _scrollOffset += 0.25;
    _controller.jumpTo(_scrollOffset);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black,
      child: SingleChildScrollView(
        controller: _controller,
        child: Column(
          children: [
            for (int i = 0; i < 1000; i++) //
              Container(
                color: i % 2 == 0 ? Colors.blue : Colors.grey,
                height: 20,
                width: 100,
                child: Text("$i"),
              ),
          ],
        ),
      ),
    );
  }
}
Screen.Recording.2024-02-12.at.19.53.31.mov

And without auto scroll:

Screen.Recording.2024-02-12.at.19.54.02.mov

And without alternating colors, showing the color from behind:

Screen.Recording.2024-02-12.at.19.56.51.mov

Hello,

I'm dealing with the same problems as mention above, thin white lines between widgets (rows in my case), when rendering to web. It looks like an anti-alias or not-pixel-perfect issue, also as already mentioned above.

Have I understood it correctly that there is no proper/native solution to this, and the best option is to use pixel_snap?

UPDATE: I also tried building/running for Linux desktop, and I get the same white lines. But using a Scaffold with backgroundColor works as a workaround, which happens to be usable in this case.

Hello,

I'm dealing with the same problems as mention above, thin white lines between widgets (rows in my case), when rendering to web. It looks like an anti-alias or not-pixel-perfect issue, also as already mentioned above.

Have I understood it correctly that there is no proper/native solution to this, and the best option is to use pixel_snap?

UPDATE: I also tried building/running for Linux desktop, and I get the same white lines. But using a Scaffold with backgroundColor works as a workaround, which happens to be usable in this case.

Also you may try space_fixer https://pub.dev/packages/space_fixer

Example:

          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),
          SpaceFixerHorizontalLine(
            context: context,
            overflowHeight: 3,
            overflowColor: Colors.black,
          ),
          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),

https://github.com/erlangparasu/space_fixer/blob/e3a5b8f158f64507a486c1f8904431cb8c6ce773/example/example1.dart#L59

You can use this widget as a divider in the list

There are many cases where adjacent boxes dont have the same color, and can not be merged. (E.g. list items with selection state, or some highlighted items, or items with different heights)

[removed]

flutter pub add pixel_snap

Replace import 'package:flutter/material.dart' with import 'package:pixel_snap/material.dart'. Gaps disappear. In this case it would be beacuse the scroll controller from pixel snap snaps the position to physical pixels.

pixel_snap.mov

Same issue on 3.13.7

Update: I found a solution, below SizedBox is a item in ListView SizedBox(height: h, child: images[index]); White Gap SizedBox(height: h.toInt().toDouble(), child: images[index]); No Gap

I don't know why, but it works well for me

Why would i ever give fixed height to a list item.

Just wanted to share another case with you (#150035). It happens with Impeller on iOS too.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Positioned.fill(child: ColoredBox(color: Colors.blue)),
          Center(
            child: SizedBox(
              width: 200,
              height: 200,
              child: Stack(
                children: [
                  Positioned.fill(child: ColoredBox(color: Colors.red)),
                  Positioned.fill(
                    child: ColoredBox(color: Colors.blue),
                  )
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Is it a skia or a Flutter issue?

Hello,
I'm dealing with the same problems as mention above, thin white lines between widgets (rows in my case), when rendering to web. It looks like an anti-alias or not-pixel-perfect issue, also as already mentioned above.
Have I understood it correctly that there is no proper/native solution to this, and the best option is to use pixel_snap?
UPDATE: I also tried building/running for Linux desktop, and I get the same white lines. But using a Scaffold with backgroundColor works as a workaround, which happens to be usable in this case.

Also you may try space_fixer https://pub.dev/packages/space_fixer

Example:

          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),
          SpaceFixerHorizontalLine(
            context: context,
            overflowHeight: 3,
            overflowColor: Colors.black,
          ),
          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),

https://github.com/erlangparasu/space_fixer/blob/e3a5b8f158f64507a486c1f8904431cb8c6ce773/example/example1.dart#L59

You can use this widget as a divider in the list

thanks to you, I was able to easily solve the problem without much overhead. thank you so much