flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
166.18k stars 27.49k forks source link

Is that possible to replace the keyboard position with another widget and keep TextField position unchanged during keyboard animation? #32583

Open sgon00 opened 5 years ago

sgon00 commented 5 years ago

Flutter version

[✓] Flutter (Channel master, v1.5.9-pre.205, on Mac OS X 10.13.6 17G4015, locale en-CN)

Hi, I will try my best to describe the problem. Basically, I want to achieve what many messenger apps do when clicking Emoji icon when keyboard is present. All messenger apps including Facebook messenger, WeChat etc.. are doing the same thing. But I have spent two days to try to do the same in Flutter, but failed.

First, hiding and showing keyboard are easy by the code SystemChannels.textInput.invokeMethod('TextInput.hide'); and SystemChannels.textInput.invokeMethod('TextInput.show');

When clicking the Emoji button, I want the textfield row position remains unchanged, then hide the keyboard and the keyboard original position should be replaced by the emoji panel. But the current behavior is the keyboard showing and hiding actions will change the UI layout during the animation. This does not happen to all the native apps that I use. Something like the following:

Showing Emoji Panel When Keyboard OnScreen

Original Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------
-- Keyboard
----------------------

After clicking Emoji Icon:

Phase 1 Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------
-- Emoji Panel
----------------------
-- Keyboard
----------------------

ListView and Emoji Panel positions changed because Both Emoji Panel and Keyboard are present at the same time. and NOTE THAT their positions will keep changing during keyboard moving down.

Phase 2 Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------
-- Emoji Panel
----------------------

ListView and Emoji Panel positions changed because Keyboard is finally gone.

Hiding Emoji Panel and Bring Keyboard Back

Original Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------
-- Emoji Panel
----------------------

Clicking Emoji Icon (or Keyboard Icon)

Phase 1 Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------

ListView and TextFieldRow positions changed because emoji panel is gone.

Phase 2 Screen:

-------Column-------
-- ListView
----------------------
-- TextFieldRow  
----------------------
-- Keyboard
----------------------

ListView and Emoji Panel's positions keeps changing while keyboard is moving up.

What I expect

I expect the ListView and TextFieldRow will not change their positions while the keyboard is moving up or down and emoji shows or hides. And this is what all native apps I used so far do. I tried many codes including playing around Stack, resizeToAvoidBottomPadding property etc.. But nothing works so far.

(Btw, I can get keyboard height with MediaQuery.of(context).viewInsets.bottom, so setting the same height to emoji panel as the keyboard is not a problem. and even if the emoji panel has a a little bit different height, the positions change should be around some pixels instead of the current behaviors)

Thank you very much for your help.

jun-hak commented 4 years ago

Is there any solution? It has been over a year, but it is still open...

thezjy commented 2 years ago

@sgon00 I have the same problem. Have you made any progress?

thezjy commented 2 years ago

I come up with a way to do this. The core idea is Scaffold(resizeToAvoidBottomInset: !showEmojiKeyboard) and below is a working example:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.orange,
      ),
      home: TextInputDemo(),
    );
  }
}

const kTextFieldPadding = 16.0;
const kIconSize = 28.0;

class TextInputDemo extends StatefulWidget {
  @override
  State<TextInputDemo> createState() => _TextInputDemoState();
}

class _TextInputDemoState extends State<TextInputDemo> {
  final focusNode = FocusNode();
  var showEmojiKeyboard = false;

  @override
  void initState() {
    focusNode.addListener(_handleFocusChange);

    super.initState();
  }

  @override
  void dispose() {
    focusNode.dispose();

    super.dispose();
  }

  void _handleFocusChange() {
    print("focus changed ${focusNode.hasFocus}");
    if (focusNode.hasFocus) {
      setState(() {
        showEmojiKeyboard = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
    final bottomPadding = MediaQuery.of(context).padding.bottom;
    print("keyboardHeight $keyboardHeight");
    print("bottomPadding $bottomPadding");

    return GestureDetector(
      onTap: () {
        FocusScope.of(context).requestFocus(FocusNode());
      },
      child: Scaffold(
          resizeToAvoidBottomInset: !showEmojiKeyboard,
          appBar: AppBar(title: const Text('Home')),
          backgroundColor: Colors.orange,
          body: SafeArea(
            child: Column(
              children: [
                Expanded(
                    child: Container(
                  color: Colors.indigo,
                  child: ListView(
                    children: List.generate(
                      30,
                      (i) => ListTile(
                        title: Text("#$i"),
                      ),
                    ).toList(),
                  ),
                )),
                Stack(
                  children: [
                    TextField(
                      // autofocus: true,
                      focusNode: focusNode,
                      showCursor: true,
                      minLines: 1,
                      maxLines: 6,
                      keyboardType: TextInputType.multiline,
                      textAlignVertical: TextAlignVertical.top,
                      style: const TextStyle(fontSize: 18.0),
                      decoration: const InputDecoration(
                        border: InputBorder.none,
                        contentPadding: EdgeInsets.symmetric(
                            vertical: kTextFieldPadding,
                            horizontal: kIconSize + 2 * kTextFieldPadding),
                        hintText: 'Enter your text here',
                        fillColor: Colors.white,
                        filled: true,
                      ),
                    ),
                    Positioned(
                      left: 0,
                      bottom: 0,
                      child: Container(
                        // decoration: BoxDecoration(border: Border.all()),
                        child: IconButton(
                          onPressed: () {
                            print("face pressed!");
                            setState(() {
                              showEmojiKeyboard = !showEmojiKeyboard;
                            });

                            focusNode.unfocus();
                          },
                          // splashRadius: 1.0,
                          padding: const EdgeInsets.fromLTRB(
                              kTextFieldPadding, 8, 8, kTextFieldPadding),
                          // constraints: const BoxConstraints(),
                          icon: const Icon(
                            Icons.face_outlined,
                            size: kIconSize,
                          ),
                        ),
                      ),
                    ),
                    Positioned(
                      right: 0,
                      bottom: 0,
                      child: Container(
                        // decoration: BoxDecoration(border: Border.all()),
                        child: IconButton(
                          onPressed: () {
                            print("pressed!");
                          },
                          // splashRadius: 1.0,
                          padding: const EdgeInsets.fromLTRB(
                              8, 8, kTextFieldPadding, kTextFieldPadding),
                          // constraints: const BoxConstraints(),
                          icon: const Icon(
                            Icons.note_add_outlined,
                            size: kIconSize,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
                if (showEmojiKeyboard)
                  SizedBox(
                    height: 336 - bottomPadding,
                    child: Container(
                      color: Colors.black,
                    ),
                  )
              ],
            ),
          )),
    );
  }
}
zhangwen0510 commented 1 year ago

+1

flutter-triage-bot[bot] commented 1 year ago

This issue is missing a priority label. Please set a priority label when adding the triaged-design label.

jwlim94 commented 4 months ago

Anyone has solved this issue?

GitLqr commented 4 months ago

Maybe you can try this package.

https://pub.dev/packages/chat_bottom_container