9

When a PopupMenuButton is pressed, the currently selected value is highlighted, enter image description here

but when a DropdownButton is pressed, the currently selected value is not highlighted. enter image description here

Is there a way to highlight the selected value of a DropdownButton?

For reference here is some sample code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MyHomePage());
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  String letter = 'A';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Popup Menu Button')),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          SizedBox(height: 16.0),
          Text('PopupMenuButton'),
          buildPopupMenuButton(),
          SizedBox(height: 16.0),
          Text('DropdownButton'),
          buildDropdownButton(),
        ],
      ),
    );
  }

  Widget buildPopupMenuButton() {
    return PopupMenuButton<String>(
      padding: EdgeInsets.zero,
      initialValue: letter,
      onSelected: (val) => setState(() => letter = val),
      child: ListTile(
        title: Text('The letter $letter'),
      ),
      itemBuilder: (BuildContext context) {
        return <PopupMenuItem<String>>[
          PopupMenuItem<String>(
            value: 'A',
            child: Text('The letter A'),
          ),
          PopupMenuItem<String>(
            value: 'B',
            child: Text('The letter B'),
          ),
        ];
      },
    );
  }

  Widget buildDropdownButton() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: DropdownButton<String>(
        value: letter,
        onChanged: (val) => setState(() => letter = val),
        items: [
          DropdownMenuItem<String>(
            value: 'A',
            child: Text('The letter A'),
          ),
          DropdownMenuItem<String>(
            value: 'B',
            child: Text('The letter B'),
          ),
        ],
      ),
    );
  }
}

Here's a video that shows the issue:

enter image description here

George
  • 6,886
  • 3
  • 44
  • 56
Simpler
  • 1,317
  • 2
  • 16
  • 31

5 Answers5

4

The DropdownMenuItem doesn't support many custom modifications on the child element, as there's no style, background, anything actually in the DropdownMenuItem attributes to help you with that. Looking at the code, it really wasn't built for that,

Yet, there's something you could add, a simple check on the child attribute of the DropdownMenuItem, and wrap the Text child element in something else or style the Text element itself if it is checked.

One example:

Widget buildDropdownButton() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16.0),
      child: DropdownButton<String>(
        value: letter,
        onChanged: (val) => setState(() => letter = val),
        items: [
          DropdownMenuItem<String>(
            value: 'A',
            child: Container(
              color: letter == 'A' ? Colors.black12 : null,
              child: Text('The letter A'),
            ),
          ),
          DropdownMenuItem<String>(
            value: 'B',
            child: Container(
              color: letter == 'B' ? Colors.black12 : null,
              child: Text('The letter B'),
            ),
          ),
        ],
      ),
    );
}

Note that in a real case scenario, you would have a method with a paremeter to build each dropdown item, so the verification wouldn't have to be hardcoded like letter == 'A'.

This would be the output:

selected output example

This approach allows you to style a bit, but it has an ugly result in some cases. Although it is customizable, there will always be a white margin around the item, and it also shows the same styles when the dropdown list is closed, so it gets a bit ugly on the main page.

Instead of changing the background, you can also change text colors, underline, icons on the side, something like that make it much better, like:

DropdownMenuItem<String>(
  value: 'A',
  child: Text('The letter A',
    style: TextStyle(
      color: letter == 'A' ? Colors.red : Colors.black87,
    ),
  ),
)
George
  • 6,886
  • 3
  • 44
  • 56
1

Responding to your original issue which was: "I'm interested in the darker background from the currently selected value when all of the values are shown."

Your PopupMenuButton will look at its initialValue: parameter each time it is opened--the item corresponding to this value will be highlighted. You will need to update the initialValue parameter each time using the onSelected function.

Make sure the parent widget is a StatefulWidget widget and create a reference to whatever your initialValue is. The PopupMenuButton has an onSelected parameter that takes in a function with parameter String.

Whenever you select an option from the PopupMenuButton, call

setState(() {
        ...
        this.initialValue = value;
});

The full class will look something like this.

Class YourClass extends StatefulWidget {
  @override
  createState() => _YourClassState();
}

class _YourClassState extends State<YourClass> {
  ...
  String initialValue = 'foo';

  @override
  Widget build(BuildContext context) {
    final items = [
      PopupMenuItem(
        value: 'foo',
        child: Text('foo'),
      ),
      PopupMenuItem(
        value: 'nice',
        child: Text('nice'),
      ),
    }
    return Scaffold(
      appBar: ...,
      drawer: ...,
      body: PopupMenuButton(
        icon: ...,
        itemBuilder: (_) => items,
        initialValue: this.initialValue,
        onSelected: (value) => bar(value),
      ),
    );
  }

  void bar(String value) {
    setState(() {
      ...
      this.initialValue = value;
    });
  }
}
Eli Whittle
  • 1,084
  • 1
  • 15
  • 19
  • 1
    The PopupMenuButton works as desired. The problem is with the DropdownButton. The current value is not highlighted when using a DropdownButton. – Simpler Aug 06 '19 at 12:44
0

Well, as far as I know this grey overlay is a so called 'Ripple effect' in the material design library. It seems that Flutter does not adapt the full design in all widgets yet.

However you can try to use the InkWell widget to add this kind of animations/colors to current widgets:

https://flutter.io/docs/cookbook/gestures/ripples

E.g:

PopupMenuItem<String>(
    value: 'B',
    child: InkWell(child: Text('The letter B'))
),

I am not sure if the width will be correct, but at least it should show the grey overlay when you press on the entry.

You can also check the Flutter source: https://github.com/flutter/flutter/blob/237fc2fb45639312001e947bf7465ef9f23bb699/packages/flutter/lib/src/material/popup_menu.dart#L933

Here you can see that a Inkwell is standard being used for the PopupMenuButton.

gi097
  • 7,313
  • 3
  • 27
  • 49
  • 2
    Thanks for the feedback, but I'm not interested in the InkWell effect when the button is pressed. I'm interested in the darker background from the currently selected value when all of the values are shown. – Simpler Feb 20 '19 at 19:58
  • I provided the source code in my answer as well, you could try to mimick it a bit since I don’t really understand what you really want to achieve. – gi097 Feb 20 '19 at 19:59
  • Both the Dropdown and the Popup menu items already come with an InkWell, apparently. – George Feb 20 '19 at 20:17
  • I'm also looking for a solution to changing the focus based on the currently selected value. If you set the initial value, it remains highlighted even after pressing other options. There doesn't seem to be an answer to this here. – Eli Whittle Aug 05 '19 at 05:45
0

You can wrap the widget with Theme to set a highlight color.

return Theme(
    data: ThemeData(highlightColor: Colors.grey[300]),
    child: DropdownButton()
Karen
  • 1,423
  • 8
  • 16
0

You may try it:

class CustomDropdownMenuItem<T> extends DropdownMenuItem<T> {
  const CustomDropdownMenuItem({
    super.key,
    super.onTap,
    super.value,
    super.enabled = true,
    super.alignment,
    required this.current,
    required super.child,
  });

  final T current;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: current == value ? Theme.of(context).highlightColor : null,
      child: super.build(context),
    );
  }
}

However, the element will not be completely covered in color. You can also add a check on the current device to exclude those that work correctly (web and desktop).

Basically, we have to wait for this issue to be solved.


Update:

Alternatively, you can use color selection if you use Text:


    final theme = Theme.of(context);
    ...
    return DropdownMenuItem<AppLocale>(
      value: value,
      onTap: () => {},
      child: Text(
        value.name,
        style: theme.textTheme.titleMedium?.copyWith(
            color: value == current ? theme.colorScheme.secondary : null),
      ),
    );
Ruble
  • 2,589
  • 3
  • 6
  • 29