简体   繁体   English

将 OverlayEntry 小部件推送到键盘上方

[英]Push OverlayEntry Widget above keyboard

I have created a custom widget where an overlay entry widget is shown right bottom of it with a search bar and a list.我创建了一个自定义小部件,其中覆盖条目小部件显示在其右下方,带有搜索栏和列表。

The problem I am facing is, when the user clicks the textfield inside the overlay widget, the screen does not push up enough for the user to see the textfield.我面临的问题是,当用户单击覆盖小部件内的文本字段时,屏幕不会向上推到足以让用户看到文本字段。 Please check the screenshoot for more details请查看屏幕截图以获取更多详细信息

The code of the custom dropdown button widget is below:自定义下拉按钮小部件的代码如下:

import 'package:flutter/material.dart';

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

  @override
  State<DropDownWidget2> createState() => _DropDownWidget2State();
}

class _DropDownWidget2State extends State<DropDownWidget2> with TickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;
  bool isOpen = false;

  final List<Map<String, dynamic>> _allData = [
    {"id": 1, "name": "Andy", "age": 29},
    {"id": 2, "name": "Aragon", "age": 40},
    {"id": 3, "name": "Bob", "age": 5},
    {"id": 4, "name": "Barbara", "age": 35},
    {"id": 5, "name": "Candy", "age": 21},
    {"id": 6, "name": "Colin", "age": 55},
    {"id": 7, "name": "Audra", "age": 30},
    {"id": 8, "name": "Banana", "age": 14},
    {"id": 9, "name": "Caversky", "age": 100},
    {"id": 10, "name": "Becky", "age": 32},
  ];

  // This list holds the data for the list view
  List<Map<String, dynamic>> _foundData = [];

  final layerLink = LayerLink();
  OverlayEntry? entry;

  @override
  void initState() {
    _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 450));
    _animation = Tween(begin: 0.0, end: -.5)
        .animate(CurvedAnimation(parent: _animationController, curve: Curves.easeOut));
    _foundData = _allData;

    super.initState();
  }

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

  void _handleOnPressed() {
    setState(() {
      isOpen = !isOpen;
      isOpen ? _animationController.forward() : _animationController.reverse();
      showOverLay();
    });
  }

  @override
  Widget build(BuildContext context) {
    return CompositedTransformTarget(
      link: layerLink,
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
            border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(10)),
        child: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('Select one from below'),
            IconButton(
              padding: EdgeInsets.zero,
              iconSize: 18,
              splashRadius: 20,
              splashColor: Colors.greenAccent,
              icon: RotationTransition(
                turns: _animation,
                child: Icon(Icons.expand_more),
              ),
              onPressed: () => _handleOnPressed(),
            )
          ],
        ),
      ),
    );
  }

  Widget itemsWidget() {
    return Container(
      width: MediaQuery.of(context).size.width,
      decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
      padding: EdgeInsets.only(left: 12, top: 4, bottom: 4),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextField(
            onChanged: (value) => {},
            decoration: const InputDecoration(labelText: 'Search', suffixIcon: Icon(Icons.search)),
          ),
          _foundData.isNotEmpty
              ? ListView.separated(
                  padding: EdgeInsets.zero,
                  shrinkWrap: true,
                  itemCount: _foundData.length,
                  itemBuilder: (context, index) {
                    return Text(_foundData[index]['name']);
                  },
                  separatorBuilder: (context, index) {
                    return Divider();
                  },
                )
              : const Text(
                  'No results found',
                  style: TextStyle(fontSize: 14),
                ),
        ],
      ),
    );
  }

  Widget buildOverlay() {
    return Material(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: const BorderSide(color: Colors.black, width: 1),
      ),
      child: AnimatedCrossFade(
        duration: const Duration(microseconds: 500),
        firstChild: Container(), // When you don't want to show menu..
        secondChild: itemsWidget(),
        crossFadeState: isOpen ? CrossFadeState.showSecond : CrossFadeState.showFirst,
      ),
    );
  }

  void showOverLay() {
    final overlay = Overlay.of(context)!;
    final renderBox = context.findRenderObject() as RenderBox;
    final size = renderBox.size;

    entry = OverlayEntry(
        builder: (context) => Positioned(
            width: size.width,
            child: CompositedTransformFollower(
                link: layerLink,
                showWhenUnlinked: false,
                offset: Offset(0, size.height),
                child: buildOverlay())));

    overlay.insert(entry!);
  }
}

As you can observe from the screenshot.正如您从屏幕截图中观察到的那样。 The keyboard moves above overlayentry hiding my textfield and my list items.键盘在覆盖条目上方移动,隐藏了我的文本字段和列表项。 Is there any way I can push the whole overlayEntry above the keyboard?有什么办法可以将整个overlayEntry 推到键盘上方? I've tried many ways like using mediaquery bottom insets and other things but i still could not achieve it.我尝试了很多方法,比如使用 mediaquery 底部插图和其他东西,但我仍然无法实现。

无键盘 . . 带键盘

Maybe you could try one of below:也许您可以尝试以下方法之一:

  • add a padding to the page widget;向页面小部件添加填充;
  • add a padding to the text field;向文本字段添加填充;

On the 1st you may add padding wrapping the body:第一次你可以添加填充包裹身体:

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: MediaQuery.of(context).viewInsets,
      child: The page ...,
    );
  }

With the 2nd option you may add bottom padding equals to the keyboard size.使用第二个选项,您可以添加等于键盘大小的底部填充。 At the page (StatefulWidget), you may add the didChangeDependencies :在页面(StatefulWidget)中,您可以添加didChangeDependencies

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    setState(() {
      _keyboardSize = MediaQuery.of(context).viewInsets.bottom;
    });
  }

And in your TextField, you set the scrollPadding property:在您的 TextField 中,您设置了scrollPadding属性:

  scrollPadding: EdgeInsets.only(
    bottom: _keyboardSize,
  ),

you can try this if that's what you really need to do:如果这是你真正需要做的,你可以试试这个:

Widget itemsWidget() {
                      return Container(
                        width: MediaQuery.of(context).size.width,
                        decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
                        padding: EdgeInsets.only(left: 12, top: 4, bottom: 4),
                        child: SingleChildScrollView(
                          child: Column(
                            mainAxisSize: MainAxisSize.min,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              TextField(
                                onChanged: (value) => {},
                                onTap: (){
                                  // here you can control the phone's default keyboard
                                  FocusScope.of(context).unfocus();
                                },
                                decoration: const InputDecoration(labelText: 'Search', suffixIcon: Icon(Icons.search)),
                              ),
                              _foundData.isNotEmpty
                                  ? ListView.separated(
                                padding: EdgeInsets.zero,
                                shrinkWrap: true,
                                itemCount: _foundData.length,
                                itemBuilder: (context, index) {
                                  return Text(_foundData[index]['name']);
                                },
                                separatorBuilder: (context, index) {
                                  return Divider();
                                },
                              )
                                  : const Text(
                                'No results found',
                                style: TextStyle(fontSize: 14),
                              ),
                            ],
                          ),
                        ),
                      );
                    }

I think you should allow it to be scrollable我认为你应该允许它是可滚动的

Try this尝试这个

you can use some of this 

return Scaffold(
      body: SafeArea(
        child: Center(
          child: Stack(

then on your ItemsWidget然后在你的 ItemsWidget

return Container(
      padding: const EdgeInsets.all(16.0),
      child: SingleChildScrollView(
        reverse: true,
        child: Form(

Some thing along these lines, i edited your code这些方面的一些事情,我编辑了你的代码

import 'package:flutter/material.dart';

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

  @override
  State<DropDownWidget2> createState() => _DropDownWidget2State();
}

class _DropDownWidget2State extends State<DropDownWidget2>
    with TickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;
  bool isOpen = false;

  final List<Map<String, dynamic>> _allData = [
    {"id": 1, "name": "Andy", "age": 29},
    {"id": 2, "name": "Aragon", "age": 40},
    {"id": 3, "name": "Bob", "age": 5},
    {"id": 4, "name": "Barbara", "age": 35},
    {"id": 5, "name": "Candy", "age": 21},
    {"id": 6, "name": "Colin", "age": 55},
    {"id": 7, "name": "Audra", "age": 30},
    {"id": 8, "name": "Banana", "age": 14},
    {"id": 9, "name": "Caversky", "age": 100},
    {"id": 10, "name": "Becky", "age": 32},
  ];

  // This list holds the data for the list view
  List<Map<String, dynamic>> _foundData = [];

  final layerLink = LayerLink();
  OverlayEntry? entry;

  @override
  void initState() {
    _animationController = AnimationController(
        vsync: this, duration: const Duration(milliseconds: 450));
    _animation = Tween(begin: 0.0, end: -.5).animate(
        CurvedAnimation(parent: _animationController, curve: Curves.easeOut));
    _foundData = _allData;

    super.initState();
  }

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

  void _handleOnPressed() {
    setState(() {
      isOpen = !isOpen;
      isOpen ? _animationController.forward() : _animationController.reverse();
      showOverLay();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(
          child: Stack(
            children: [
              Container(
                alignment: Alignment.center,
                child: CompositedTransformTarget(
                  link: layerLink,
                  child: Container(
                    padding: const EdgeInsets.symmetric(horizontal: 4),
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.black),
                        borderRadius: BorderRadius.circular(10)),
                    child: Row(
                      mainAxisSize: MainAxisSize.max,
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        const Text('Select one from below'),
                        IconButton(
                          padding: EdgeInsets.zero,
                          iconSize: 18,
                          splashRadius: 10,
                          splashColor: Colors.greenAccent,
                          icon: RotationTransition(
                            turns: _animation,
                            child: const Icon(Icons.expand_more),
                          ),
                          onPressed: () => _handleOnPressed(),
                        )
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget itemsWidget() {
    return Container(
        width: MediaQuery.of(context).size.width,
        decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
        padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4),
        child: SingleChildScrollView(
          reverse: true,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              TextField(
                onChanged: (value) => {},
                decoration: const InputDecoration(
                    labelText: 'Search', suffixIcon: Icon(Icons.search)),
              ),
              _foundData.isNotEmpty
                  ? ListView.separated(
                      padding: EdgeInsets.zero,
                      shrinkWrap: true,
                      itemCount: _foundData.length,
                      itemBuilder: (context, index) {
                        return Text(_foundData[index]['name']);
                      },
                      separatorBuilder: (context, index) {
                        return const Divider();
                      },
                    )
                  : const Text(
                      'No results found',
                      style: TextStyle(fontSize: 14),
                    ),
            ],
          ),
        ));
  }

  Widget buildOverlay() {
    return Material(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: const BorderSide(color: Colors.black, width: 1),
      ),
      child: AnimatedCrossFade(
        duration: const Duration(microseconds: 500),
        firstChild: Container(), // When you don't want to show menu..
        secondChild: itemsWidget(),
        crossFadeState:
            isOpen ? CrossFadeState.showSecond : CrossFadeState.showFirst,
      ),
    );
  }

  void showOverLay() {
    final overlay = Overlay.of(context)!;
    final renderBox = context.findRenderObject() as RenderBox;
    final size = renderBox.size;

    entry = OverlayEntry(
        builder: (context) => Positioned(
            width: size.width,
            child: CompositedTransformFollower(
                link: layerLink,
                showWhenUnlinked: false,
                offset: Offset(0, size.height),
                child: buildOverlay())));

    overlay.insert(entry!);
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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