在flutter的TextField小部件中键入文字时,如何设置文本格式?

时间:2019-05-05 07:44:11

标签: android flutter

我要在键入TextField widget时设置文本格式。例如,当我在字段中使用下划线_时,它们之间的字符应变为斜体。 我想要的是一种 markdown式文本格式,可以实时在 Flutter 的同一小部件​​中进行。

RichText可以提供帮助,但是它们不可编辑。为此,我需要一个RichTextField,但是Flutter中不存在类似的东西。

我的结果应该类似于WhatsApp的消息字段,该字段应用粗体,斜体,删除线等。

2 个答案:

答案 0 :(得分:4)

我发现解决方案很简单。

我只需要扩展/实现TextEditingController并创建TextSpan buildTextSpan({TextStyle style , bool withComposing})的实现。

buildTextSpan方法负责创建TextField中显示的文本范围。

例如,选中this code of my package.

答案 1 :(得分:3)

请参阅有关在现有“ TextField”上实现答案的示例代码。

Screen Recording

import 'dart:ui';
import 'package:flutter/material.dart';

final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: const Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class TextFieldColorizer extends TextEditingController {
  final Map<String, TextStyle> map;
  final Pattern pattern;

  TextFieldColorizer(this.map)
      : pattern = RegExp(
            map.keys.map((key) {
              return key;
            }).join('|'),
            multiLine: true);

  @override
  set text(String newText) {
    value = value.copyWith(
      text: newText,
      selection: TextSelection.collapsed(offset: newText.length),
      composing: TextRange.empty,
    );
  }

  @override
  TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
    final List<InlineSpan> children = [];
    String patternMatched;
    String formatText;
    TextStyle myStyle;
    text.splitMapJoin(
      pattern,
      onMatch: (Match match) {
        myStyle = map[match[0]] ??
            map[map.keys.firstWhere(
              (e) {
                bool ret = false;
                RegExp(e).allMatches(text)
                  ..forEach((element) {
                    if (element.group(0) == match[0]) {
                      patternMatched = e;
                      ret = true;
                      return true;
                    }
                  });
                return ret;
              },
            )];

        if (patternMatched == r"_(.*?)\_") {
          formatText = match[0].replaceAll("_", " ");
        } else if (patternMatched == r'\*(.*?)\*') {
          formatText = match[0].replaceAll("*", " ");
        } else if (patternMatched == "~(.*?)~") {
          formatText = match[0].replaceAll("~", " ");
        } else if (patternMatched == r'```(.*?)```') {
          formatText = match[0].replaceAll("```", "   ");
        } else {
          formatText = match[0];
        }
        children.add(TextSpan(
          text: formatText,
          style: style.merge(myStyle),
        ));
        return "";
      },
      onNonMatch: (String text) {
        children.add(TextSpan(text: text, style: style));
        return "";
      },
    );

    return TextSpan(style: style, children: children);
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget();
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  final TextEditingController _controller = TextFieldColorizer(
    {
      r"@.\w+": TextStyle(color: Colors.blue, shadows: kElevationToShadow[2]),
      'red': const TextStyle(
          color: Colors.red, decoration: TextDecoration.underline),
      'green': TextStyle(color: Colors.green, shadows: kElevationToShadow[2]),
      'purple': TextStyle(color: Colors.purple, shadows: kElevationToShadow[2]),
      r'_(.*?)\_': TextStyle(
          fontStyle: FontStyle.italic, shadows: kElevationToShadow[2]),
      '~(.*?)~': TextStyle(
          decoration: TextDecoration.lineThrough,
          shadows: kElevationToShadow[2]),
      r'\*(.*?)\*': TextStyle(
          fontWeight: FontWeight.bold, shadows: kElevationToShadow[2]),
      r'```(.*?)```': TextStyle(
          color: Colors.yellow,
          fontFeatures: [const FontFeature.tabularFigures()],
          shadows: kElevationToShadow[2]),
    },
  );

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: TextField(
        maxLines: 5,
        onChanged: (text) {
          final val = TextSelection.collapsed(offset: _controller.text.length);
          _controller.selection = val;
        },
        style: const TextStyle(fontSize: 32),
        controller: _controller,
      ),
    );
  }
}