点击 TextField 的 prefixIcon 会导致 TextField 被聚焦

Posted

技术标签:

【中文标题】点击 TextField 的 prefixIcon 会导致 TextField 被聚焦【英文标题】:tapping prefixIcon of TextField causes TextField to be focussed 【发布时间】:2020-02-13 18:06:29 【问题描述】:

我有一个 TextField,它有一个属性 prefixIcon,它接受一个小部件。我传递了一个 GestureDetector 以便我可以对它做一些 onTap 事件。但是我面临的问题是,一旦我点击它,虽然它会调用 onTap 事件,但同时它也关注了进一步启动键盘的 TextField。

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(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body:MyWidget,
    );
  


class MyWidget extends StatefulWidget
  @override
  State<StatefulWidget> createState() 
    return MyWidgetState();
  


class MyWidgetState extends State<MyWidget>

  @override
  Widget build(BuildContext context) 
    return Container(
      height: 200,
      width: 200,
      padding: EdgeInsets.all(20),
      child: TextField(
        decoration: InputDecoration(
          prefixIcon: GestureDetector(
            child: Container(color: Colors.greenAccent, width: 25, height: 25,),
            onTap: () => print("hmm"),
          ),
        ),
      ),
    );
  

所以我试图找到一种方法来点击 prefixIcon 小部件(这里 GestureDetector 不关注 TextField)。 如何实现该功能?

【问题讨论】:

【参考方案1】:

您可以将 TextField 包装在一个 Row 中,然后在该图标之前添加一个可点击的图标。那么默认行为是什么就无关紧要了。

Container(
  height: 200,
  width: 200,
  padding: EdgeInsets.all(20),
  child: Row(
    children: <Widget>[
      GestureDetector(
        child: Container(color: Colors.greenAccent, width: 25, height: 25,),
        onTap: () => print("hmm"),
      ),
      Expanded(child: TextField()),
    ],
  ),
)

我想我有一个更好的解决方案给你,因为它不需要对 FocusNode 进行任何操作。只需将两者传递到堆栈中,使用 CompositedTransformTarget/Followers 并用您想要的项目覆盖装饰器。我已经对其进行了测试,并且可以正常工作。它还使您想要放置在前缀输入上的图标跟随文本字段的大小(如果这是您想要的)。保持同步。

class TestWidget extends StatelessWidget 

  final LayerLink link = LayerLink();

  @override
  Widget build(BuildContext context) 
    return Stack(
      children: <Widget>[

        TextField(
          maxLines: null,
          decoration: InputDecoration(
            prefixIcon: CompositedTransformTarget(
              child: Container(color: Colors.transparent, width: 25, height: 25,),
              link: link,
              ),
          )
        ),



        CompositedTransformFollower(
          link: link,
          child: GestureDetector(
            child: Container(color: Colors.greenAccent, width: 25, height: 25,),
            onTap: () => Vibrate.feedback(FeedbackType.heavy),
          ),
        )
      ],
    );
  

【讨论】:

实际上,这与我尝试过的解决方法类似,但总的来说,这不是一个更好的选择,因为如果您应用不同的样式,TextField 上的 InputDecoration (如边框等)您必须模仿所有前缀的样式。 在这里,我添加了另一个不需要太多努力的解决方案,基本上可以用任何你想要的东西替换前缀图标。 我不知道这些小部件!似乎对某些特定情况很有用。我将对其进行测试,看看它是如何工作的,但乍一看,我发现您需要在两个地方创建两次小部件,以保持其尺寸相等...... 这是我第一次使用这些小部件。我认为您不需要两次创建小部件。您可能可以将此代码制作成一个类,该类接受一个小部件/前缀图标参数并将该小部件的给定大小传递给实际前缀图标位置中更轻量级的小部件。像 SizedBox.fromSize 这样的东西可能就足够了。然后创建需要遵循的 Rect ,无需太多其他内容。我只是认为使用焦点节点可能有点粗略,以防颤振团队在未来改变行为。不太可能,但有风险。 是的,我会尝试简化这种方法以避免重复代码,并查看是否可以不使用 Overlay 将 TextField 与 Stack 包装起来,这样我们就不必改变太多的结构小部件,但我不知道它会变得多么复杂。【参考方案2】:

我遇到了类似的问题,在这里我解释了问题和可能的解决方案:Flutter DropdownButton inside TextFormField as prefix

在你的情况下,DropdownButtonContainer,所以你可以这样做:

Wrap(
  children: <Widget>[
    Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16.0),
        boxShadow: [BoxShadow()],
      ),
      child: Row(
        children: <Widget>[
          GestureDetector(
            child: Container(color: Colors.greenAccent, width: 25, height: 25,),
            onTap: () => print("hmm"),
          ),
          Flexible(
            child: TextField(),
          ),
        ],
      ),
    ),
  ],
)

编辑:不幸的是,TextFieldprefix 旨在用于 TextField 的焦点。我可以想出一个解决方法来检测TextField 的焦点并在prefix 被点击时取消焦点,这是一个示例:

final _controller = TextEditingController(text: "Test");
final _focusNode = FocusNode();
var _prefixTapped = false;

@override
void initState() 
  super.initState();
  _focusNode.addListener(() 
    if (_focusNode.hasFocus & _prefixTapped) _focusNode.unfocus();
    _prefixTapped = false;
  );


@override
Widget build(BuildContext context) 
  return Scaffold(
    appBar: AppBar(title: Text("TextFieldWithGesturePrefix")),
    body: TextField(
      controller: _controller,
      focusNode: _focusNode,
      decoration: InputDecoration(
        prefixIcon: GestureDetector(
          child: Container(
            color: Colors.greenAccent,
            child: Text("Any widget"),
          ),
          onTap: () 
            _prefixTapped = true;
            _focusNode.unfocus();
            print("prefix tapped");
          ,
        ),
      ),
    ),
  );

【讨论】:

这也与我尝试过的解决方法非常相似,但一般来说它不会是一个更好的选择,因为如果你应用不同的样式,TextField 上的 InputDecoration 像边框等你必须模仿所有前缀的样式。 谢谢,成功了。但我建议进行一些编辑,实际上是否需要 ListView?我认为这不是必需的。你怎么看?此外,我们可以使用 prefixIcon 来代替前缀,即使 TextField 没有聚焦也可见。 不错的解决方法,Pablo。我自己为此创建了一个新的解决方案,它根本不需要担心焦点节点,同时仍将图标保持在应有的位置。 伟大的阿德里安,我会看到的。 Nimish 我编辑了您在代码中提到的更改并删除了注释以使其保持简单并避免混淆,如果有人想使用它。

以上是关于点击 TextField 的 prefixIcon 会导致 TextField 被聚焦的主要内容,如果未能解决你的问题,请参考以下文章

点击另一个 TextField 时,SwiftUI TextField 不会提交更改

用户点击字段时如何将 textFieldStyle 切换为 TextField?

当您点击另一个 TextField 时,如何获取要更新的 TextField 的绑定值?

增加 TextField 的可点击区域

点击时TextField消失

如何检测何时点击 TextField? (MacOS 上的 SwiftUI)