【问题标题】:Flutter - how to make TextField width fit its text ("wrap content")Flutter - 如何使 TextField 宽度适合其文本(“包装内容”)
【发布时间】:2019-04-03 22:34:38
【问题描述】:

我正在尝试使用一些代表选定联系人的芯片来执行“搜索联系人列表”功能,用户可以在文本字段中键入以过滤和添加更多联系人:

这是通过Wrap 小部件完成的,包装Chip 小部件列表,并以ContainerTextField 小部件结束列表。

我的尝试:

如果我不设置TextField的宽度,默认占一整行。为了清楚起见,我们将其设为红色:

我不想要一整行,所以我将它设置为一个小的值,50。但是如果文本很长,这不起作用:

问题:

是否可以让TextField 从小开始,并在需要时自动扩展到整行?我在BoxConstraint 中尝试过“minWidth”,但由于TextField 默认为一整行,这不起作用。在这里使用 Wrap 和 TextField 是否正确?

【问题讨论】:

  • 你能添加你的示例代码吗?
  • @diegoveloper 问题中解释了布局组成。代码本身很无趣:Wrap(children:[Chip(), Container(color: red, child: TextField(controller: _controller))]).
  • @user1032613 您是否已经尝试将您的Container() 包装在Expanded() 小部件中?
  • 我面临着类似的问题。我使用了flutter_typeahead pub,在选择后在文本字段之前添加筹码。但是 textField 占据整个宽度。

标签: flutter


【解决方案1】:

使用IntrinsicWidth 小部件将子级调整为子级的最大固有宽度。在这种情况下,有效地收缩包装 TextField:

IntrinsicWidth(
  child: TextField(),
)

但是,这会使 TextField 为空时太小。为了解决这个问题,我们可以使用ConstrainedBox 来强制限制最小宽度。例如:

ConstrainedBox(
  constraints: BoxConstraints(minWidth: 48),
  child: IntrinsicWidth(
    child: TextField(),
  ),
)

最终结果:

【讨论】:

    【解决方案2】:

    从我问这个问题到忘记这个问题已经过去了整整一年……我今天稍微思考了一下,这次采取了不同的方法。

    关键问题是,我们不能让TextField 占据恰到好处的空间。所以这种方法使用一个简单的Text 来显示文本内容,并使用一个很细的TextField(4 px)只是为了让它呈现闪烁的光标,显示为红色:

    如果它对任何人都有帮助,请随意使用此方法作为起点。

    用法:

    TextChip()

    演示:

    代码:(草稿,如上面演示的那样工作;应仅用作起点)

    class TextChip extends StatefulWidget {
      @override
      _TextChipState createState() => _TextChipState();
    }
    
    class _TextChipState extends State<TextChip> {
      final _focus = FocusNode();
      final _controller = TextEditingController();
      String _text = "";
    
      @override
      Widget build(BuildContext context) {
        return InputChip(
          onPressed: () => FocusScope.of(context).requestFocus(_focus),
          label: Stack(
            alignment: Alignment.centerRight,
            overflow: Overflow.visible,
            children: [
              Text(_text),
              Positioned(
                right: 0,
                child: SizedBox(
                  width: 4, // we only want to show the blinking caret
                  child: TextField(
                    scrollPadding: EdgeInsets.all(0),
                    focusNode: _focus,
                    controller: _controller,
                    style: TextStyle(color: Colors.transparent),
                    decoration: InputDecoration(
                      border: InputBorder.none,
                    ),
                    onChanged: (_) {
                      setState(() {
                        _text = _controller.text;
                      });
                    },
                  ),
                ),
              ),
            ],
          ),
        );
      }
    }
    

    【讨论】:

      【解决方案3】:

      我尝试过但失败了。我在确定 TextField 何时溢出时遇到问题。由于tp.layout(maxWidth: constraints.maxWidth/2); 是硬编码的,因此该解决方案无法处理动态变化的芯片。

      有两种方法可以解决此问题:

      • TextController 有溢出标志

      • tp.layout(maxWidth: constraints.maxWidth/2) 中,LayoutBuilder 可以计算出芯片剩余的宽度。

      这是我的尝试

      import 'package:flutter/material.dart';
      
      void main() => runApp(MyApp());
      
      class MyApp extends StatelessWidget {
        // This widget is the root of your application.
        @override
        Widget build(BuildContext context) {
          return MaterialApp(
            title: 'Flutter Demo',
            theme: ThemeData(
              primarySwatch: Colors.blue,
            ),
            home: MyHomePage(title: 'Flutter Demo Home Page'),
          );
        }
      }
      
      class MyHomePage extends StatefulWidget {
        MyHomePage({Key key, this.title}) : super(key: key);
      
        final String title;
      
        @override
        _MyHomePageState createState() => _MyHomePageState();
      }
      
      class _MyHomePageState extends State<MyHomePage> {
        TextEditingController _controller;
        String _text = "";
        bool _textOverflow = false;
        @override
        void initState() {
          // TODO: implement initState
          super.initState();
          _textOverflow = false;
          _controller = TextEditingController();
          _controller.addListener((){
            setState(() {
              _text = _controller.text;
            });
          });
        }
        @override
        void dispose() {
          // TODO: implement dispose
          super.dispose();
          _controller.dispose();
        }
      
        Widget chooseChipInput(BuildContext context, bool overflow, List<Widget> chips) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              overflow ? Wrap(children: chips, alignment: WrapAlignment.start,): Container(),
              Container(
                color: Colors.red,
                child: TextField( 
                  controller: _controller,
                  maxLines: overflow ? null : 1,
                  decoration:  InputDecoration(icon: overflow ? Opacity(opacity: 0,) : Wrap(children: chips,)),
                ),
              )
      
            ]
          );
        }
      
        @override
        Widget build(BuildContext context) {
          const _counter = 0;
          return Scaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text(
                    'You have pushed the button this many times:',
                  ),
                  Text(
                    '$_counter',
                    style: Theme.of(context).textTheme.display1,
                  ),
      
                  LayoutBuilder(builder: (context, constraints){
                      var textStyle = DefaultTextStyle.of(context).style;
                      var span = TextSpan(
                        text: _text,
                        style: textStyle,
                      );
                      // Use a textpainter to determine if it will exceed max lines
                      var tp = TextPainter(
                        maxLines: 1,
                        textAlign: TextAlign.left,
                        textDirection: TextDirection.ltr,
                        text: span,
                      );
                      // trigger it to layout
                      tp.layout(maxWidth: constraints.maxWidth/2);
      
                      // whether the text overflowed or not
                      print("****** ${tp.didExceedMaxLines} ${constraints.maxWidth}");
                      return chooseChipInput(
                        context, 
                        tp.didExceedMaxLines, 
                        <Widget>[Chip(label: Text("chip1"),), 
                            Chip(label: Text("chip2")),]
                      );
                  },),
      
                ],
              ),
            ),
          );
        }
      }
      

      这次尝试由几个部分组成:

      Edit3:添加大量筹码并修复 Column(Warp) 时添加图片

      就像我说的,最大的问题是我无法确定文本框何时溢出。

      还有人想试试吗?我认为这个问题需要一个自定义插件来解决

      Edit2:我找到了库,但我没有测试它https://github.com/danvick/flutter_chips_input

      【讨论】:

      • +1 时间和精力。当有更多筹码并开始包装到下一行时,这会起作用吗?例如,如果选择了 10 个联系人,则显示筹码可能需要 2-3 行。
      • 是的,我应该测试很多芯片。 InputDecoration 显然不能处理多行。我想我们需要检测芯片何时移动到下一行
      猜你喜欢
      • 2020-09-06
      • 1970-01-01
      • 2019-11-24
      • 2019-06-18
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      • 2023-03-08
      相关资源
      最近更新 更多