Flutter - 如何使TextField宽度适应其文本(“自适应内容”)

17

我正在尝试创建一个“搜索联系人列表”的功能,使用一些筹码来表示已选择的联系人,用户可以在文本框中输入以进行筛选并添加更多联系人:

Desired result

这可以通过使用包装Chip小部件列表的Wrap小部件,并以TextField小部件的Container结尾来完成。 我尝试过的: 如果我不设置TextField的宽度,则默认占据整行。让我们将其设置为红色以清晰明了。

Default width is whole line

我不想为此设置整行,因此将其设置为较小的值50。但是如果文本很长,则无效:

Fixing width hides long texts

问题:

是否可以使TextField最初很小,需要时自动扩展到整行?我尝试在BoxConstraint中使用"minWidth",但由于TextField默认为整行,所以无效。在这里使用Wrap和TextField是否正确?


你能添加你的示例代码吗? - diegoveloper
@diegoveloper 布局组合在问题中已经解释了。代码本身非常无聊:Wrap(children:[Chip(), Container(color: red, child: TextField(controller: _controller))]) - WSBT
@user1032613 你是否已经尝试将你的 Container() 包裹在一个 Expanded() 小部件中? - Rubens Melo
你尝试过 https://pub.dev/packages/fitted_text_field_container 吗? - SteveM
我遇到了类似的问题。我使用了flutter_typeahead插件,在选择后在文本字段之前添加芯片。但是textField占据了整个宽度。 - Sachin Tanpure
@SachinTanpure 你看过我下面的答案了吗? - WSBT
4个回答

64

使用 IntrinsicWidth 小部件将子项大小调整为子项的最大内在宽度。 在这种情况下,有效地缩小 TextField 的包裹范围:

IntrinsicWidth(
  child: TextField(),
)

然而,若 TextField 为空,则其会显得过于狭窄。为了解决这个问题,我们可以使用 ConstrainedBox 去强制设定最小宽度限制。例如:

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

最终结果:

这里输入图片描述


做这件事的方法真棒! - Cương Nguyễn
这在我的情况下几乎可行。但我希望文本字段不要比装饰标签小。 - GuiRitter
这在我的情况下几乎可以工作。但是我希望文本字段不要比装饰标签小。 - undefined

3

已经过去了一整年,自从我提出并忘记了这个问题……今天我又好好思考了一下,并采用了不同的方法。

关键问题是,我们无法让TextField占据恰当的空间。因此,这种方法使用简单的Text来显示文本内容,并使用非常细的TextField(4像素)来渲染闪烁的光标,如下图所示:

widget composition diagram

如果有帮助,可以随意使用此方法作为起点。

用法:

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;
                  });
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

1
我已经尝试了但失败了。我无法解决TextField溢出的问题。由于tp.layout(maxWidth: constraints.maxWidth/2);是硬编码的,所以这种解决方案无法处理动态更改的芯片。
修复此解决方案有两个选项:
  • TextController具有溢出标志

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

以下是我的尝试

enter image description here

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")),]
                );
            },),

          ],
        ),
      ),
    );
  }
}

这次尝试包括以下几个部分:

编辑3:添加图片,当您添加大量芯片并修复Column(Warp)时。 enter image description here enter image description here

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

还有其他人想尝试吗?我认为这个问题需要一个定制插件来解决。

编辑2:我找到了这个库,但我没有测试过它 https://github.com/danvick/flutter_chips_input


如果有更多的芯片并且开始换行,这个方法还能正常工作吗?例如,如果选择了10个联系人,可能需要2-3行才能显示所有芯片。 - WSBT
是的,我应该测试很多芯片。InputDecoration显然无法处理多行。我猜我们需要检测芯片何时移到下一行。 - user1462442

0
如果您也希望修饰符与文本字段具有相同的大小,请使用

isCollapsed

在我的情况下,该应用程序仅允许用户输入最多8个字符,无需显示计数器文本或错误小部件。以下是一个示例:
ConstrainedBox(
            constraints: const BoxConstraints(minWidth: 50),
            child: IntrinsicWidth(
              child: TextField(
                controller: _textController,
                keyboardType: TextInputType.number,
                maxLength: 8,
                cursorColor: MyTheme.grey2,
                decoration: const InputDecoration(
                  border: textFieldBorder,
                  focusedBorder: textFieldBorder,
                  counterText: '',
                  contentPadding:
                      EdgeInsets.symmetric(vertical: 4, horizontal: 6),
                  isCollapsed: true,
                ),
                style: Theme.of(context)
                    .textTheme
                    .labelSmall
                    ?.copyWith(color: MyTheme.grey2),
              ),
            ),
          ),

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接