Selection Automatic jump

Problem Description:

Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              _controller.text = "newText";
            },
            child: Text("click me"),
          ),
          TextField(
            controller: _controller,
            autofocus: true,),,)Copy the code

When you click the button to modify the contents of the TextField via TextEditingController the TextField cursor is automatically moved to the front.

Problem solving:

Column(
        children: <Widget>[
          RaisedButton(
            onPressed: () {
              _controller.text = "newText";
              
              // You need to manually modify selection each time you modify the content
              _controller.selection = TextSelection.fromPosition(
                  TextPosition(offset: _controller.text.length));
            },
            child: Text("click me"),
          ),
          TextField(
            controller: _controller,
            autofocus: true,),,)Copy the code

The keyboard overlays the input field

Problem Description:

The author’s project adopts the mixed development mode (native +Flutter), and a FlutterActivity is configured on the Android terminal to carry the Flutter page. However, during the development, it was found that the input box would always be overwritten when the interface keyboard with TextField pops up. So I started on a long trip to the pits:

  • First, I went to find the relevant issue, but failed
  • Google later found out that the open source gods had written a helper class called EnsureVisible. Dart, and they used their CV skills to no avail. But I found that the author had written this line:

DEPRECATED. (Now integrated Into Flutter!!!) . Ensure Visible for Flutter. Makes sure TextField or other widgets are scrolled into view when they receive input focus. Just pass the focusNode provided to your TextField inside the builder.

No more fuss, Flutter already supports this property….

  • As an Android developer, I naturally thought of the adjustResize feature of the Activity, so I tried it and found it worked well, so the problem was solved successfully.

The default Activity configuration created when FlutterApplication is created by default is as follows:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">
    <! -- This keeps the window background of the activity showing until Flutter renders its first frame. It can be removed if there is no splash screen (such as the default splash screen defined in @style/LaunchTheme). -->
    <meta-data
        android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
        android:value="true" />
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
</activity>
Copy the code

It can be seen that windowSoftInputMode is configured, so using the official configuration will not encounter my problems.

I then took a closer look at the aforementioned EnsureVisible class to see what it solves and how:

  • The first is what was solved: AdjustResize is known to reduce the height of the layout above the keyboard, but when the layout change can force part or all of the input box out of the screen, it calls Scrollable’s ensureVisible method to scroll the input box into the visible area
  • EnsureVisible uses the WidgetsBinding class to retrieve the new page state when the page layout changes via the WidgetsBindingObserver callback.
class _EnsureVisibleState extends State<EnsureVisible> with WidgetsBindingObserver {
  final FocusNode _focusNode = new FocusNode();
  bool _alreadyScrolling = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }
  
  @override
  void didChangeMetrics() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      // Callback when layout changes
      if(_focusNode.hasFocus && ! _alreadyScrolling) {final alignment = resolveAlignment();
        if(alignment ! =null) {
          _alreadyScrolling = true;
          Scrollable.ensureVisible(context,
            alignment: alignment,
            duration: widget.duration,
            curve: widget.curve,
          ).whenComplete(() => _alreadyScrolling = false); }}}); }@override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose(); }}Copy the code

In the didChangeMetrics method, get the resolveAlignment parameter to scroll:

double resolveAlignment() {
    if (widget.alignment == null) {
      final RenderObject object = context.findRenderObject();
      final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
      if (viewport == null) {
        // If we have no viewport we don't attempt to scroll.
        return null;
      }
      ScrollableState scrollableState = Scrollable.of(context);
      if (scrollableState == null) {
        // If we can't find a ancestor Scrollable we don't attempt to scroll.
        return null;
      }
      ScrollPosition position = scrollableState.position;
      if (position.pixels > viewport.getOffsetToReveal(object, 0.0).offset) {
        // Move down to the top of the viewport
        return 0.0;
      }
      else if (position.pixels < viewport.getOffsetToReveal(object, 1.0).offset) {
        // Move up to the bottom of the viewport
        return 1.0;
      }
      else {
        // No scrolling is necessary to reveal the child
        return null; }}else {
      // Use supplied Alignment parameter.
      return 0.5 + (0.5* widget.alignment.y); }}Copy the code

Of course, the authors also introduce FocusNode, which manually calls the didChangeMetrics method when the keyboard gets focus, but I don’t see the need to do that again.

Extension: Based on the above ideas, I have written a Widget that listens for hidden events on the keyboard:

import 'package:flutter/material.dart';

typedef KeyboardShowCallback = void Function(bool isKeyboardShowing);

class KeyboardDetector extends StatefulWidget {

  KeyboardShowCallback keyboardShowCallback;

  Widget content;

  KeyboardDetector({this.keyboardShowCallback, @required this.content});

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

class _KeyboardDetectorState extends State<KeyboardDetector>
    with WidgetsBindingObserver {
  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      print(MediaQuery.of(context).viewInsets.bottom); setState(() { widget.keyboardShowCallback ? .call(MediaQuery.of(context).viewInsets.bottom >0);
      });
    });
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    returnwidget.content; }}Copy the code

Use not to repeat ~