Виджет вертикального текста для Flutter

В документах TextDirection говорится:

Flutter разработан для удовлетворения потребностей приложений, написанных на любом из языков, используемых в настоящее время в мире, независимо от того, используют ли они направление письма справа налево или слева направо. Flutter не поддерживает другие режимы написания, такие как вертикальный текст или бустрофедонный текст, поскольку они редко используются в компьютерных программах. (курсив мой)

Flutter не только не поддерживает вертикальный текст, но и не будет официально поддерживаться в будущем. Это было раннее дизайнерское решение (см. здесь и здесь).

Тем не менее, сегодня есть реальная польза от поддержки вертикального скрипта. Помимо некоторых видов использования на китайском и японском, тег традиционное монгольское письмо требует этого. Этот сценарий очень часто используется в компьютерных программах во Внутренней Монголии (пример, пример, пример, пример, пример, пример).

Он пишется сверху вниз, а строки переносятся слева направо:

введите описание изображения здесь

Кроме того, символы эмодзи и CJK сохраняют свою ориентацию при вертикальном отображении (не обязательно, но желательно). На изображении ниже верхний абзац показывает текущую реализацию, а нижний абзац показывает правильный вертикальный рендеринг:

введите описание изображения здесь

// sample text
ᠨᠢᠭᠡ ᠬᠣᠶᠠᠷ ᠭᠣᠷᠪᠠ ᠳᠥᠷᠪᠡ ᠲᠠᠪᠤ ᠵᠢᠷᠭᠤᠭ᠎ᠠ ᠳᠣᠯᠣᠭ᠎ᠠ ᠨᠠ‍ᠢᠮᠠ ᠶᠢᠰᠦ ᠠᠷᠪᠠ one two three four five six seven eight nine ten ????????汉字 한국어 モンゴル語 English? ᠮᠣᠩᠭᠣᠯ︖

Поскольку Flutter не поддерживает вертикальный скрипт, он должен быть реализован с нуля. Весь фактический макет текста и рисование выполняются в движке Flutter с помощью LibTxt, поэтому я не могу это изменить.

Что потребуется для создания виджета с вертикальным текстом сверху вниз?

Обновлять

Я все еще ищу ответ. Ответ Реми хорош для однострочного текста, но не работает для многострочного текста.

В настоящее время я исследую три различных возможных решения:

  • Создайте подкласс RenderObject (или, возможно, подкласс RenderParagraph), который поддерживает настраиваемый StatelessWidget похож на виджет RichText.
  • Используйте виджет CustomPaint
  • Создайте составной виджет из ListView (одна строка на текстовую строку), текстовых виджетов и WidgetSpans.

Все это потребовало бы измерения текста, его разметки и получения списка строк.

Еще одно обновление

В настоящее время я склоняюсь к созданию собственного виджета и объекта рендеринга, который будет имитировать RichText и RenderParagraph. Под капотом RenderParagraph использует TextPainter для компоновки и раскрашивания текста. Моя проблема сейчас в том, что TextPainter тесно связан с базовой библиотекой Skia LibTxt. Вот где происходит собственно макет и покраска. Размещение текста чем-либо, кроме стандартных ltr и rtl, оказывается большой проблемой.

Еще одно обновление

Принятый ответ соответствует критериям, которые я сформулировал в этом вопросе. Я думаю, что это удовлетворит краткосрочные потребности. У меня есть некоторые оговорки по поводу применения стиля текста, но мне нужно будет провести больше тестов. В долгосрочной перспективе я все еще могу попытаться сделать собственный макет текста и рисование.


person Suragch    schedule 27.06.2019    source источник
comment
Если кто-то ищет английский язык, этот вопрос может помочь.   -  person CopsOnRoad    schedule 11.10.2019


Ответы (6)


Это решение основано на макете flex-box. Он преобразует строку в список слов и помещает их в вертикально повернутые текстовые виджеты. Они размещены с виджетом Wrap, установленным на Axis.vertical. Виджет «Перенос» автоматически обрабатывает слова, которые необходимо перенести, помещая их в следующий столбец.

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Wrap(
          direction: Axis.vertical,
          children: _getWords(),
        ),
      ),
    );
  }

  List<Widget> _getWords() {
    const text =
        "That's all ???????? 12345 One Two Three Four Five ᠸᠢᠺᠢᠫᠧᠳᠢᠶᠠ᠂ ᠴᠢᠯᠦᠭᠡᠲᠦ ᠨᠡᠪᠲᠡᠷᠬᠡᠢ ᠲᠣᠯᠢ ᠪᠢᠴᠢᠭ ᠪᠣᠯᠠᠢ᠃";
    var emoji = RegExp(r"([\u2200-\u3300]|[\uD83C-\uD83E].)");
    List<Widget> res = [];
    var words = text.split(" ");
    for (var word in words) {
      var matches = emoji.allMatches(word);
      if (matches.isEmpty) {
        res.add(RotatedBox(quarterTurns: 1, child: Text(word + ' ')));
      } else {
        var parts = word.split(emoji);
        int i = 0;
        for (Match m in matches) {
          res.add(RotatedBox(quarterTurns: 1, child: Text(parts[i++])));
          res.add(Text(m.group(0)));
        }
        res.add(RotatedBox(quarterTurns: 1, child: Text(parts[i] + ' ')));
      }
    }
    return res;
  }
}

введите описание изображения здесь

person Spatz    schedule 08.07.2019

Боковой текст можно использовать с помощью RotatedBox, чего достаточно для правильного переноса текста, как и следовало ожидать.

Row(
  children: <Widget>[
    RotatedBox(
      quarterTurns: 1,
      child: Text(sample),
    ),
    Expanded(child: Text(sample)),
    RotatedBox(
      quarterTurns: -1,
      child: Text(sample),
    ),
  ],
),

введите описание изображения здесь

Точно так же Flutter теперь поддерживает встроенные виджеты внутри текста. Это можно использовать для поворота смайлов внутри текста.

RotatedBox(
  quarterTurns: 1,
  child: RichText(
    text: TextSpan(
      text: 'Hello World',
      style: DefaultTextStyle.of(context).style,
      children: [
        WidgetSpan(
          child: RotatedBox(quarterTurns: -1, child: Text('????')),
        )
      ],
    ),
  ),
),

введите описание изображения здесь

person Rémi Rousselet    schedule 02.07.2019
comment
Отличное представление о новом WidgetSpan. Это должно работать с однострочным текстом. Ту же концепцию можно использовать для поддержки символов CJK с помощью списка диапазонов виджетов. - person Suragch; 02.07.2019
comment
Действительно. Небольшой скрипт должен легко выполнить это преобразование. - person Rémi Rousselet; 02.07.2019
comment
Для многострочного текста простого поворота абзаца недостаточно. Монгольский текст пишется сверху вниз, а столбцы переносятся слева направо. В вашем первом примере текст в первом дочернем элементе строки переносится справа налево. Это было бы похоже на чтение книги на английском языке, начиная с конца книги и постепенно увеличивая ее. В третьем дочернем элементе строки текст перевернут. - person Suragch; 02.07.2019
comment
Боковое примечание: для вертикальной китайской / японской раскладки столбцы обычно имеют перенос строк справа налево. Так что простой поворот абзаца (quarterTurns: 1) может быть правильным решением. Поскольку создание виджетов обходится дешево, я полагаю, что наличие WidgetSpan для каждого отдельного символа может работать, но это необходимо проверить. - person Suragch; 02.07.2019

Простое использование RotatedBox

RotatedBox(
  quarterTurns: 1,
  child: //your text
),

person Røhäñ Dås    schedule 07.06.2021

Если вы создаете собственный шрифт со всеми символами, повернутыми на 180 ° (жесткая часть), вы можете просто изменить textDirection на TextDirection.rtl (справа налево) и повернуть текст на четверть (тоже непросто).

person Spatz    schedule 06.07.2019
comment
Идея хороша, хотя я не думаю, что эта конкретная реализация сработает, поскольку изменение направления текста не меняет порядок рисования глифов. Аналогичная идея - вертикальное отражение глифов в шрифте, а затем поворот и зеркальное отражение виджет «Текст» (см. второе изображение здесь). Это имеет недостаток, заключающийся в том, что любые глифы, которые не включены в шрифт, в конечном итоге зеркально отражаются, но это, возможно, можно было бы обработать с помощью WidgetSpan, как указано в ответе Реми. В любом случае, это реальный вариант. - person Suragch; 06.07.2019
comment
Да, идея двойного переворота выглядит идеально, но как она будет работать в режиме редактирования? Кстати. Мои эксперименты с режимом письма справа налево тоже разочаровали. Удачи в этом великом испытании. - person Spatz; 06.07.2019
comment
Раньше я использовал метод двойного переворота в Android, но в конце концов отказался от него в пользу нестандартной компоновки и раскраски. В конце концов, это кажется более гибким. Спасибо за ваш вклад. - person Suragch; 06.07.2019

В итоге я создал собственный виджет для обработки текста.

введите описание изображения здесь

Это была нетривиальная задача, но она гибкая. Другие посетители этого вопроса могут следовать аналогичной схеме, если другие ответы не соответствуют вашим потребностям.

Если вы хотите больше контролировать рендеринг текста во Flutter, прочтите Мой первый Разочарование Flutter и добавьте комментарий к этой проблеме GitHub.

person Suragch    schedule 11.02.2020

Просто нарисуйте то, что вы хотите

Примеры flutter-widget-positioning-guide

тогда

Column(
    children: [
        MyWidget(),
        MyWidget(),
        MyWidget()
    ]
);

// or swap a Column for a Row to flip the axis

Row(children: [ ]);

// and this is all just sugar for the Flex widget
Flex(
    direction: Axis.vertical<--------------
)
person Chanaka Weerasinghe    schedule 08.07.2019