【问题标题】:Handling grapheme clusters in Dart在 Dart 中处理字素簇
【发布时间】:2019-06-26 05:56:51
【问题描述】:

据我所知,Dart 不支持字素集群,尽管有人说支持它:

在实现之前,我有哪些用于迭代字素集群的选项?例如,如果我有这样的字符串:

String family = '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}'; // ????‍????‍????
String myString = 'Let me introduce my $family to you.';

并且在五码点家族表情符号之后有一个光标:

如何将光标向左移动一个用户感知的字符?

(在这种特殊情况下,我知道字素簇的大小,所以我可以做到,但我真正要问的是找到任意长的字素簇的长度。)

更新

我从this article 看到,Swift 使用系统的ICU 库。在 Flutter 中可能会出现类似的情况。

补充代码

对于那些想玩弄我上面的例子的人,这里有一个演示项目。按钮将光标向右或向左移动。目前需要按 8 次按钮才能将光标移过家庭表情符号。

main.dart

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Grapheme cluster testing')),
        body: BodyWidget(),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  _BodyWidgetState createState() => _BodyWidgetState();
}

class _BodyWidgetState extends State<BodyWidget> {

  TextEditingController controller = TextEditingController(
      text: 'Let me introduce my \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467} to you.'
  );

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          controller: controller,
        ),
        Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('<<'),
                onPressed: () {
                  _moveCursorLeft();
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('>>'),
                onPressed: () {
                  _moveCursorRight();
                },
              ),
            ),
          ],
        )
      ],
    );
  }

  void _moveCursorLeft() {
    int currentCursorPosition = controller.selection.start;
    if (currentCursorPosition == 0)
      return;
    int newPosition = currentCursorPosition - 1;
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }

  void _moveCursorRight() {
    int currentCursorPosition = controller.selection.end;
    if (currentCursorPosition == controller.text.length)
      return;
    int newPosition = currentCursorPosition + 1;
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }
}

【问题讨论】:

  • 你能给出一些移动光标的代码示例吗?是不是类似于moveCursorTo(Int index)?
  • @TruongSinh,您可以通过在TextSelection 中设置int 偏移量来移动光标。我在问题中添加了一个演示项目。

标签: unicode dart flutter icu grapheme-cluster


【解决方案1】:

更新:使用https://pub.dartlang.org/packages/icu

示例代码:

import 'package:flutter/material.dart';


import 'dart:async';
import 'package:icu/icu.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Grapheme cluster testing')),
        body: BodyWidget(),
      ),
    );
  }
}

class BodyWidget extends StatefulWidget {
  @override
  _BodyWidgetState createState() => _BodyWidgetState();
}

class _BodyWidgetState extends State<BodyWidget> {
  final ICUString icuText = ICUString('Let me introduce my \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467} to you.\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}');
  TextEditingController controller;
  _BodyWidgetState() {
    controller = TextEditingController(
      text: icuText.toString()
  );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          controller: controller,
        ),
        Row(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('<<'),
                onPressed: () async {
                  await _moveCursorLeft();
                },
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: RaisedButton(
                child: Text('>>'),
                onPressed: () async {
                  await _moveCursorRight();
                },
              ),
            ),
          ],
        )
      ],
    );
  }

  void _moveCursorLeft() async {
    int currentCursorPosition = controller.selection.start;
    if (currentCursorPosition == 0)
      return;
    int newPosition = await icuText.previousGraphemePosition(currentCursorPosition);
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }

  void _moveCursorRight() async {
    int currentCursorPosition = controller.selection.end;
    if (currentCursorPosition == controller.text.length)
      return;
    int newPosition = await icuText.nextGraphemePosition(currentCursorPosition);
    controller.selection = TextSelection(baseOffset: newPosition, extentOffset: newPosition);
  }
}


原答案:

在 Dart/Flutter 完全实现 ICU 之前,我认为你最好的选择是使用 PlatformChannel 传递原生的 Unicode 字符串(iOS Swift4+ 或 Android Java/Kotlin)在那里迭代/操作,然后发回结果。

  • 对于 Swift4+,它与您提到的文章一样是开箱即用的(不是 Swift3-,不是 ObjC)
  • 对于 Java/Kotlin,将 Oracle 的 BreakIterator 替换为 ICU library 的,效果更好。除了导入语句之外没有任何变化。

我建议使用原生操作(而不是在 Dart 上使用)的原因是因为 Unicode 有太多东西需要处理,例如规范化、规范等价、ZWNJ、ZWJ、ZWSP 等。

如果您需要一些示例代码,请在下方评论。

【讨论】:

  • 我希望会有一个原生的 Dart 答案,但你可能是对的。如果你有示例代码,那就太好了,但你的答案也很好。
  • 其实让我看看能不能把它封装在一个插件里,这样代码作者就不用和原生的Swift/Java进行这么多的交互了
  • 太棒了!这是一个很大的帮助!
  • @TruongSinh 您有将插件迁移到 AndroidX 的计划吗?我还建议您在 Flutter Packages 中链接您的 Github 存储库。
  • 如何在kotlin中导入ICU库?是不是简单的加了个语句import icu?
【解决方案2】:

2020 年更新

使用 Dart 团队的 characters 包。它现在是处理字素簇的官方方式。

使用text.characters 获取字素簇。用户text.characters.iterator 移动它们。我仍在研究如何将CharacterRange 转换为TextSelection。当我有更多细节时,我会更新这个答案。

注意:这是对我旧答案的完全重写。有关详细信息,请参阅编辑历史记录。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-15
    • 2019-02-17
    • 2020-03-05
    • 2017-07-15
    • 1970-01-01
    相关资源
    最近更新 更多