Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix ListTile Material issues (Add self-contained Material) #102310

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

TahaTesser
Copy link
Member

@TahaTesser TahaTesser commented Apr 21, 2022

This PR fixes the following issues

fixes #89642

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

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

class MyApp extends StatelessWidget {
  const MyApp({this.dark = true});

  final bool dark;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: dark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(
        brightness: Brightness.light,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      home: const Example(),
    );
  }
}

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final Animation<double> _animation;

  @override
  void initState() {
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat(reverse: true);

    _animation = CurvedAnimation(
      parent: _controller,
      curve: Curves.easeIn,
    );
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('fade transition example'),
      ),
      body: FadeTransition(
        opacity: _animation,
        child: Column(
          children: <Widget>[
            const ListTile(
              title: Text('ListTile with custom background'),
              tileColor: Colors.green,
            ),
            Container(
              height: 100,
              color: Colors.green,
              alignment: Alignment.center,
              child: const Text('Container with custom background'),
            ),
            ElevatedButton(
              style: ElevatedButton.styleFrom(
                minimumSize: const Size(80, 50),
                maximumSize: const Size(200, 50),
              ),
              onPressed: () {},
              child: const Text('ElevatedButton'),
            ),
          ],
        ),
      ),
    );
  }
}
Video previews

Bug

transition.bug.mov

Fixed

transition.fixed.mov

fixes #89550

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

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

class MyApp extends StatelessWidget {
  const MyApp({this.dark = true});

  final bool dark;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: dark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(
        brightness: Brightness.light,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      home: const Example(),
    );
  }
}

class Example extends StatelessWidget {
  const Example({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hero Sample'),
      ),
      body: Center(
        child: Hero(
          tag: 'list_tile',
          child: ListTile(
            leading: const Icon(Icons.ac_unit),
            title: const Text('Title'),
            subtitle: const Text('Subtitle'),
            trailing: const Text('Trailing'),
            onTap: () {
              Navigator.push(
                  context,
                  MaterialPageRoute<Widget>(
                      builder: (BuildContext context) => const SecondScreen()));
            },
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        heroTag: 'fab',
        onPressed: () {
          Navigator.push(
              context,
              MaterialPageRoute<Widget>(
                  builder: (BuildContext context) => const SecondScreen()));
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Screen'),
      ),
      body: Hero(
        tag: 'list_tile',
        child: ListTile(
          leading: const Icon(Icons.ac_unit),
          title: const Text('Title'),
          subtitle: const Text('Subtitle'),
          trailing: const Text('Trailing'),
          selectedTileColor: Colors.black54,
          selected: true,
          onTap: () => Navigator.pop(context),
        ),
      ),
      floatingActionButton: FloatingActionButton.large(
        heroTag: 'fab',
        onPressed: () => Navigator.pop(context),
        child: const Icon(Icons.add),
      ),
    );
  }
}
Video previews

Bug

hero.bug.mov

Fixed

hero.fix.mov

fixes #85256

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

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

class MyApp extends StatelessWidget {
  const MyApp({this.dark = true});

  final bool dark;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: dark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(
        brightness: Brightness.light,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      home: const Example(),
    );
  }
}

class Example extends StatelessWidget {
  const Example({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hero Sample'),
      ),
      body: Center(
        child: Container(
          color: Colors.yellow,
          padding: const EdgeInsets.all(5),
          child: ListView.builder(
            shrinkWrap: true,
            padding: EdgeInsets.zero,
            itemCount: 10,
            itemBuilder: (BuildContext context, int i) {
              return CheckboxListTile(
                  title: Text('hello $i'),
                  tileColor: Colors.blueGrey,
                  value: false,
                  shape: RoundedRectangleBorder(
                      side: const BorderSide(width: 0.5, color: Colors.blue),
                      borderRadius: BorderRadius.circular(30)),
                  onChanged: (bool? val) {});
            },
          ),
        ),
      ),
    );
  }
}
Screenshot previews

Bug

container bug

Fixed

container fix

fixes #83124

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

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

class MyApp extends StatelessWidget {
  const MyApp({this.dark = true});

  final bool dark;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      themeMode: dark ? ThemeMode.dark : ThemeMode.light,
      theme: ThemeData(
        brightness: Brightness.light,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorSchemeSeed: const Color(0xff6750a4),
      ),
      home: const Example(),
    );
  }
}

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  bool _show = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Tile example')),
      body: CustomScrollView(
        slivers: [
          SliverToBoxAdapter(
            child: AnimatedSwitcher(
              duration: const Duration(milliseconds: 300),
              transitionBuilder: (Widget child, Animation<double> animation) =>
                  SizeTransition(
                sizeFactor: animation,
                axisAlignment: 1,
                child: child,
              ),
              child: _show
                  ? const ListTile(
                      tileColor: Colors.yellow,
                      title: Text('Titlte'),
                    )
                  : const SizedBox.shrink(),
            ),
          ),
          SliverToBoxAdapter(
            child: ElevatedButton(
              child: const Text('toggle'),
              onPressed: () {
                setState(() {
                  _show = !_show;
                });
              },
            ),
          ),
        ],
      ),
    );
  }
}
Video previews

Bug

animation.bug.mov

Fixed

aniamtion.fix.mov

fixes #84989

minimal code sample
/// Flutter code sample for ReorderableListView

import 'package:flutter/material.dart';

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

/// This is the main application widget.
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: Scaffold(
        appBar: AppBar(title: const Text(_title)),
        body: const MyStatefulWidget(),
      ),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    final ColorScheme colorScheme = Theme.of(context).colorScheme;
    final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
    final Color evenItemColor = colorScheme.primary.withOpacity(0.15);

    return ReorderableListView(
      padding: const EdgeInsets.symmetric(horizontal: 40),
      children: <Widget>[
        for (int index = 0; index < _items.length; index++)
          ListTile(
            key: Key('$index'),
            tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
            title: Text('Item ${_items[index]}'),
            selectedTileColor: Colors.pink[100],
            selected: _items[index] == 3,
          ),
      ],
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (oldIndex < newIndex) {
            newIndex -= 1;
          }
          final int item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
    );
  }
}
Video previews

Bug

Screen.Recording.2022-04-22.at.17.25.00.mov

Fixed

Screen.Recording.2022-04-22.at.17.23.33.mov

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Apr 21, 2022
@TahaTesser
Copy link
Member Author

TahaTesser commented Apr 21, 2022

This needs the following PRs to land first Landed
#102309
#102311

Improved no longer used import
@maheshj01
Copy link
Member

maheshj01 commented May 26, 2022

I am wondering if this PR would also require to update the ListTile docs. Specifically, this part where it says the ListTile needs to be ancestor of a Material Widget for the colors to take effect.

One ancestor must be a Material widget and typically this is provided by the app's Scaffold. The tileColor, selectedTileColor, focusColor, and hoverColor are not painted by the list tile itself but by the material widget ancestor. This generally has no effect. However, if an opaque widget, like Container(color: Colors.white), is included in between the ListTile and its Material ancestor, then the opaque widget will obscure the material widget and its background tileColor, etc. If this a problem, one can wrap a material widget around the list tile, e.g.:

@TahaTesser
Copy link
Member Author

I am wondering if this PR would also require to update the ListTile docs. Specifically, this part where it says the ListTile needs to be ancestor of a Material Widget for the colors to take effect.

That's a good idea, I'll update the docs once this PRs gets an initial review.

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there is a reason ListTile does not have a self-containing Material, especially since it was specifically documented that the user needs to supply a Material ancestor, rather than just adding one in like this.

My first guess would be how it affects the Inkwell in the ListTile, but I am not entirely sure. @HansMuller do you recall why ListTile does not contain Material?

@HansMuller
Copy link
Contributor

I believe the original idea was that none of the components would have an intrinsic Material widget and that ink splashes would be rendered on the common "material" substrate without respect for their components' boundaries. That no longer matches the Material design and it turned out to be impractical in some cases. In this case cost is also a factor; applications may build large number of ListTiles and Material widgets aren't super-cheap.

@Piinks
Copy link
Contributor

Piinks commented Jun 8, 2022

Ahh so it sounds like having one Material ancestor for potentially many ListTiles is more efficient?

@HansMuller
Copy link
Contributor

It's less Material widgets, so yes.

@TahaTesser
Copy link
Member Author

If you look at the linked issues, they need to add Material to every tile (in a list or ReorderableListView) to get desired results.

I guess that can be worked around by adding a Material to every tile

from #89642

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds like a reasonable decision for the developer to make to me. It could be that we just need more documentation and example code for each of these issues, and particularly some docs on the performance implications.

@TahaTesser
Copy link
Member Author

Piinks left a comment
That sounds like a reasonable decision for the developer to make to me. It could be that we just need more documentation and example code for each of these issues, and particularly some docs on the performance implications.

Thank you! Sounds good to me, I would like to close all the linked issues with docs and examples.

Closing this PR as a result.

@TahaTesser TahaTesser closed this Jun 10, 2022
@TahaTesser TahaTesser deleted the fix_list_tile_material branch June 10, 2022 07:00
@TahaTesser
Copy link
Member Author

Filed #105760

@TahaTesser
Copy link
Member Author

Closed all the linked issues under #105760

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
4 participants