Skip to content
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

Setting a decent label is a nightmare! #107

Closed
ShahoodulHassan opened this issue Dec 8, 2022 · 13 comments
Closed

Setting a decent label is a nightmare! #107

ShahoodulHassan opened this issue Dec 8, 2022 · 13 comments
Labels
documentation Improvements or additions to documentation

Comments

@ShahoodulHassan
Copy link

Hi,

Thanks for such a nice and customizable widget.

There is just one issue that at least I couldn't fix even after trying every option that seemed relevant.

In case of DropdownButtonTextField2, when we add label in the decoration, it aligns to the extreme left. If we try to apply a padding via contentPadding, it duly applies to the label but then, the dropdown menu and the contents of the button also get padding on the left.

After a lot of tries, I've finally decided to not use label at all.

Can you pl look into it and provide a fix for that?

Thanks

@AhmedLSayed9 AhmedLSayed9 added the documentation Improvements or additions to documentation label Dec 8, 2022
@AhmedLSayed9
Copy link
Owner

Wrap your label with padding:

label: Padding(
      padding: EdgeInsets.only(left: 20.0),
      child: Text('hello'),
    ),

@ShahoodulHassan
Copy link
Author

ShahoodulHassan commented Dec 8, 2022

I've tried this as well but then there is a huge gap between the label and the outline border on the left. It looks even more awkward.

Screenshot 2022-12-08 174606_054639

@AhmedLSayed9
Copy link
Owner

That's correct.
Well, That's the default behavior of InputDecoration and this is related to flutter.
You can use floatingLabelAlignment to align label to center instead but It's not possible to set padding for the label without affecting the hint/contents.

@AhmedLSayed9
Copy link
Owner

Btw, as you have padding for the contents anyway, why not using contentPadding of left and only use right padding for the hint/contents ?

@ShahoodulHassan
Copy link
Author

ShahoodulHassan commented Dec 8, 2022

Btw, as you have padding for the contents anyway, why not using contentPadding of left and only use right padding for the hint/contents ?

I don't think I understood this properly.

Anyway, here is my content padding in the InputDecorator -> contentPadding: EdgeInsets.zero, and here is my button padding in DropdownButton2 -> buttonPadding: const EdgeInsets.fromLTRB(0, 6, 6, 6)

@AhmedLSayed9
Copy link
Owner

Can you share a small sample code ?

@AhmedLSayed9
Copy link
Owner

Here's an example (hence: contentPadding, itemPadding, dropdownWidth and offset properties):

final List<String> genderItems = [
  'Male',
  'Female',
];

String? selectedValue;

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: SizedBox(
        width: 300,
        child: DropdownButtonFormField2(
          decoration: InputDecoration(
            isDense: true,
            label: const Text("Label"),
            contentPadding: const EdgeInsets.symmetric(horizontal: 20),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(15),
            ),
          ),
          hint: const Text(
            'Select Your Gender',
            style: TextStyle(fontSize: 14),
          ),
          icon: const Icon(
            Icons.arrow_drop_down,
            color: Colors.black45,
          ),
          buttonHeight: 60,
          itemPadding: const EdgeInsets.only(left: 20),
          dropdownWidth: 300,
          offset: const Offset(-20, 0),
          items: genderItems
              .map((item) => DropdownMenuItem<String>(
                    value: item,
                    child: Text(
                      item,
                      style: const TextStyle(
                        fontSize: 14,
                      ),
                    ),
                  ))
              .toList(),
          onChanged: (value) {
            //Do something when changing the item if you want.
          },
        ),
      ),
    ),
  );
}

@ShahoodulHassan
Copy link
Author

ShahoodulHassan commented Dec 8, 2022

Can you share a small sample code ?

I've created this widget as a sample taking the same properties as that in my actual code.

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

  @override
  State<MyDropdownButton> createState() => _MyDropdownButtonState();
}

class _MyDropdownButtonState extends State<MyDropdownButton> {
  final List<String> customers = [
    'Customer 1',
    'Customer 2',
  ];

  String? selectedCustomer;

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.fromLTRB(8.0, 16.0, 8.0, 0.0),
      child: Expanded(
        child: InputDecorator(
          decoration: InputDecoration(
            filled: true,
            fillColor: Colors.teal.shade50,
            contentPadding: EdgeInsets.zero,
            enabledBorder: OutlineInputBorder(
              borderSide: BorderSide(
                color: Colors.teal.shade800,
              ),
            ),
            label: Padding(
              padding: const EdgeInsets.only(left: 20.0),
              child: Text(
                'Customer',
                style: TextStyle(
                  color: Colors.teal.shade800,
                ),
              ),
            ),
          ),
          child: DropdownButtonHideUnderline(
            child: DropdownButton2<String>(
              buttonPadding: const EdgeInsets.fromLTRB(0, 6, 6, 6),
              hint: Text(
                'Select customer',
                style: TextStyle(
                  color: Colors.teal.shade800,
                ),
              ),
              style: const TextStyle(
                color: Colors.black,
              ),
              selectedItemBuilder: (context) {
                return customers.map((element) {
                  return Align(
                    alignment: Alignment.centerLeft,
                    child: Text(
                      element,
                      style: TextStyle(
                        color: Colors.teal.shade800,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  );
                }).toList();
              },
              icon: Icon(
                Icons.arrow_drop_down,
                color: Colors.teal.shade800,
              ),
              items: customers
                  .map((item) => DropdownMenuItem<String>(
                        value: item,
                        child: Text(
                          item,
                          style: const TextStyle(
                            fontSize: 14,
                          ),
                        ),
                      ))
                  .toList(),
              isExpanded: true,
              value: selectedCustomer,
              onChanged: (value) {
                setState(() => selectedCustomer = value);
              },
            ),
          ),
        ),
      ),
    );
  }
}

@AhmedLSayed9
Copy link
Owner

same idea :)

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

  @override
  State<MyDropdownButton> createState() => _MyDropdownButtonState();
}

class _MyDropdownButtonState extends State<MyDropdownButton> {
  final List<String> customers = [
    'Customer 1',
    'Customer 2',
  ];

  String? selectedCustomer;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          margin: const EdgeInsets.fromLTRB(8.0, 16.0, 8.0, 0.0),
          child: InputDecorator(
            decoration: InputDecoration(
              filled: true,
              fillColor: Colors.teal.shade50,
              contentPadding: const EdgeInsets.symmetric(horizontal: 20),
              enabledBorder: OutlineInputBorder(
                borderSide: BorderSide(
                  color: Colors.teal.shade800,
                ),
              ),
              label: Text(
                'Customer',
                style: TextStyle(
                  color: Colors.teal.shade800,
                ),
              ),
            ),
            child: DropdownButtonHideUnderline(
              child: DropdownButton2<String>(
                buttonPadding: const EdgeInsets.symmetric(vertical: 6),
                itemPadding: const EdgeInsets.symmetric(horizontal: 20),
                dropdownWidth: MediaQuery.of(context).size.width -
                    (8.0 + 8.0), //L&R container's margin
                offset: const Offset(-20, 0),
                hint: Text(
                  'Select customer',
                  style: TextStyle(
                    color: Colors.teal.shade800,
                  ),
                ),
                style: const TextStyle(
                  color: Colors.black,
                ),
                selectedItemBuilder: (context) {
                  return customers.map((element) {
                    return Align(
                      alignment: Alignment.centerLeft,
                      child: Text(
                        element,
                        style: TextStyle(
                          color: Colors.teal.shade800,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    );
                  }).toList();
                },
                icon: Icon(
                  Icons.arrow_drop_down,
                  color: Colors.teal.shade800,
                ),
                items: customers
                    .map((item) => DropdownMenuItem<String>(
                          value: item,
                          child: Text(
                            item,
                            style: const TextStyle(
                              fontSize: 14,
                            ),
                          ),
                        ))
                    .toList(),
                isExpanded: true,
                value: selectedCustomer,
                onChanged: (value) {
                  setState(() => selectedCustomer = value);
                },
              ),
            ),
          ),
        ),
      ),
    );
  }
}

@ShahoodulHassan
Copy link
Author

ShahoodulHassan commented Dec 8, 2022

Awesome!
As far as I can understand, dropdownWidth and offset made all the difference here.

However, just out of curiosity, if in above code, I remove dropdownWidth and offset, I get the following result. This is something that I kept struggling with without knowing why the contents of the button ('Select customer', in this case) were being wrongly aligned. Is button's width somehow linked with the dropdownWidth?

Screenshot 2022-12-08 223854

@AhmedLSayed9
Copy link
Owner

When both buttonWidth & dropdownWidth is null (not used), their width will be calculated from the maximum width of items or hint text IndexedStack.
Therefore, MenuHorizontalPadding (itemPadding+dropdownPadding+dropdownScrollPadding) need to be added to that IndexedStack so Both button and menu Adapt to max items width when buttonWidth & dropdownWidth is null.

so that behavior is necessary for another use case not yours. Adding any dropdownWidth value for your case will prevent adding that default MenuHorizontalPadding. but you need your dropdown menu to be equal to the button which in your case is screen size - 16.0 (right and left margin).

@AhmedLSayed9
Copy link
Owner

AhmedLSayed9 commented Dec 8, 2022

All that tricky work is necessary because of the need to add contentPadding in your case. which is added to the InputDecorator around the DropdownButton2 so you need to manually consider it with dropdown width too :)

@ShahoodulHassan
Copy link
Author

After having a better clarity on these concepts, I've further finetuned the UI by adding an icon on the left of the DropdownButton2 widget. 'dropdownWidth' had to be made dependent on the width of InputDecorator this time, instead of the width retrieved from MediaQuery. Here is the final result:

Screenshot 2022-12-09 000445

I must say I've learned a lot about this widget today and I'll be way more confident and comfortable using it in future. Thanks a lot for taking time to explain the underlying concepts and providing a fix suitable to my particular use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

2 participants