Flutter-如何使TextField宽度适合其文本(“环绕内容”)

时间:2019-04-03 22:34:38

标签: flutter

我正在尝试使用某些表示所选联系人的筹码进行“搜索联系人列表”功能,并且用户可以在文本字段中键入内容以过滤并添加更多联系人:

Desired result

这是通过Wrap小部件完成的,将Chip小部件的列表包装起来,并以Container小部件的TextField结束列表。

我尝试过的事情:

如果我未设置TextField的宽度,则默认情况下它将占据整行。为了清楚起见,我们将其设为红色:

Default width is whole line

我不需要整行,因此将其设置为一个较小的值50。但是如果文本较长,则此操作将无效:

Fixing width hides long texts

问题:

是否可以使TextField的开头变小,并在需要时自动扩展为整行?我已经在BoxConstraint中尝试过“ minWidth”,但是由于TextField默认为整行,因此不起作用。在这里使用Wrap和TextField是正确的方法吗?

3 个答案:

答案 0 :(得分:4)

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

IntrinsicWidth(
  child: TextField(),
)

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

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

最终结果:

enter image description here

答案 1 :(得分: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")),]
                );
            },),

          ],
        ),
      ),
    );
  }
}

此尝试包括以下几个部分:

Edit3:添加大量芯片并修复Column(Warp)时添加了图片 enter image description here enter image description here

就像我说的那样,最大的问题是我无法弄清楚文本框何时溢出。

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

Edit2:我找到了该库,但没有对其进行测试https://github.com/danvick/flutter_chips_input

答案 2 :(得分:1)

自从我问了这个问题并忘了这个问题以来已经过去了整整一年的时间...今天我给了我更多的想法,这次采用了另一种方法。

关键问题在于,我们无法让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;
                  });
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}