Skip to content

ListView under a Transform breaks for some perspective values #173104

@FufferKS

Description

@FufferKS

Hi, I'm experiencing render issues when playing around with perspective in Flutter.

I have a "Road" like widget that is basically a ListView under perspective, but for certain matrix values it breaks.

I've raised similar issues in the past:
#155450
#154104
And got some help from @jason-simmons, so 🤞 its resolvable this time around as well.

I've provided minimal repro and a video, where it's visible for which values the ListView starts breaking (-0.003 on the device I was testing) (iPhone 16 Pro Simulator).

  • Using y scale makes the situation worse.
  • Changing the height of the Container does not seem to matter.
  • I am confident this is not device, platform or debug mode specific, as our actual app experiences this for both ios and android + release and debug.

Steps to reproduce

Create a ListView, wrap it in a Transform and setEntry(3,1, highEnoughValueLike0point005), see it disappear.

Code sample

Code sample
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: 'Perspective Issues',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  double _yScale = 1.0;
  double _perspective = 0.000;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Perspective Issues')),
      body: Stack(
        alignment: Alignment.bottomCenter,
        children: [
          Transform(
            transform: Matrix4.identity()
              ..setEntry(3, 1, _perspective)
              ..scale(1.0, _yScale, 1.0),
            alignment: Alignment.center,

            child: ListView.builder(
              reverse: true,
              itemBuilder: (context, index) {
                final hue = (index * 137.5) % 360;
                final color = HSLColor.fromAHSL(1.0, hue, 0.6, 0.5).toColor();
                return Container(
                  height: 100.0,
                  color: color,
                  child: Center(
                    child: Text(
                      'Index: $index',
                      style: const TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
                    ),
                  ),
                );
              },
            ),
          ),
          _buildDebugControls(),
        ],
      ),
    );
  }

  Widget _buildDebugControls() {
    return Card(
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 12.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildSlider(
              label: 'Perspective',
              value: _perspective,
              max: 0,
              min: -0.005,
              defaultValue: 0.000,
              onChanged: (value) => setState(() => _perspective = value),
            ),
            _buildSlider(
              label: 'Y Scale',
              value: _yScale,
              min: 1.0,
              max: 5.0,
              defaultValue: 1.0,
              onChanged: (value) => setState(() => _yScale = value),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSlider({
    required String label,
    required double value,
    required double min,
    required double max,
    required double defaultValue,
    required ValueChanged<double> onChanged,
  }) {
    return Column(
      children: [
        Text('$label: ${value.toStringAsFixed(4)}'),
        Row(
          children: [
            Expanded(
              child: Slider(
                value: value,
                min: min,
                max: max,
                label: value.toStringAsFixed(4),
                onChanged: onChanged,
              ),
            ),
            const SizedBox(width: 8.0),
            IconButton(icon: const Icon(Icons.refresh, size: 20), onPressed: () => onChanged(defaultValue)),
          ],
        ),
      ],
    );
  }
}

Screenshots or Video

Details
Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-08-01.at.14.32.53.mp4

Logs

Logs

Nothing in the logs

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.32.6, on macOS 14.5 23F79 darwin-arm64, locale en-PL)
[!] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
    ! Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.3)
[✓] VS Code (version 1.102.3)
[✓] Connected device (3 available)
...
[✓] Network resources

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work liste: impellerImpeller rendering backend issues and features requestsf: material designflutter/packages/flutter/material repository.f: scrollingViewports, list views, slivers, etc.found in release: 3.32Found to occur in 3.32found in release: 3.33Found to occur in 3.33frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer versionteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions