Skip to content

Glitches when scrolling on weak Android TV devices #113314

@DenisovAV

Description

@DenisovAV

Steps to Reproduce

Execute the Flutter app with complex graphics and scroll in release mode on an older device (reproduced on MiStick and MiBox, app example added)

Scroll to the bottom

Get the glitches and strange pixels artifacts

The issue is reproduced on flutter with version >= 3.0.0

Expected results: No artifacts or glitches

Actual results: Glitches and artifacts

The link to video

Code sample
import 'dart:math' as math;

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

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AndroidTv glitch POC',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const Scaffold(body: MyHomePage()),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: Colors.black12,
      child: Shortcuts(
        shortcuts: <ShortcutActivator, Intent>{
          LogicalKeySet(LogicalKeyboardKey.arrowUp): const _FocusUpIntent(),
          LogicalKeySet(LogicalKeyboardKey.arrowDown): const _FocusDownIntent(),
        },
        child: Actions(
          actions: <Type, Action<Intent>>{
            _FocusUpIntent: CallbackAction<_FocusUpIntent>(
              onInvoke: (intent) {
                _controller.animateTo(
                  _controller.offset - 150,
                  duration: const Duration(milliseconds: 50),
                  curve: Curves.easeIn,
                );
                return null;
              },
            ),
            _FocusDownIntent: CallbackAction<_FocusDownIntent>(
              onInvoke: (intent) {
                _controller.animateTo(
                  _controller.offset + 150,
                  duration: const Duration(milliseconds: 50),
                  curve: Curves.easeIn,
                );
                return null;
              },
            ),
          },
          child: CustomScrollView(
            controller: _controller,
            scrollDirection: Axis.vertical,
            slivers: [
              SliverList(
                delegate: SliverChildBuilderDelegate(
                  (context, rowIndex) {
                    return SizedBox(
                      height: 200,
                      child: ListView(
                        scrollDirection: Axis.horizontal,
                        children: [
                          // Text(rowIndex.toString()),
                          ...List.generate(
                            8,
                            (columnIndex) {
                              return const Item();
                            },
                          ),
                        ],
                      ),
                    );
                  },
                  childCount: 100,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _FocusDownIntent extends Intent {
  const _FocusDownIntent();
}

class _FocusUpIntent extends Intent {
  const _FocusUpIntent();
}

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

  @override
  Widget build(BuildContext context) {
    return Focus(
      child: SizedBox.fromSize(
        size: const Size(500, 200),
        child: CustomPaint(
          painter: BackgroundColor(const Size(500, 200)),
          isComplex: true,
          willChange: false,
        ),
      ),
    );
  }
}

class BackgroundColor extends CustomPainter {
  static const List<Color> colors = [
    Colors.orange,
    Colors.purple,
    Colors.blue,
    Colors.green,
    Colors.purple,
    Colors.red,
  ];

  final Size _size;
  BackgroundColor(this._size);

  @override
  void paint(Canvas canvas, Size size) {
    final rand = math.Random();

    for (var i = 0; i < 100; i++) {
      canvas.drawOval(
          Rect.fromCenter(
            center: Offset(
              rand.nextDouble() * _size.width - 100,
              _size.height / 2 + rand.nextDouble(),
            ),
            width: rand.nextDouble() * rand.nextInt(150) + 20,
            height: rand.nextDouble() * rand.nextInt(150) + 20,
          ),
          Paint()..color = colors[rand.nextInt(colors.length)].withOpacity(0.3));
    }
  }

  @override
  bool shouldRepaint(BackgroundColor oldDelegate) => false;
}
Logs
Running Gradle task 'assembleRelease'...                        
w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.5.30/5fd47535cc85f9e24996f939c2de6583991481b0/kotlin-stdlib-jdk8-1.5.30.jar (version 1.5)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.10/e1c380673654a089c4f0c9f83d0ddfdc1efdb498/kotlin-stdlib-jdk7-1.6.10.jar (version 1.6)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.10/b8af3fe6f1ca88526914929add63cf5e7c5049af/kotlin-stdlib-1.6.10.jar (version 1.6)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.10/c118700e3a33c8a0d9adc920e9dec0831171925/kotlin-stdlib-common-1.6.10.jar (version 1.6)
w: Some runtime JAR files in the classpath have an incompatible version. Consider removing them from the classpath
w: Runtime JAR files in the classpath should have the same version. These files were found in the classpath:
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.5.30/5fd47535cc85f9e24996f939c2de6583991481b0/kotlin-stdlib-jdk8-1.5.30.jar (version 1.5)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.6.10/e1c380673654a089c4f0c9f83d0ddfdc1efdb498/kotlin-stdlib-jdk7-1.6.10.jar (version 1.6)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.10/b8af3fe6f1ca88526914929add63cf5e7c5049af/kotlin-stdlib-1.6.10.jar (version 1.6)
    /Users/Aleksandr_Denisov/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.10/c118700e3a33c8a0d9adc920e9dec0831171925/kotlin-stdlib-common-1.6.10.jar (version 1.6)
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.3.4, on macOS 12.6 21G115 darwin-x64, locale en-DE)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.3)
[✓] VS Code (version 1.71.2)
[✓] Connected device (3 available)
    ! Error: Guest Room is busy: Making Guest Room ready for development. Xcode will continue when Guest Room is finished. (code -10)
[✓] HTTP Host Availability

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: regressionIt was better in the past than it is nowc: renderingUI glitches reported at the engine/skia or impeller rendering levelcustomer: crowdAffects or could affect many people, though not necessarily a specific customer.e: device-specificOnly manifests on certain devicesengineflutter/engine related. See also e: labels.platform-androidAndroid applications specificallyr: fixedIssue is closed as already fixed in a newer version

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions