Persistent CupertinoListTile leading and trailing (#166799)

The bug occurs because the background color passed into the underlying
`Container` changes from null to `backgroundColorActivated`. Under the
hood, the `Container` wraps its child in a `ColoredBox` only if the
color is non-null. So the extra wrapping with a `ColoredBox` happening
mid-animation breaks reparenting, causing the widgets to be
replaced/inflated instead of updated.

### Before



https://github.com/user-attachments/assets/ca0b657a-1340-405f-8c1d-34b34366b994



### After



https://github.com/user-attachments/assets/8445c55c-0d5d-4b5f-96d2-4f12d908bdec





Fixes [CupertinoListTile animations are not running when pressing
longer](https://github.com/flutter/flutter/issues/153225)

<details>
<summary>Sample code</summary>

```dart

import 'package:flutter/cupertino.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(home: const ListTileExample());
  }
}

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

  @override
  State<ListTileExample> createState() => _ListTileExampleState();
}

class _ListTileExampleState extends State<ListTileExample> {
  bool _pushedToggle = false;

  void _toggle() {
    setState(() {
      _pushedToggle = !_pushedToggle;
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: Center(
        child: SizedBox(
          height: 40,
          child: CupertinoListTile(
              onTap: _toggle,
              title: Center(
                child: Text(
                  'Toggle',
                ),
              ),
              leading: CupertinoSwitch(
                value: _pushedToggle,
                onChanged: (_) {},
              ),
              trailing: CupertinoSwitch(
                value: _pushedToggle,
                onChanged: (_) {},
              )),
        ),
      ),
    );
  }
}


```

</details>
diff --git a/packages/flutter/lib/src/cupertino/list_tile.dart b/packages/flutter/lib/src/cupertino/list_tile.dart
index cf0be02..e14f656 100644
--- a/packages/flutter/lib/src/cupertino/list_tile.dart
+++ b/packages/flutter/lib/src/cupertino/list_tile.dart
@@ -308,7 +308,7 @@
     // null and it will resolve to the correct color provided by context. But if
     // the tile was tapped, it is set to what user provided or if null to the
     // default color that matched the iOS-style.
-    Color? backgroundColor = widget.backgroundColor;
+    Color backgroundColor = widget.backgroundColor ?? CupertinoColors.transparent;
     if (_tapped) {
       backgroundColor =
           widget.backgroundColorActivated ?? CupertinoColors.systemGrey4.resolveFrom(context);
@@ -321,44 +321,46 @@
       _CupertinoListTileType.notched => _kNotchedMinHeightWithoutLeading,
     };
 
-    final Widget child = Container(
+    final Widget child = ConstrainedBox(
       constraints: BoxConstraints(minWidth: double.infinity, minHeight: minHeight),
-      color: backgroundColor,
-      child: Padding(
-        padding: padding,
-        child: Row(
-          children: <Widget>[
-            if (widget.leading case final Widget leading) ...<Widget>[
-              SizedBox.square(dimension: widget.leadingSize, child: Center(child: leading)),
-              SizedBox(width: widget.leadingToTitle),
-            ] else
-              SizedBox(height: widget.leadingSize),
-            Expanded(
-              child: Column(
-                mainAxisAlignment: MainAxisAlignment.spaceBetween,
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: <Widget>[
-                  title,
-                  if (widget.subtitle case final Widget subtitle) ...<Widget>[
-                    const SizedBox(height: _kNotchedTitleToSubtitle),
-                    DefaultTextStyle(
-                      style: coloredStyle.copyWith(
-                        fontSize: baseType ? _kSubtitleFontSize : _kNotchedSubtitleFontSize,
+      child: ColoredBox(
+        color: backgroundColor,
+        child: Padding(
+          padding: padding,
+          child: Row(
+            children: <Widget>[
+              if (widget.leading case final Widget leading) ...<Widget>[
+                SizedBox.square(dimension: widget.leadingSize, child: Center(child: leading)),
+                SizedBox(width: widget.leadingToTitle),
+              ] else
+                SizedBox(height: widget.leadingSize),
+              Expanded(
+                child: Column(
+                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: <Widget>[
+                    title,
+                    if (widget.subtitle case final Widget subtitle) ...<Widget>[
+                      const SizedBox(height: _kNotchedTitleToSubtitle),
+                      DefaultTextStyle(
+                        style: coloredStyle.copyWith(
+                          fontSize: baseType ? _kSubtitleFontSize : _kNotchedSubtitleFontSize,
+                        ),
+                        maxLines: 1,
+                        overflow: TextOverflow.ellipsis,
+                        child: subtitle,
                       ),
-                      maxLines: 1,
-                      overflow: TextOverflow.ellipsis,
-                      child: subtitle,
-                    ),
+                    ],
                   ],
-                ],
+                ),
               ),
-            ),
-            if (widget.additionalInfo case final Widget additionalInfo) ...<Widget>[
-              DefaultTextStyle(style: coloredStyle, maxLines: 1, child: additionalInfo),
-              if (widget.trailing != null) const SizedBox(width: _kAdditionalInfoToTrailing),
+              if (widget.additionalInfo case final Widget additionalInfo) ...<Widget>[
+                DefaultTextStyle(style: coloredStyle, maxLines: 1, child: additionalInfo),
+                if (widget.trailing != null) const SizedBox(width: _kAdditionalInfoToTrailing),
+              ],
+              if (widget.trailing != null) widget.trailing!,
             ],
-            if (widget.trailing != null) widget.trailing!,
-          ],
+          ),
         ),
       ),
     );
diff --git a/packages/flutter/test/cupertino/list_tile_test.dart b/packages/flutter/test/cupertino/list_tile_test.dart
index 559b619..3e572cd 100644
--- a/packages/flutter/test/cupertino/list_tile_test.dart
+++ b/packages/flutter/test/cupertino/list_tile_test.dart
@@ -89,9 +89,10 @@
       ),
     );
 
-    // Container inside CupertinoListTile is the second one in row.
-    final Container container = tester.widgetList<Container>(find.byType(Container)).elementAt(1);
-    expect(container.color, backgroundColor);
+    final ColoredBox coloredBox = tester.widget<ColoredBox>(
+      find.descendant(of: find.byType(CupertinoListTile), matching: find.byType(ColoredBox)),
+    );
+    expect(coloredBox.color, backgroundColor);
   });
 
   testWidgets('does not change backgroundColor when tapped if onTap is not provided', (
@@ -121,9 +122,10 @@
     await tester.tap(find.byType(CupertinoListTile));
     await tester.pump();
 
-    // Container inside CupertinoListTile is the second one in row.
-    final Container container = tester.widgetList<Container>(find.byType(Container)).elementAt(1);
-    expect(container.color, backgroundColor);
+    final ColoredBox coloredBox = tester.widget<ColoredBox>(
+      find.descendant(of: find.byType(CupertinoListTile), matching: find.byType(ColoredBox)),
+    );
+    expect(coloredBox.color, backgroundColor);
   });
 
   testWidgets('changes backgroundColor when tapped if onTap is provided', (
@@ -153,17 +155,19 @@
       ),
     );
 
-    // Container inside CupertinoListTile is the second one in row.
-    Container container = tester.widgetList<Container>(find.byType(Container)).elementAt(1);
-    expect(container.color, backgroundColor);
+    ColoredBox coloredBox = tester.widget<ColoredBox>(
+      find.descendant(of: find.byType(CupertinoListTile), matching: find.byType(ColoredBox)),
+    );
+    expect(coloredBox.color, backgroundColor);
 
     // Pump only one frame so the color change persists.
     await tester.tap(find.byType(CupertinoListTile));
     await tester.pump();
 
-    // Container inside CupertinoListTile is the second one in row.
-    container = tester.widgetList<Container>(find.byType(Container)).elementAt(1);
-    expect(container.color, backgroundColorActivated);
+    coloredBox = tester.widget<ColoredBox>(
+      find.descendant(of: find.byType(CupertinoListTile), matching: find.byType(ColoredBox)),
+    );
+    expect(coloredBox.color, backgroundColorActivated);
 
     // Pump the rest of the frames to complete the test.
     await tester.pumpAndSettle();
@@ -184,7 +188,6 @@
       ),
     );
 
-    // Container inside CupertinoListTile is the second one in row.
     expect(find.byType(GestureDetector), findsNothing);
   });
 
@@ -203,7 +206,6 @@
       ),
     );
 
-    // Container inside CupertinoListTile is the second one in row.
     expect(find.byType(GestureDetector), findsOneWidget);
   });
 
@@ -251,9 +253,10 @@
     await tester.tap(find.byType(CupertinoButton));
     await tester.pumpAndSettle();
 
-    // Container inside CupertinoListTile is the second one in row.
-    final Container container = tester.widget<Container>(find.byType(Container));
-    expect(container.color, backgroundColor);
+    final ColoredBox coloredBox = tester.widget<ColoredBox>(
+      find.descendant(of: find.byType(CupertinoListTile), matching: find.byType(ColoredBox)),
+    );
+    expect(coloredBox.color, backgroundColor);
   });
 
   group('alignment of widgets for left-to-right', () {
@@ -494,4 +497,49 @@
 
     expect(tester.takeException(), null);
   });
+
+  testWidgets('Leading and trailing animate on listtile long press', (WidgetTester tester) async {
+    bool value = false;
+    await tester.pumpWidget(
+      CupertinoApp(
+        home: CupertinoPageScaffold(
+          child: StatefulBuilder(
+            builder: (BuildContext context, StateSetter setState) {
+              return CupertinoListTile(
+                title: const Text(''),
+                onTap:
+                    () => setState(() {
+                      value = !value;
+                    }),
+                leading: CupertinoSwitch(value: value, onChanged: (_) {}),
+                trailing: CupertinoSwitch(value: value, onChanged: (_) {}),
+              );
+            },
+          ),
+        ),
+      ),
+    );
+
+    final CurvedAnimation firstPosition =
+        (tester.state(find.byType(CupertinoSwitch).first) as dynamic).position as CurvedAnimation;
+    final CurvedAnimation lastPosition =
+        (tester.state(find.byType(CupertinoSwitch).last) as dynamic).position as CurvedAnimation;
+
+    expect(firstPosition.value, 0.0);
+    expect(lastPosition.value, 0.0);
+
+    await tester.longPress(find.byType(CupertinoListTile));
+    await tester.pump();
+    await tester.pump(const Duration(milliseconds: 65));
+
+    expect(firstPosition.value, greaterThan(0.0));
+    expect(lastPosition.value, greaterThan(0.0));
+
+    expect(firstPosition.value, lessThan(1.0));
+    expect(lastPosition.value, lessThan(1.0));
+
+    await tester.pumpAndSettle();
+    expect(firstPosition.value, 1.0);
+    expect(lastPosition.value, 1.0);
+  });
 }