3

I am still learning Flutter and I am trying to create a custom dropdownlist. Below is my custom dropdownlist and it seems to work and return the selected value but I still need help on how to show popup list when the inkwell is pressed. Also, how do I pass in a string array to build/populate the popup list items. Thanks for your help on this.

return new Expanded(
    flex: 4,
    child: new InputDropdownList(
      labelText: "Select a value",
      valueText: viewModel.selectedValue,
      valueStyle: Theme.of(context).inputDecorationTheme.labelStyle,
      items: <String>["ValueA", "ValueB", "ValueC", "ValueD"],
      onPressed: () { 
      },
      selectedValue: (String value) { 
        setState(() { viewModel.selectedValue= value; });
      },
    ),
),


class InputDropdownList extends StatelessWidget {
  const _InputDropdownList({
    Key key,
    this.labelText,
    this.valueText,
    this.valueStyle,
    this.items,
    this.onPressed,
    this.selectedValue }) : super(key: key);

  final String labelText;
  final String valueText;
  final TextStyle valueStyle;
  final List<String> items;
  final Function() onPressed;
  final ValueChanged<String> selectedValue;

  @override
  Widget build(BuildContext context) {
    return new InkWell(
      onTap: () {},
      child: new InputDecorator(
        decoration: new InputDecoration(
          labelText: labelText,
        ),
        baseStyle: valueStyle,
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            new Text(valueText, style: valueStyle),
            new PopupMenuButton<String>(
              icon: new Icon(Icons.arrow_drop_down, color: Theme.of(context).brightness == Brightness.light ? Colors.grey.shade700 : Colors.white70),
              padding: EdgeInsets.zero,
              onSelected: (value) {
                selectedValue(value);
              },
              itemBuilder: (BuildContext context) => <PopupMenuItem<String>>[
                new PopupMenuItem<String>(
                  value: "ValueA",
                  child: const Text('ValueA')
                ),
                new PopupMenuItem<String>(
                  value: "ValueB",
                  child: const Text('ValueB')
                ),
              ]
            ),
          ],
        ),
      ),
    );
  }
}
Edmand Looi
  • 3,331
  • 6
  • 19
  • 21
  • You seem to be trying to re-create the third item down on the menu page of the Flutter gallery (`PopupMenuButton` with a `ListTile` child). See the source here https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/material/menu_demo.dart If this is close to what you want, can we use that as a starting point? – Richard Heap Apr 26 '18 at 19:22
  • Richard, that is exactly what I'm trying todo. I have tried using the example but I ran into an issue where the spacing is not consistent height with my other widget on the page and I need to add a label for the field. I did manage to get it working using the showMenu function. However, I am still interested in understanding how to trigger an onPress or onTap event of the child widget from a parent widget. Thanks for your help on this. – Edmand Looi Apr 27 '18 at 13:29
  • As you probably now can tell the way to affect child widgets is to set your own state, which causes the children to be re-built. This allows you to pass the new state in the constructor of the re-built children (or some other way like an Inherited Widget) and they can display themselves appropriately, e.g. red instead of blue, selected instead of not selected, open instead of closed, etc. – Richard Heap Apr 27 '18 at 16:59

2 Answers2

0

So you have two issues here: you'd like to use a List<String> to populate PopupMenuButton, and clicking InkWell doesn't open the popup menu.

For the first issue, a function for generating a PopupMenuItem List can be created to be set on PopupMenuButton itemBuilder.

_popupMenuItems() {
  var popupMenuItemList = <PopupMenuItem<String>>[];
  items.forEach((itemValue) {
    popupMenuItemList.add(
        PopupMenuItem<String>(value: itemValue, child: Text('$itemValue')));
  });
  return popupMenuItemList;
}

Set _popupMenuItems() in PopupMenuButton itemBuilder.

PopupMenuButton<String>(
  ...
  itemBuilder: (BuildContext context) => _popupMenuItems(),
);

As for the second issue, the current setup base from the sample you've given has InkWell as the parent widget. The popup menu can only be displayed when PopupMenuButton is clicked. With this approach, the PopupMenuButton is only clickable on a small area of the widget.

InkWell(
  child: InputDecorator(
    child: Row(
      children: <Widget>[
        Text(),
        PopupMenuButton(),
      ],
    ),
  ),
)

As a solution, PopupMenuButton can be the parent widget of InkWell. With this, clicking the child inside PopupMenuButton should still display the popup menu.

PopupMenuButton(
  child: InkWell(
    child: InputDecorator(
      child: Row(
        children: <Widget>[
          Text(),
          Icon(),
        ],
      ),
    ),
  ),
)

Note that you can only use either child or icon in PopupMenuButton, not both. Since icon can't be used, we can set the Icon beside Text.

Here's a complete working sample.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  var selectedValue = 'Default';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: InputDropdownList(
          labelText: "Select a value",
          valueText: selectedValue,
          valueStyle: Theme.of(context).inputDecorationTheme.labelStyle,
          items: <String>["ValueA", "ValueB", "ValueC", "ValueD"],
          onPressed: () {},
          selectedValue: (String value) {
            setState(() {
              selectedValue = value;
            });
          },
        ),
      ), 
    );
  }
}

class InputDropdownList extends StatelessWidget {
  const InputDropdownList(
      {Key key,
      this.labelText,
      this.valueText,
      this.valueStyle,
      this.items,
      this.onPressed,
      this.selectedValue})
      : super(key: key);

  final String labelText;
  final String valueText;
  final TextStyle valueStyle;
  final List<String> items;
  final Function() onPressed;
  final ValueChanged<String> selectedValue;

  _popupMenuItems() {
    var popupMenuItemList = <PopupMenuItem<String>>[];
    items.forEach((itemValue) {
      print('$itemValue');
      popupMenuItemList.add(
          PopupMenuItem<String>(value: itemValue, child: Text('$itemValue')));
    });
    return popupMenuItemList;
  }

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<String>(
      child: InkWell(
        // onTap() function overrides the popup displayed from PopupMenuButton
        // onTap: () {
        //   debugPrint('Inkwell tapped');
        // },
        child: InputDecorator(
          decoration: InputDecoration(
            labelText: labelText,
          ),
          baseStyle: valueStyle,
          child: Row(
            children: [
              Text(valueText, style: valueStyle),
              Icon(Icons.arrow_drop_down,
                  color: Theme.of(context).brightness == Brightness.light
                      ? Colors.grey.shade700
                      : Colors.white70),
            ],
          ),
        ),
      ),
      padding: EdgeInsets.zero,
      onSelected: (value) {
        selectedValue(value);
      },
      itemBuilder: (BuildContext context) => _popupMenuItems(),
    );
  }
}

Demo

Omatt
  • 8,564
  • 2
  • 42
  • 144
0
Here is the dropdown with custom UI,
List<String> slots = [
    '1',
    '2',
    '3',
    '4',
    '5',
];

    List<DropdownMenuItem<String>> mItems = new List();
    
        for (int i = 0; i < slots.length; i++) {
          mItems.add(new DropdownMenuItem(
            child: new Text(slots[i]),
            value: slots[i],
          ));
        }
    

    Container(
    height: 50,
    padding: EdgeInsets.only(left: 10, right: 10),
    margin: EdgeInsets.only(
    bottom: 20, left: 15, right: 15),
    alignment: Alignment.center,
        decoration: BoxDecoration(
        color: Colors.grey,
        borderRadius: const BorderRadius.all(
        const Radius.circular(12.0),
        ),
        ),
        child: DropdownButton(
        isExpanded: true,
        underline: Container(),
        icon: Icon(
        Icons.keyboard_arrow_down,
        color: textBlue,
        ),
        hint: Text(
        value,
        style: kTextFieldStyleFill,
        ),
        value: value,
        onChanged: (newValue) {
        setState(() {
        value = newValue;
        });
        },
        items: mItems,
        ),
    )
Bhoomika Chauhan
  • 928
  • 1
  • 9
  • 11