Search code examples
flutterdynamiclayoutdrop-down-menuon-screen-keyboard

How to adjust Flutter DropdownMenu height based on onscreen keyboard height?


In my following piece of code, I intend to set the height of my DropdownMenu according to the presence/absence of onscreen keyboard. I have also considered height of other elements like AppBar, and TextField while calculating the appropriate height of the dropdown menu (for details please refer to the code). I've not been able to update properly the height of the keyboard heightKeyboard variable in my code such that the computation of heightDropdownMenu can use it. The heightKeyboard remains unupdated and hence the height of dropdown menu does not adapt to the presence of onscreen keyboard, and some part of it hides below the keyboard such that the user can't access them.

What I've tried: In an attempt to fix it, I tried implementing InkWell around the TextField and in its onTap property I tried to update the keyboard height and then dropdown height, but this didn't seem to be working, also I don't see the print() function within it working as well, and so can't say if the values are being updated or not.

Hope someone would help me out. Thank you.

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

const List<String> list = <String>[
  "A",
  "B",
  "C",
  "D",
  "E",
  "F",
  "G",
  "H",
  "I",
  "J",
];

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey? _key01 = GlobalKey();
  final GlobalKey? _key02 = GlobalKey();

  late final TextEditingController firstController;

  late Size screenSize;
  double screenWidth = 0;
  double screenHeight = 0;

  double heightAppBar = 0;
  double heightTextField = 0;
  double heightKeyboard = 0;
  double heightOthersCummulative = 0;
  double heightAdjustment = 60;
  double heightDropdownMenu = 0;

  void setDropdownMenuHeight(
      double heightAppBar, double heightTextField, double heightKeyboard) {
    if (heightAppBar > 0 && heightTextField > 0) {
      heightDropdownMenu = screenHeight -
          heightAppBar -
          heightTextField -
          heightKeyboard -
          heightOthersCummulative -
          heightAdjustment;
      // return heightDropdownMenu;
    } else {
      heightDropdownMenu = 150;
      // return heightDropdownMenu;
    }
  }

  late String dropdownValue = list.first;

  String result = "";
  String effective_available_screen_height = "";
  String dimensions = "";

  FlutterView view = WidgetsBinding.instance.platformDispatcher.views.first;

  late Size size;
  double width = 0;
  double height = 0;
  double height01 = 0;

  @override
  void initState() {
    super.initState();
    firstController = TextEditingController();

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      setState(() {
        heightAppBar = _key01?.currentContext?.size?.height ?? -1;
        heightTextField = _key02?.currentContext?.size?.height ?? -1;
        heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
        setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
        dimensions =
            "KeyboardHeight=${heightKeyboard}\nDropdownMenuHeight=${heightDropdownMenu}\nAppBarHeight=${_key01?.currentContext?.size?.height}\nTextFieldHeight=${_key02?.currentContext?.size?.height}";
      });
    });
  }

  @override
  void dispose() {
    firstController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    screenSize = MediaQuery.of(context).size;
    screenWidth = screenSize.width;
    screenHeight = screenSize.height;

    Size ph_size = view.physicalSize;
    double ph_height = ph_size.height;

    size = MediaQuery.of(context).size;
    width = size.width;
    height = size.height;

    final padding = MediaQuery.of(context).viewPadding;
    height01 = height - padding.top - padding.bottom;

    effective_available_screen_height = "$height01";

    // Height of Keyboard when onscreen (ohterwise zero):
    double keyboard_height = MediaQuery.of(context).viewInsets.bottom;

    return Scaffold(
      appBar: AppBar(
        key: _key01,
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Container(
        padding: const EdgeInsets.all(5.0),
        child: Column(
          children: [
            Container(
              child: Row(
                children: [
                  Expanded(
                    flex: 5,
                    child: Container(
                      padding: const EdgeInsets.all(5.0),
                      child: InkWell(
                        onTap: () {
                          setState(() {
                            heightKeyboard =
                                MediaQuery.of(context).viewInsets.bottom;
                            setDropdownMenuHeight(
                                heightAppBar, heightTextField, heightKeyboard);
                            // THESE PRINT DOES NOT SEEM TO PRINT ONTAP:
                            print("==========[InkWell]$heightDropdownMenu");
                            print("========[InkWell](kbdHt=)$heightKeyboard");
                          });
                        },
                        child: TextField(
                          key: _key02,
                          style: const TextStyle(
                            fontSize: 16,
                          ),
                          controller: firstController,
                          keyboardType: TextInputType.number,
                          onChanged: (value) {
                            setState(() {
                              result = value;
                              /*
                              // The following gave error:
                              dimensions =
                                  "${_key01?.currentContext.size.height}";
                              */
                              dimensions =
                                  "${_key01?.currentContext?.size?.height}";
                            });
                          },
                        ),
                      ),
                    ),
                  ),
                  Expanded(
                    flex: 5,
                    child: Container(
                      padding: const EdgeInsets.all(5.0),
                      child: DropdownMenu<String>(
                        menuHeight: heightDropdownMenu,
                        // menuHeight: 300,
                        textStyle: const TextStyle(
                          fontSize: 16.0,
                        ),
                        initialSelection: dropdownValue,
                        onSelected: (String? value) {
                          print("==========[DropdownMenu]$heightDropdownMenu");
                          print(
                              "========[DropdownMenu](kbdHt=)$heightKeyboard");
                          setState(() {
                            // codes here
                          });
                        },
                        dropdownMenuEntries:
                            list.map<DropdownMenuEntry<String>>((String value) {
                          return DropdownMenuEntry<String>(
                              value: value, label: value);
                        }).toList(),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            Text("Result = $result"),
            Text("Total physical height = $ph_height"),
            Text("Total logical height = $height"),
            Text("Onscreen Keyboard height = $keyboard_height"),
            Text(
                "Working Height Available (logical) = $effective_available_screen_height"),
            Text("Dimensions: $dimensions"),
          ],
        ),
      ),
    );
  }
}

Solution

  • You can't use InkWell because the TextField will intercept the gesture, what you can do is use a FocusNode, set it into the TextField en listen when the focus change, then wait some milliseconds to wait the keyboard appears and recalculate the size.

    Result:

    enter image description here

    Code:

    class _MyHomePageState extends State<MyHomePage> {
      final GlobalKey _key01 = GlobalKey();
      final GlobalKey _key02 = GlobalKey();
    
      late final TextEditingController firstController;
    
      late Size screenSize;
      double screenWidth = 0;
      double screenHeight = 0;
    
      double heightAppBar = 0;
      double heightTextField = 0;
      double heightKeyboard = 0;
      double heightOthersCummulative = 0;
      double heightAdjustment = 60;
      double heightDropdownMenu = 0;
    
      void setDropdownMenuHeight(
          double heightAppBar, double heightTextField, double heightKeyboard) {
        if (heightAppBar > 0 && heightTextField > 0) {
          heightDropdownMenu = screenHeight -
              heightAppBar -
              heightTextField -
              heightKeyboard -
              heightOthersCummulative -
              heightAdjustment;
          // return heightDropdownMenu;
        } else {
          heightDropdownMenu = 150;
          // return heightDropdownMenu;
        }
      }
    
      late String dropdownValue = list.first;
    
      String result = "";
      String effective_available_screen_height = "";
      String dimensions = "";
    
      FlutterView view = WidgetsBinding.instance.platformDispatcher.views.first;
    
      late Size size;
      double width = 0;
      double height = 0;
      double height01 = 0;
      final FocusNode _focusNode = FocusNode();
    
      @override
      void initState() {
        super.initState();
        firstController = TextEditingController();
        _focusNode.addListener(_onFocusChange);
    
        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
          setState(() {
            heightAppBar = _key01.currentContext?.size?.height ?? -1;
            heightTextField = _key02.currentContext?.size?.height ?? -1;
            heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
            setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
            dimensions =
                "KeyboardHeight=$heightKeyboard\nDropdownMenuHeight=$heightDropdownMenu\nAppBarHeight=${_key01.currentContext?.size?.height}\nTextFieldHeight=${_key02.currentContext?.size?.height}";
          });
        });
      }
    
      void _onFocusChange() {
        Future.delayed(const Duration(milliseconds: 500), () {
          setState(() {
            heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
            setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
          });
        });
      }
    
      @override
      void dispose() {
        _focusNode.removeListener(_onFocusChange);
        _focusNode.dispose();
        firstController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        screenSize = MediaQuery.of(context).size;
        screenWidth = screenSize.width;
        screenHeight = screenSize.height;
    
        Size phSize = view.physicalSize;
        double phHeight = phSize.height;
    
        size = MediaQuery.of(context).size;
        width = size.width;
        height = size.height;
    
        final padding = MediaQuery.of(context).viewPadding;
        height01 = height - padding.top - padding.bottom;
    
        effective_available_screen_height = "$height01";
    
        // Height of Keyboard when onscreen (ohterwise zero):
        double keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
    
        return Scaffold(
          appBar: AppBar(
            key: _key01,
            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
            title: Text(widget.title),
          ),
          body: GestureDetector(
            onTap: () {
              FocusScope.of(context).requestFocus(FocusNode());
            },
            child: Container(
              padding: const EdgeInsets.all(5.0),
              child: Column(
                children: [
                  Container(
                    child: Row(
                      children: [
                        Expanded(
                          flex: 5,
                          child: Container(
                            padding: const EdgeInsets.all(5.0),
                            child: TextField(
                              key: _key02,
                              style: const TextStyle(
                                fontSize: 16,
                              ),
                              controller: firstController,
                              keyboardType: TextInputType.number,
                              focusNode: _focusNode,
                              onChanged: (value) {
                                setState(() {
                                  result = value;
                                  /*
                                  // The following gave error:
                                  dimensions =
                                      "${_key01?.currentContext.size.height}";
                                  */
                                  dimensions =
                                      "${_key01.currentContext?.size?.height}";
                                });
                              },
                            ),
                          ),
                        ),
                        Expanded(
                          flex: 5,
                          child: Container(
                            padding: const EdgeInsets.all(5.0),
                            child: DropdownMenu<String>(
                              menuHeight: heightDropdownMenu,
                              // menuHeight: 300,
                              textStyle: const TextStyle(
                                fontSize: 16.0,
                              ),
                              initialSelection: dropdownValue,
                              onSelected: (String? value) {
                                print(
                                    "==========[DropdownMenu]$heightDropdownMenu");
                                print(
                                    "========[DropdownMenu](kbdHt=)$heightKeyboard");
                                setState(() {
                                  // codes here
                                });
                              },
                              dropdownMenuEntries: list
                                  .map<DropdownMenuEntry<String>>((String value) {
                                return DropdownMenuEntry<String>(
                                    value: value, label: value);
                              }).toList(),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                  Text("Result = $result"),
                  Text("Total physical height = $phHeight"),
                  Text("Total logical height = $height"),
                  Text("Onscreen Keyboard height = $keyboardHeight"),
                  Text(
                      "Working Height Available (logical) = $effective_available_screen_height"),
                  Text("Dimensions: $dimensions"),
                ],
              ),
            ),
          ),
        );
      }
    }