简体   繁体   中英

Flutter Dropdown Button and Dropdown Handler different size

I want to create a dropdown menu on flutter where the handler button that opens the dropdown uses just an icon and the menu list opened by it uses an icon and a text.

I almost manage to create it, as you can check on the following screenshots:

Closed Opened
关闭 打开

I'm struggling with the opened width, so my question is how to give the opened menu enough width and keep the handler button on its current width.

Notice that I want the dropdown to be at the end of the Row, so consider this black box to be an area of something else, nothing important.

I'm adding the relevant code below and the complete code on the following links.

import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Question Dropdown",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(
        optionStream: BehaviorSubject<Option>(),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  final BehaviorSubject<Option> optionStream;

  const HomePage({
    Key? key,
    required this.optionStream,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Question Dropdown"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              children: [
                Expanded(
                  child: Container(
                    height: 48,
                    color: Colors.black,
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: StreamBuilder<Option>(
                    initialData: Option.A,
                    stream: optionStream,
                    builder: (context, snapshot) {
                      final option = snapshot.data ?? Option.A;
                      return _dropDownMenu(context, option);
                    },
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _dropDownMenu(
    BuildContext context,
    Option option,
  ) {
    const items = Option.values;
    return DropdownButtonHideUnderline(
      child: DropdownButton<Option>(
        value: option,
        selectedItemBuilder: (context) =>
            items.map((e) => _dropdownHandler(context, e)).toList(),
        items: items.map((e) => _dropdownItem(context, e)).toList(),
        onChanged: (e) => optionStream.add(e ?? Option.A),
      ),
    );
  }

  OptionsItemHelper _dropDownItemData(
    BuildContext context,
    Option option,
  ) {
    Widget icon;
    String text;
    switch (option) {
      case Option.A:
        icon = const Icon(Icons.ac_unit);
        text = "An option";
        break;
      case Option.B:
        icon = const Icon(Icons.baby_changing_station);
        text = "Best option";
        break;
      case Option.C:
        icon = const Icon(Icons.cake_sharp);
        text = "Closest option";
        break;
      case Option.D:
        icon = const Icon(Icons.dashboard);
        text = "Dumb option";
        break;
    }
    return OptionsItemHelper(text, icon);
  }

  Widget _dropdownHandler(
    BuildContext context,
    Option option,
  ) {
    final helper = _dropDownItemData(context, option);
    return helper.icon;
  }

  DropdownMenuItem<Option> _dropdownItem(
    BuildContext context,
    Option option,
  ) {
    final helper = _dropDownItemData(context, option);
    return DropdownMenuItem<Option>(
      value: option,
      child: Row(
        children: [
          helper.icon,
          const SizedBox(width: 16),
          Text(helper.text),
        ],
      ),
    );
  }
}

enum Option {
  A,
  B,
  C,
  D,
}

class OptionsItemHelper {
  final String text;
  final Widget icon;

  OptionsItemHelper(
    this.text,
    this.icon,
  );
}

Complete code on Github

Complete code on Gitlab

I did find a workaround using GestureDetector and showMenu, I'm sharing here and pushing to the repo as "workaround" commit in case you need the same as I need now, I'm keeping the question without answer in case someone finds a better way using the dropdown.

The new dropDownMenu function

  Widget _dropDownMenu(
    BuildContext context,
    Option option,
  ) {
    const items = Option.values;
    return GestureDetector(
      onTapDown: (details) async {
        final offset = details.globalPosition;
        final newOption = await showMenu(
          context: context,
          position: RelativeRect.fromLTRB(offset.dx, offset.dy, 0, 0),
          items: items.map((e) => _dropdownItem(context, e, option)).toList(),
        );
        if (newOption != null) {
          optionStream.add(newOption);
        }
      },
      child: _dropdownHandler(context, option),
    );
  }

and the new dropdownItem function.

  PopupMenuEntry<Option> _dropdownItem(
    BuildContext context,
    Option option,
    Option selected,
  ) {
    final helper = _dropDownItemData(context, option);
    return CheckedPopupMenuItem<Option>(
      value: option,
      checked: option == selected,
      child: Row(
        children: [
          Expanded(child: Container()),
          Text(helper.text),
          const SizedBox(width: 16),
          helper.icon,
        ],
      ),
    );
  }

How it looks like

Closed Opened Bigger Screen
关闭 打开 更大的屏幕

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM