最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

flutter - How to keep focus on TextField when adding a button on text edit? - Stack Overflow

programmeradmin2浏览0评论

My app has a TextField. When editing is done, an _isEdited flag is set which causes a (new) "submit" button to be added to the widget tree. However, this causes the TextField to lose the input focus and cursor, at least for MacOS and web. The user must touch the text field again to continue typing. Yuk!

How can I prevent this and allow the text field to keep the input focus?

The following has the problem:

    Widget child = ... textfield ...;
    
    if (_isEdited)
      child = Column(children: [child, button]);

but this does not:

    Widget child = ... textfield ...;
    
    if (_isEdited)
      child = Column(children: [child, button]);
    else
      child = Column(children: [child]);

It's problematic because I don't want to keep the same widget tree (which is much more complex in practice). I thought providing keys for widgets was supposed to solve this,

 TextField(key: const Key("Key1"), ... )

but it seems to make no difference.

I also don't want to simply make the widget invisible since invisible widget take up layout space.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isEdited = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Text Input Form'),
      ),
      body: body(context),
    );
  }

  Widget submitButton(BuildContext context) {
    Widget child = ElevatedButton(
      onPressed: () {},
      child: Text('Save'),
    );
    child = Padding(padding: EdgeInsets.all(20), child: child);
    return child;
  }

  void onChanged(String) {
    setState(() {
      _isEdited = true;
    });
  }

  Widget body(BuildContext context) {
    Widget child = TextField(
        key: const Key("Key1"),
        focusNode: _focusNode,
        controller: _controller,
        onChanged: onChanged);

    Widget button = submitButton(context);

    if (_isEdited)
      child = Column(children: [child, button]);
    // else
    //   child = Column(children: [child]);

    return child;
  }
}

Update: (see @Nguyen family's answer, below). The following fails,

    child =
      ValueListenableBuilder<bool>(
        valueListenable: isEditing,
        builder: (ctx, val, child_) {
          if (!val) return child_!;
          return Column(children: [
            child_!, button]);
        },
        child: child,
      );

but:

child =
      ValueListenableBuilder<bool>(
        valueListenable: isEditing,
        builder: (ctx, val, child_) {
          if (!val) return child_!;
          return Column(children: [
            child_!, button]);
        },
        child: child,
      );

succeeds, presumably also because the text field is in the same position in the widget tree as the successful example above.

My app has a TextField. When editing is done, an _isEdited flag is set which causes a (new) "submit" button to be added to the widget tree. However, this causes the TextField to lose the input focus and cursor, at least for MacOS and web. The user must touch the text field again to continue typing. Yuk!

How can I prevent this and allow the text field to keep the input focus?

The following has the problem:

    Widget child = ... textfield ...;
    
    if (_isEdited)
      child = Column(children: [child, button]);

but this does not:

    Widget child = ... textfield ...;
    
    if (_isEdited)
      child = Column(children: [child, button]);
    else
      child = Column(children: [child]);

It's problematic because I don't want to keep the same widget tree (which is much more complex in practice). I thought providing keys for widgets was supposed to solve this,

 TextField(key: const Key("Key1"), ... )

but it seems to make no difference.

I also don't want to simply make the widget invisible since invisible widget take up layout space.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isEdited = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Text Input Form'),
      ),
      body: body(context),
    );
  }

  Widget submitButton(BuildContext context) {
    Widget child = ElevatedButton(
      onPressed: () {},
      child: Text('Save'),
    );
    child = Padding(padding: EdgeInsets.all(20), child: child);
    return child;
  }

  void onChanged(String) {
    setState(() {
      _isEdited = true;
    });
  }

  Widget body(BuildContext context) {
    Widget child = TextField(
        key: const Key("Key1"),
        focusNode: _focusNode,
        controller: _controller,
        onChanged: onChanged);

    Widget button = submitButton(context);

    if (_isEdited)
      child = Column(children: [child, button]);
    // else
    //   child = Column(children: [child]);

    return child;
  }
}

Update: (see @Nguyen family's answer, below). The following fails,

    child =
      ValueListenableBuilder<bool>(
        valueListenable: isEditing,
        builder: (ctx, val, child_) {
          if (!val) return child_!;
          return Column(children: [
            child_!, button]);
        },
        child: child,
      );

but:

child =
      ValueListenableBuilder<bool>(
        valueListenable: isEditing,
        builder: (ctx, val, child_) {
          if (!val) return child_!;
          return Column(children: [
            child_!, button]);
        },
        child: child,
      );

succeeds, presumably also because the text field is in the same position in the widget tree as the successful example above.

Share Improve this question edited Mar 27 at 23:33 user48956 asked Mar 27 at 4:00 user48956user48956 15.8k24 gold badges100 silver badges171 bronze badges 2
  • i didnt test it but TextField.autofocus could help – pskink Commented Mar 27 at 6:07
  • @pskink Doesn't to help. Thanks though. – user48956 Commented Mar 27 at 23:00
Add a comment  | 

2 Answers 2

Reset to default 1

Maybe you could try this solution. Instead of assign like this:

    if (isEditing)
      child = Column(children: [child, button]);
    else
      child = Column(children: [child]);

You could make your _isEdited as a ValueNotifier:

final ValueNotifier<bool> isEditing = ValueNotifier<bool>(false);

@override
void dispose() {
    isEditing.dispose();
}

void onChanged(String) {
    isEditing.value = true;
}

Widget body(BuildContext context) {
    return Column(
        children: [
            TextField(),
            ValueListenableBuilder<bool>(
                valueListenable: isEditing,
                builder: (ctx, val, child) {
                    if (!val) return SizedBox();
                    return SubmitButton();
                },
            ),
        ],
    );
}

By this way, your body won't be rebuild again. I think your TextField still can keep the focus status.

Try this. If you have any question, u can comment under my answer.

Using


  # Nope:   Key("") # ValueKey # ObjectKey #UniqueKey 
  final textKey = GlobalKey();  
   ...
      child = TextField(  
         key: textKey=

resolves this.

发布评论

评论列表(0)

  1. 暂无评论