flutterflutter-dropdownbutton

Flutter Dropdown Different Selected Item Style than Items


I am developping an UI for the user to change the app language.

When showing the currently selected language I want to only display the flag with a dropdown icon. When the user clicks on the dropdown button I want the full string representation of the language to be added.

I can't seem to find a way to make the the selected item to look different than the menu items.

What I want: This for unopened dropdown: flag only, dropdown closed What I want: This for opened dropdown: flag and text, dropdown opened

Unwanted: flag only, dropdown opened Unwanted: flag and text, dropdown closed

I only get text for both or just flag for both, but never as desired.

The Code so far:

DropdownButton(
  items: LanguageUtils.getSupportedLanguagesAsStringList()
      .map<DropdownMenuItem<String>>((String langCodeString) {
    return DropdownMenuItem<String>(
      value: langCodeString,
      child: buildLanguageRowForDropDownButton(
          langCode:
              LanguageUtils.getLangCodeFromItsStringRep(langCodeString)),
    );
  }).toList(),
  value: LanguageUtils.getStringRepForLangCode(_chosenLangCode),
  onChanged: (String? value) {
    changeLanguage();
  },

);

/// Build row to be displayed in DropDownMenu.
Row buildLanguageRowForDropDownButton({LangCode? langCode}) {
  // Use currently _chosenLangCode as default.
  langCode = langCode ?? _chosenLangCode;

  // Build row from settings.
  List<Widget> rowElements = [];
  if (showFlag) {
    rowElements.add(getImageOfLangCode(langCode: langCode));
  }
  if (showWrittenLanguage) {
    rowElements
        .add(Text(LanguageUtils.getFullNameForLangCode(langCode)));
  }

  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: rowElements,
  );
}
// LanguageUtils

/// Functions around setting app translation language.
class LanguageUtils {
  LanguageUtils() {}

  /// Get written rep of language to display to user.
  static String getFullNameForLangCode(LangCode langCodeToGetStringFor) {
    switch (langCodeToGetStringFor) {
      case LangCode.en:
        return "English";
        break;
      case LangCode.de:
        return "Deutsch";
        break;
    }
  }

  /// Get short rep of language code to use programmatically when exporting state.
  static String getStringRepForLangCode(LangCode langCodeToGetStringFor) {
    switch (langCodeToGetStringFor) {
      case LangCode.en:
        return "en";
        break;
      case LangCode.de:
        return "de";
        break;
    }
  }

  /// Get all supported LangCodes as string rep to create dropdown menu from.
  static List<String> getSupportedLanguagesAsStringList() {
    return LangCode.values.map((e) => e.name).toList();
  }

  /// To return LangCode from its converted rep.
  ///
  /// Defaults to en => Failsafe, always returns valid LangCode.
  static LangCode getLangCodeFromItsStringRep(String langCodeAsString) {
    switch (langCodeAsString) {
      case "de":
        return LangCode.de;
        break;

      case "en":
      default:
        return LangCode.en;
        break;
    }
  }
}

/// Supported Languages.
enum LangCode { en, de }

I looked at flutter dev, searched stackoverflow, googled and attempted to change things on my own.


Solution

  • Looks like you need to use the selectedItemBuilder of DropdownButton.

    Here's an example you can try and plug in your data to get the desired output:

    import 'package:flutter/material.dart';
    
    const List<String> list = <String>['One  ', 'Two ', 'Three', 'Four'];
    
    void main() => runApp(const DropdownButtonApp());
    
    class DropdownButtonApp extends StatelessWidget {
      const DropdownButtonApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text('DropdownButton Sample')),
            body: const Center(
              child: DropdownButtonExample(),
            ),
          ),
        );
      }
    }
    
    class DropdownButtonExample extends StatefulWidget {
      const DropdownButtonExample({super.key});
    
      @override
      State<DropdownButtonExample> createState() => _DropdownButtonExampleState();
    }
    
    class _DropdownButtonExampleState extends State<DropdownButtonExample> {
      String dropdownValue = list.first;
    
      @override
      Widget build(BuildContext context) {
        return DropdownButton<String>(
          value: dropdownValue,
          //icon: const Icon(Icons.arrow_downward),
          elevation: 16,
          style: const TextStyle(color: Colors.deepPurple),
          underline: Container(
            height: 2,
            color: Colors.deepPurpleAccent,
          ),
          onChanged: (String? value) {
            // This is called when the user selects an item.
            setState(() {
              dropdownValue = value!;
            });
          },
    
          selectedItemBuilder: (context) =>
              List.generate(1,
                            (i) => 
                           const SizedBox(
                            width: 50,
                            child: Icon(Icons.flag)
                            )
                           ),
         
          items: list.map<DropdownMenuItem<String>>((String value) {
            return DropdownMenuItem<String>(
              value: value,
              child: SizedBox(
                width: 100,
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [const Icon(Icons.flag), Text(value)],
                ),
              ),
            );
          }).toList(),
        );
      }
    }
    

    Note: SizedBox is important so it doesn't throw viewport errors.