Mary
Mary

Reputation: 20495

When the keyboard appears, the Flutter widgets resize. How to prevent this?

I have a Column of Expanded widgets like this:

 return new Container(
      child: new Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          new Expanded(
            flex: 1,
            child: convertFrom,
          ),
          new Expanded(
            flex: 1,
            child: convertTo,
          ),
          new Expanded(
            flex: 1,
            child: description,
          ),
        ],
      ),
    );

It looks like this:

enter image description here

convertFrom, includes a TextField. When I tap on this text field, the Android keyboard appears on the screen. This changes the screen size, so the widgets resize like this:

enter image description here

Is there a way to have the keyboard "overlay" the screen so that my Column doesn't resize? If I don't use Expanded widgets and hardcode a height for each widget, the widgets don't resize, but I get the black-and-yellow striped error when the keyboard appears (because there isn't enough space). This also isn't flexible for all screen sizes.

I'm not sure if this is an Android-specific or Flutter-specific.

Upvotes: 297

Views: 314732

Answers (27)

M. Massula
M. Massula

Reputation: 4820

In my case I was using the form inside a dialog.

To prevent the widget overflow I used the scrollable attribute from the AlertDialog, setting it to true solved my problem.

Upvotes: 1

Uzzam Altaf
Uzzam Altaf

Reputation: 403

Using SingleChildScrollView with below provided physics and primary along with ConstrainedBox and IntrinsicHeight worked for me.

return Scaffold(
  body: SingleChildScrollView(
    physics:
        const ClampingScrollPhysics(parent: NeverScrollableScrollPhysics()),
    primary: false,
    child: ConstrainedBox(
      constraints: BoxConstraints(
        minWidth: MediaQuery.of(context).size.width,
        minHeight: MediaQuery.of(context).size.height,
      ),
      child: IntrinsicHeight(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text(
              "SIGNUP",
              style: TextStyle(
                fontSize: largeTextSize,
                fontWeight: FontWeight.bold,
              ),
            ),

Upvotes: 1

Ahmed M. Hassan
Ahmed M. Hassan

Reputation: 1286

I've built this reusable widget which expands to fit the screen if collapsed and collapses if the keyboard is shown.

How to use

child: IntrinsicHeightScrollView(
  child: Column(
    children: [
      ...
      Spacer(), // Add spacer where you need the view to expand  
      ...
    ],
  )
),

Screenshots

Expanded Collapsed
enter image description here enter image description here

Source

import 'package:flutter/material.dart';

/// A scrollable view that enforces an intrinsic height based on its content.
///
/// The `IntrinsicHeightScrollView` widget is used to create a scrollable
/// view that adjusts its height to fit its content. It ensures that the
/// child widget inside it does not overflow vertically.
///
/// Example usage:
///
/// ```dart
/// IntrinsicHeightScrollView(
///   child: LoginForm(),
/// )
/// ```
///
/// In this example, the `IntrinsicHeightScrollView` will adjust its height
/// to accommodate the `LoginForm` widget without causing vertical overflow.
final class IntrinsicHeightScrollView extends StatelessWidget {
  /// Creates an `IntrinsicHeightScrollView` with the specified [child].
  ///
  /// The [child] is the widget that will be displayed within the scroll view.
  const IntrinsicHeightScrollView({
    required this.child,
    Key? key,
  }) : super(key: key);

  /// The child widget to be displayed within the scroll view.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraint) {
        return SingleChildScrollView(
          child: ConstrainedBox(
            constraints: BoxConstraints(minHeight: constraint.maxHeight),
            child: IntrinsicHeight(
              child: child,
            ),
          ),
        );
      },
    );
  }
}

Upvotes: 3

Covenant T. Junior
Covenant T. Junior

Reputation: 66

The Scaffold property resizeToAvoidBottomInset does the job. And from the documentation, https://api.flutter.dev/flutter/material/Scaffold/resizeToAvoidBottomInset.html, this property is a boolean, so setting it to false does the trick. Here is a use case:

@override
Widget build(BuildContext context) {
  return Scaffold(
    resizeToAvoidBottomInset: false,
    backgroundColor: Colors.grey[300],
    appBar: AppBar(
      toolbarHeight: 50,
      leading: const Icon(
        Icons.menu,
        color: Color.fromARGB(255, 138, 60, 55),
      ),
      title: const Text(
        'Tokyo',
        style: TextStyle(
          fontFamily: 'DM Serif Display',
          fontSize: 30.0,
          color: Color.fromARGB(255, 138, 60, 55),
        ),
      ),
      centerTitle: true,
      backgroundColor: Colors.grey[300],
    ),
    body: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          // Add your widgets here
        ],
      ),
    ),
  );
}

A screenshot is also provided.

resizeToAvoidBottomInset example

Upvotes: 3

Ashutosh Kumar
Ashutosh Kumar

Reputation: 101

My approach is to use SingleChildScrollView with the BouncingScrollPhysics physics.

 SingleChildScrollView(
  physics: BouncingScrollPhysics(),
  child: Container(),
) 

Upvotes: 4

Rudr Thakur
Rudr Thakur

Reputation: 410

I was facing the same issue and I started to try random solutions to fix it and suddenly this fixed it.

Wrap the main parent container within a SingleChildScrollView() and give it the device height i.e device_height = MediaQuery.of(context).size.height.

This will make the entire page scrollable but does not resize any widget.

Upvotes: 0

Ashkan Sarlak
Ashkan Sarlak

Reputation: 7344

For a login screen or similar, in addition to solutions provided by all other top answers, I would also add reverse: true on SingleChildScrollView. These screens typically consist of some image or logo at the top half, and some text fields (username, password) and a button (sign in) at the bottom.

So in this particular case (which I constantly encounter, and I refer to this thread mostly because of it), it makes sense to scroll all the way down, so that all the text fields and button(s) are visible.

Code:

@override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: true,
      body: SingleChildScrollView(
        reverse: true,
        physics: const ClampingScrollPhysics(),
        child: SizedBox(
          height: MediaQuery.of(context).size.height,
          child: PutYourContentHere(),
        ),
      ),
    );
  }

Upvotes: 3

Matheus Cardoso
Matheus Cardoso

Reputation: 131

According to the latest version of flutter resizeToAvoidBottomPadding is deprecated and should not be used, now you must use resizeToAvoidBottomInset.

Example:

class YourWidget extends StatelessWidget {
  const YourWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
       resizeToAvoidBottomInset: false, // this property here
       body: ...
    )
  }
}

Warning: you can only use this if your Widget has a Scaffold, otherwise you'll have to search specifically your case.

Upvotes: 1

Adam Bridges
Adam Bridges

Reputation: 518

Within the Scaffold, set resizeToAvoidBottomInset property to false.

However, if you want to have certain Widgets resize within a ListView after setting the Scaffold, then here is a solution I am currently using:

class _YourCustomWidgetState extends State<YourCustomWidget> {

  final ScrollController _scrollController = ScrollController();
  final FocusNode _focusableWidgetNode = FocusNode();

  @override
  Widget build(BuildContext context) {

    final double keyboardHeight = MediaQuery.of(context).viewInsets.bottom;

    // This is just if you want to have the last focusable Widget scroll into view:
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      if (_focusableWidgetNode.hasPrimaryFocus) {
        // Tweak this if you want to change where you want to scroll to but 
        // it should be rarely necessary:
        final double bottomOffset = _scrollController.position.maxScrollExtent;
        _scrollController.animateTo(bottomOffset,
            duration: Duration(milliseconds: 100), curve: Curves.linear);
      }
    });

  return Column(
   children: [
    // Widgets
    ListView(
      controller: _scrollController,
      shrinkWrap: true,
      padding: EdgeInsets.only(
         // Just ensure this is set:
         bottom: keyboardHeight
      ),
      children: [
        // Widgets
      ],
    ),
    // Widgets
  ],
);
  }

}

Alternatively, a SingleChildScrollView as suggested by others is another way but I've found it doesn't work well if you have Widgets between a ListView like I do.

Upvotes: 1

ArtiomLK
ArtiomLK

Reputation: 2260

Set resizeToAvoidBottomInset to false instead of resizeToAvoidBottomPadding which is deprecated.

    return Scaffold(
      resizeToAvoidBottomInset : false,
      body: YourWidgets(),
    );

Upvotes: 44

vishnu reji
vishnu reji

Reputation: 398

You can use

Scaffold(
  resizeToAvoidBottomInset: false, 
  ... 
)

or you can wrap your widgets in

SingleChildScrollView

Upvotes: 5

Duncan Jones
Duncan Jones

Reputation: 69389

Most other answers suggest using resizeToAvoidBottomPadding=false. In my experience this allows the keyboard to cover up text fields if they are underneath where the keyboard would appear.

My current solution is to force my column to be the same height as the screen, then place it in a SingleChildScrollView so that Flutter automatically scrolls my screen up just enough when the keyboard is used.

Widget build(BuildContext context) {
  return Scaffold(
    body: SingleChildScrollView(
      physics: const NeverScrollableScrollPhysics(),
      child: ConstrainedBox(
        constraints: BoxConstraints(
          minWidth: MediaQuery.of(context).size.width,
          minHeight: MediaQuery.of(context).size.height,
        ),
        child: IntrinsicHeight(
          child: Column(
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[
              // CONTENT HERE
            ],
          ),
        ),
      ),
    ),
  );
}

I use NeverScrollableScrollPhysics so that the user cannot scroll around themselves.

Upvotes: 132

Zakaria Ferzazi
Zakaria Ferzazi

Reputation: 472

i had same problem with my screen and here how i fix it :

Scaffold(
  resizeToAvoidBottomInset: false, 
  ... 
)

enter image description here

Upvotes: 4

Muhammad Nadeem
Muhammad Nadeem

Reputation: 320

This strange behavoiur of media query when we click on textfield and keyBoard open media query rebulid one more same page in stack.

MaterialApp( useInheritedMediaQuery: true,)

useInheritedMediaQuery to true will help you.

Upvotes: 0

user18530386
user18530386

Reputation: 21

This will scroll with your keypad and collapse size when keyboard disappears.

showModalBottomSheet(
      isScrollControlled: true,
      context: context,
      builder: (context) {
        return Padding(
            padding: MediaQuery.of(context).viewInsets,
            child:SingleChildScrollView(
            physics: ClampingScrollPhysics(),
            child: Container(.......)));

Upvotes: 2

Yauheni Prakapenka
Yauheni Prakapenka

Reputation: 1734

Feature:

  • Background image does not resize when keyboard is opened
  • Ability to scroll elements hidden behind the keyboard
import 'package:flutter/material.dart';

SizedBox addPaddingWhenKeyboardAppears() {
  final viewInsets = EdgeInsets.fromWindowPadding(
    WidgetsBinding.instance!.window.viewInsets,
    WidgetsBinding.instance!.window.devicePixelRatio,
  );

  final bottomOffset = viewInsets.bottom;
  const hiddenKeyboard = 0.0; // Always 0 if keyboard is not opened
  final isNeedPadding = bottomOffset != hiddenKeyboard;

  return SizedBox(height: isNeedPadding ? bottomOffset : hiddenKeyboard);
}

/// The size of the screen.
class ScreenSizeService {
  final BuildContext context;

  const ScreenSizeService(
    this.context,
  );

  Size get size => MediaQuery.of(context).size;
  double get height => size.height;
  double get width => size.width;
}

class LoginPage extends StatelessWidget {
  final _imageUrl =
      'https://images.unsplash.com/photo-1631823460501-e0c045fa716f?ixid=MnwxMjA3fDB8MHxlZGl0b3JpYWwtZmVlZHwyNHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60';

  const LoginPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final screenWidth = ScreenSizeService(context).width;
    final screenHeight = ScreenSizeService(context).height;

    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Container(
        decoration: BoxDecoration(
          image: DecorationImage(
            image: NetworkImage(_imageUrl),
            fit: BoxFit.cover,
          ),
        ),
        child: SingleChildScrollView(
          child: ConstrainedBox(
            constraints: BoxConstraints(
              minWidth: screenWidth,
              minHeight: screenHeight,
            ),
            child: Column(
              children: [
                ...List.generate(6, (index) {
                  return Column(
                    children: [
                      Container(
                        height: 60,
                        width: double.maxFinite,
                        color: Colors.pink[100],
                        child: Center(child: Text('$index')),
                      ),
                      const SizedBox(height: 40),
                    ],
                  );
                }),
                Container(color: Colors.white, child: const TextField()),
                addPaddingWhenKeyboardAppears(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Upvotes: 7

Amimul Ihsan
Amimul Ihsan

Reputation: 156

Setting the value false for resizeToAvoidBottomInset worked fine for me.

Also, resizeToAvoidBottomPadding worked fine for me. You can use either one.

Upvotes: 6

Phani Pavan
Phani Pavan

Reputation: 385

Might Be too late to answer but the following worked for me

Scaffold(
  body: SingleChildScrollView(
    physics: ClampingScrollPhysics(parent: NeverScrollableScrollPhysics()),
      child: Container(

Clamping will auto scroll to make textfield visible, its parent NeverScrollable will not allow the user to scroll.

Upvotes: 12

Pumuckelo
Pumuckelo

Reputation: 389

This is the perfect solution that gives you the ability to have a full-screen column inside of a SingleChildScrollView. This allows you to create a perfect layout for all screen sizes + the ability to have a scrollable screen that only scrolls if you open the keyboard or if the screen overflows after rendering (e.g. text input field validation)

class PerfectFullScreen extends StatelessWidget {
  const PerfectFullScreen({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SafeArea(
        child: Scaffold(
      backgroundColor: Theme.of(context).backgroundColor,
      appBar: AppBar(),
      body: Builder(
        builder: (context) => SingleChildScrollView(
            child: ConstrainedBox(
                constraints: BoxConstraints(
                    minHeight: MediaQuery.of(context).size.height -
                        (MediaQuery.of(context).padding.top + kToolbarHeight)),
                child: IntrinsicHeight(
                    child: Column(
                  children: [
                    Container(
                      height: randomImageHeight,
                      child: Image.asset(
                        "assets/images/change_password.png",
                        fit: BoxFit.cover,
                      ),
                    ),
                    Expanded(
                        child: WidgetThatShouldTakeRemainingSpace() )
                  ],
                )))),
      ),
    ));
  }
}

The important part is the ConstrainedBox with the correct BoxConstraints and the InstrinsicHeight Widget.

PS: (MediaQuery.of(context).padding.top + kToolbarHeight) == Height of the Appbar

Upvotes: 2

Saman
Saman

Reputation: 523

The best solution to avoid resizing widgets and also focus on the text field is to use SingleChildScrollView() with ClampingScrollPhysics() physics. Also, remember to set height for its child (ex: use container()), so you can use your widgets through Column():

return Scaffold(
   body: SingleChildScrollView(
      physics: ClampingScrollPhysics(),
      child: Container(
         height: size.height,
         child: Column(
            children:[
               TextFormField()
            ],
         ),
      ),
   ),
);

Upvotes: 12

Shady Aziza
Shady Aziza

Reputation: 53347

Updated Answer

resizeToAvoidBottomPadding is now deprecated.

The updated solution is to set resizeToAvoidBottomInset property to false.


Original Answer

In your Scaffold, set resizeToAvoidBottomPadding property to false.

Upvotes: 667

Toni
Toni

Reputation: 5155

My suggestion is to use resizeToAvoidBottomInset: false anyway to prevent widgets from resizing if the keyboard suddenly appears on the screen. For example, if a user uses Facebook chat heads while in your app.

To prevent the keyboard from overlaying widgets, on screens where you need it, I suggest the following approach, where is the height of SingleChildScrollView reduced to the height of the available space. In this case, SingleChildScrollView also scrolls to the focused widget.

final double screenHeight = MediaQuery.of(context).size.height;
final double keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
return Scaffold(
  resizeToAvoidBottomInset: false,
  body: SizedBox(
    height: screenHeight - keyboardHeight,
    child: SingleChildScrollView(
      child: Column(
        children: [
          const SizedBox(height: 200),
          for (var i = 0; i < 10; i++) const TextField()
        ],
      ),
    ),
  ),
);

Upvotes: 23

Javeed Ishaq
Javeed Ishaq

Reputation: 7105

For me changing below item property from true to false

<item name="android:windowFullscreen">false</item>

in file

android/app/src/main/res/values/styles.xml

has made Flutter drag all page content upwards on input focus

Flutter drag all page content upwards on input focus

Upvotes: 3

Den
Den

Reputation: 1725

My approach is to use SingleChildScrollView with the ClampingScrollPhysics physics.

SingleChildScrollView(
  physics: ClampingScrollPhysics(),
  child: Container(),
)

Upvotes: 43

Deepak Jha
Deepak Jha

Reputation: 1609

Well I think if we implement @Aman's solution it will make our app behaves ugly as when the keyboard appears, it will not adjust our viewport of the screen as per available height and it will make out other fields hide behind the keyboard. So I would suggest useSingleChildScrollView instead.

Wrap your code with SingleChildScrollView as given below,

 return new Container(
  child: SingleChildScrollView(
    child: new Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: <Widget>[
      new Expanded(
        flex: 1,
        child: convertFrom,
      ),
      new Expanded(
        flex: 1,
        child: convertTo,
      ),
      new Expanded(
        flex: 1,
        child: description,
      ),
    ],
  ),
 ),
);

Upvotes: 3

yatin deokar
yatin deokar

Reputation: 781

Method 1: Remove android:windowSoftInputMode="adjustResize" from AndroidManifest.xml file (Otherwise it will override flutter code) and add resizeToAvoidBottomPadding: false in Scaffold like below:

Scaffold(
      resizeToAvoidBottomPadding: false,
      appBar: AppBar()
)

Method 2(Not Recommended): Just Add android:windowSoftInputMode="stateVisible" in android AndroidManifest.xml in activity it will only work for Android an Not for IOS like.

<activity
       ...
        android:windowSoftInputMode="stateVisible">

Note: Don't set it to android:windowSoftInputMode="adjustResize"

Upvotes: 7

aptik
aptik

Reputation: 613

Depending on the use case you could also consider using a listview. That would ensure that the contents scroll when there is not enough room. As an example, you can look at the textfield demo in the gallery app

Upvotes: 3

Related Questions