简体   繁体   English

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

[英]Flutter - how to make TextField width fit its text ("wrap content")

I'm trying to do a "search contact list" feature with some chips representing selected contacts, and a user can type on text field to filter and add more contacts:我正在尝试使用一些代表所选联系人的芯片来实现“搜索联系人列表”功能,用户可以在文本字段中键入以过滤和添加更多联系人:

期望的结果

This is done with a Wrap widget, wrapping a list of Chip widgets, and ending the list with a Container of a TextField widget.这是通过Wrap小部件完成的,包装Chip小部件列表,并以TextField小部件的Container结束列表。

What I've tried:我试过的:

If I do not set the width of the TextField , it defaults to occupy a whole line.如果我不设置TextField的宽度,它默认占据一整行。 Let's make it red for clarity:为了清楚起见,我们将其设为红色:

默认宽度为整行

I do not want a whole line for it, so I set it to a small value, 50. But this doesn't work if the text is long:我不想要一整行,所以我将它设置为一个小值,50。但是如果文本很长,这将不起作用:

固定宽度隐藏长文本

Question:问题:

Is it possible to make the TextField starts small, and auto expands to a whole line when needed?是否可以使TextField开始时变小,并在需要时自动扩展到整行? I've tried "minWidth" in BoxConstraint but since the TextField defaults to a whole line, that doesn't work.我在BoxConstraint中尝试过“minWidth”,但由于TextField默认为整行,所以它不起作用。 Is using Wrap and TextField the correct way here?在这里使用 Wrap 和 TextField 的方法正确吗?

Use IntrinsicWidth widget to size a child to the child's maximum intrinsic width.使用IntrinsicWidth小部件将孩子的大小调整为孩子的最大内在宽度。 In this case, effectively shrink wrapping the TextField:在这种情况下,有效地收缩包装 TextField:

IntrinsicWidth(
  child: TextField(),
)

However, this will make the TextField too small when it's empty.但是,这会使 TextField 在为空时变得太小。 To fix that, we can use ConstrainedBox to force a minimum width constraint.为了解决这个问题,我们可以使用ConstrainedBox来强制最小宽度约束。 For example:例如:

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

End result:最终结果:

在此处输入图片说明

I tried but failed.我试过但失败了。 I have issues figuring out when the TextField overflows.我在确定 TextField 何时溢出时遇到问题。 This solution cannot work with dynamically changing chips since tp.layout(maxWidth: constraints.maxWidth/2);由于tp.layout(maxWidth: constraints.maxWidth/2);此解决方案不能与动态变化的芯片tp.layout(maxWidth: constraints.maxWidth/2); is hard coded.是硬编码的。

There are two options to fix this solution:有两个选项可以修复此解决方案:

  • TextController has a overflow flag TextController 有一个溢出标志

  • In tp.layout(maxWidth: constraints.maxWidth/2) , LayoutBuilder can figure out the width left over from chips.tp.layout(maxWidth: constraints.maxWidth/2) ,LayoutBuilder 可以计算出芯片剩余的宽度。

Here is my attempt这是我的尝试

在此处输入图片说明

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

          ],
        ),
      ),
    );
  }
}

This attempt comprised of a few parts:这个尝试包括几个部分:

Edit3: Added picture when you add tons of chips and fix the Column(Warp) Edit3:添加大量芯片并修复Column(Warp)时添加的图片在此处输入图片说明 在此处输入图片说明

Like I said, the largest problem is that I cannot figure out when the text box overflows.就像我说的,最大的问题是我无法弄清楚文本框何时溢出。

Anyone else wants try?还有人想试试吗? I think this question needs a custom plugin to solve我认为这个问题需要一个自定义插件来解决

Edit2: I found the library but I did not test it https://github.com/danvick/flutter_chips_input Edit2:我找到了该库,但我没有对其进行测试https://github.com/danvick/flutter_chips_input

Over a whole year has passed since I asked and forgot about this question... I gave it a little bit more thoughts today, and took a different approach this time.自从问了这个问题又忘记了整整一年过去了……今天想多了,这次换了个思路。

The key problem is that, we are not able to let TextField occupy just the right amount of space.关键问题是,我们不能让TextField占据恰到好处的空间。 So this approach uses a simple Text to display the text content, and use a very thin TextField (at 4 px) just to make it render the blinking cursor, shown in red:所以这种方法使用一个简单的Text来显示文本内容,并使用一个非常细的TextField (4 px)来渲染闪烁的光标,以红色显示:

小部件组成图

Feel free to use this approach as a starting point if it helps anyone.如果它对任何人有帮助,请随意使用这种方法作为起点。

Usage:用法:

TextChip()

Demo:演示:

Code: (draft, works as demoed above; should only be used as a starting point)代码:(草稿,如上面演示的那样工作;应该只用作起点)

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

If you also want to the decoration has the same size with textfield, use如果您还希望装饰与文本字段具有相同的大小,请使用

isCollapsed已折叠

In my case, the app just allows the user input maximum 8 characters and do not need to show counter text or error widgets.在我的例子中,该应用程序只允许用户输入最多 8 个字符,不需要显示计数器文本或错误小部件。 Here is an example:这是一个例子:

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),
              ),
            ),
          ),

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM