Make only one widget float above the keyboard in Flutter
Asked Answered
O

6

16

I want to display a "Close keyboard" button above the keyboard when it is visible.

I know that the resizeToAvoidBottomInset can impact how the keyboard interact with the rest of the application, however it doesn't do exactly what I want.

I have a background image and others widgets (not shown in the sample below) which should not be resized and not moved when the keyboards is shown. This is an ok behavior when the resizeToAvoidBottomInset attribute is set to false.

However, I would like to add a button which should appear above the keyboard.

How can I do that? I only want one widget floating above the keyboard, not all the app.

Here is a sample code :

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var home = MyHomePage(title: 'Flutter Demo Home Page');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: home,
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _getBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  Widget _getBody() {
    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      Positioned(
        bottom: 0,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
  }
}
Offensive answered 8/7, 2019 at 8:33 Comment(0)
C
27

Your Positioned widget has a bottom of 0, replacing with an appropriate value should do the job.

MediaQuery.of(context).viewInsets.bottom will give you the value of the height covered by the system UI(in this case the keyboard).

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var home = MyHomePage(title: 'Flutter Demo Home Page');
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: home,
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _getBody(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  Widget _getBody() {
    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      Positioned(
        bottom: MediaQuery.of(context).viewInsets.bottom,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
  }
}

gif

Caplin answered 8/7, 2019 at 9:44 Comment(2)
how can this works? does it naturally rebuild every after keyboard show up?Alexei
I know a lot of time has passed, but basically having MediaQuery.of(context) anywhere in your widget means it will rebuild when states of the phone change like rotation, keyboard showing up etc.Nates
H
7

You can use the bottomSheet of a Scaffold widget.

Example:

    return Scaffold(
      appBar: AppBar(
        title: const Text("New Game"),
      ),
      bottomSheet: Container(
          padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
          color: Colors.blue,
          child: const SizedBox(
            width: double.infinity,
            height: 20,
            child: Text("Above Keyboard"),
          ))
...
)

Humdrum answered 29/7, 2022 at 8:41 Comment(0)
S
5

2022 Update

A PR was merged that provides platform-synchronized animations for closing/opening the keyboard. See the PR in effect here.

Detailed Answer

To achieve keyboard-visibility-based animated padding, here are a few modifications over @10101010's great answer:

If you want the bottom change when keyboard changes visibility to be animated AND you want extra padding under your floating child then:

1- Use keyboard_visibility flutter pub

To listen when keyboard is appearing/disappearing, like so:

  bool isKeyboardVisible = false;
  
  @override
  void initState() {
    super.initState();

    KeyboardVisibilityNotification().addNewListener(
      onChange: (bool visible) {
        isKeyboardVisible = visible;
      },
    );
  }

Optionally you can write your own native plugins, but it's already there you can check the pub's git repo.

2- Consume visibility flag in your AnimatedPostioned:

For fine-tuned animated padding, like so:

Widget _getBody() {
    double bottomPadding = 0;
    if (isKeyboardVisible) {
      // when keyboard is shown, our floating widget is above the keyboard and its accessories by `16`
      bottomPadding = MediaQuery.of(context).viewInsets.bottom + 16;
    } else {
      // when keyboard is hidden, we should have default spacing
      bottomPadding = 48; // MediaQuery.of(context).size.height * 0.15;
    }

    return Stack(children: <Widget>[
      Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: AssetImage("assets/sample.jpg"), fit: BoxFit.fitWidth)),
        // color: Color.fromARGB(50, 200, 50, 20),
        child: Column(
          children: <Widget>[TextField()],
        ),
      ),
      AnimatedPositioned(
        duration: Duration(milliseconds: 500),
        bottom: bottomPadding,
        left: 0,
        right: 0,
        child: Container(
          height: 50,
          child: Text("Aboveeeeee"),
          decoration: BoxDecoration(color: Colors.pink),
        ),
      ),
    ]);
}

3- Keyboard-specific animation curve and duration for synchronized animation

For now this is still an known ongoing issue

Stolzer answered 14/11, 2019 at 6:27 Comment(0)
D
2

If Stack method Failed??
That Positioned bottom: MediaQuery.of(context).viewInsets.bottom!! Then go through this method

Use bottomsheet and put your button widget there

Wrap with SingleChildScrollView if you need scroll body part

Sample code & attachments below

Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(),
      body: const SingleChildScrollView(
          child: Column(
        children: [
          // your Widgets here
          //Textfield 1
          //Textfield 2
          //Textfield 3
        ],
      )),
      bottomSheet: SizedBox(
        height: 70,
        child: ButtonWidget(
          buttonName: "Continue",
          onpressed: () {
            //Button press code here
            context.pushRoute(const SignUpStepsPageRoute());
          },
        ),
      ));
}

Output will be

Image that keyboard open / close state

Disputable answered 17/5, 2023 at 5:38 Comment(1)
Perfect approach!Babylonian
F
1

You can use bottomSheet parameter of the Scaffold, which keep a persistent bottom sheet. See below code.

class InputScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Close')),
      bottomSheet: Container(
          padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16),
          color: Colors.black,
          child: const SizedBox(width: double.infinity, height: 10)),
      body: Column(
        children: [
          const TextField(
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              hintText: 'Enter your input here',
            ),
          ),
          ElevatedButton(
            onPressed: () {},
            child: const Text('Submit'),
          ),
        ],
      ),
    );
  }
}
Foreboding answered 17/8, 2021 at 13:10 Comment(0)
T
0

check this package, it can show a dismiss button above the keyboard.

Tinder answered 8/7, 2019 at 9:11 Comment(1)
That package is interesting and in the same idea of what I want to do, however the solution mentioned by @Caplin is easier to implement in my case.Offensive

© 2022 - 2024 — McMap. All rights reserved.