1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-N3.md 21 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Небольшие хитрости Flutter: работа с шрифтами и решение проблем

Эта небольшая статья посвящена теме отрисовки шрифтов в Flutter. Хотя это может показаться незначительной деталью, она важна при возникновении связанных проблем.

В этой статье мы быстро познакомимся с основами отрисовки шрифтов, решим некоторые проблемы, связанные с ними, а также рассмотрим несколько полезных приемов. Статья может оказаться достаточно объемной, поэтому рекомендуется отметить её для последующего чтения.

1. Шрифты

Сперва ответьте на вопрос, который я часто задаю на собеседованиях: Какие шрифты используются в Flutter на платформах Android и iOS?

Если вы знакомы с исходным кодом typography.dart, то ваш первый вывод будет следующим:

  • На Android используется шрифт Roboto;
  • На iOS используются шрифты .SF UI Display или .SF UI Text.

Однако если вы углубитесь в эту тему, то узнаете, что:

  • По умолчанию на iOS:
    • Китайский шрифт: PingFang SC (для традиционного китайского — PingFang TC, для гонконгского — PingFang HK);
    • Английский шрифт: .SF UI Text или .SF UI Display.
  • По умолчанию на Android:
    • Китайский шрифт: Source Han Sans или Noto;
    • Английский шрифт: Roboto.

Теперь вы можете спросить: Можно ли использовать шрифт PingFang для отображения английских букв вместо .SF? Ответ положительный, но формы и вес буквы могут немного отличаться. Например, буква G в этих двух шрифтах выглядит по-разному.image-20220601141145552

А что делать, если вам нужно отобразить китайские и корейские буквы одновременно? В этом случае шрифты PingFang и .SF недостаточны, и вам потребуется использовать универсальный шрифт, такой как Apple SD Gothic Neo. Однако здесь стоит обратить внимание на одну ошибку, которую вы можете встретить в Flutter.

На приведённом ниже рисунке видно, что при использовании шрифта Apple SD Gothic Neo для отображения китайских и корейских символов некоторые буквы могут выглядеть странно. Например, буквы «推» и «广». Буква «广», которая отсутствует в универсальном шрифте, отображается как китайская буква «广», тогда как буква «推» использует форму из универсального шрифта.

image-20220601141720525

Для решения этой проблемы можно воспользоваться следующим простым методом: Добавьте в конфигурацию fontFamilyFallback свойства TextStyle или Theme значения ["PingFang SC", "Heiti SC"].image-20220601142805434

Кроме того, если вы всё ещё сомневаетесь в различиях между шрифтами .SF UI Display и .SF UI Text, то вам не стоит слишком беспокоиться, так как приблизительно можно понять следующее:

.SF Text подходит для более мелкого шрифта; .SF Display подходит для крупного шрифта, границей может служить примерно 20 пунктов. Однако SF (San Francisco) является динамическим шрифтом, и система будет динамически подбирать наиболее подходящий вариант.# Второй раздел. Flutter Text

Хотя выше были рассмотрены некоторые аспекты шрифтов, есть и различия между ними в Flutter. В Flutter логика отображения текста имеет несколько уровней, среди которых:

  • Библиотека libtxt, основанная на Minikin, используется для выбора шрифтов и разбиения строк;
  • HarfBuzz используется для выбора глифов и их формирования;
  • Skia используется как движок рендера/GPU;
  • На Android/Fuchsia используется FreeType для рендера шрифтов, а на iOS — CoreGraphics.

Высота строки

А теперь представьте, что я спрошу вас: Какую высоту займет буква "H" со значением fontSize: 100? Как бы вы ответили?

Сначала сравним красный контейнер размером 100 пикселей и текст "H" со значением fontSize: 100. Можно заметить, что область синего цвета, где находится текст "H", должна быть больше области красного цвета.

image-20220601145346189

Фактически, эта синяя область представляет собой высоту строки, которая зависит от параметра height в объекте TextStyle.

По умолчанию значение параметра height равно null, но когда мы его установим равным 1, как показано ниже, можно видеть, что высота синей области совпадает с высотой красной области, то есть она становится 100 пикселей. Таким образом, буква "H" полностью помещается в синюю область.

image-20220601145634196Что такое height? В объекте TextStyle значение параметра height влияет на высоту строки следующим образом:

  • Когда значение height равно null, высота строки по умолчанию определяется мерой шрифта (об этом подробнее позже);
  • Когда значение height установлено, высота строки равна произведению значения height и значения fontSize;

Например, сравнение синего и красного цветов показывает разницу высот при значениях height равных null и 1.

image-20220601145346189 image-20220601145634196image-20220601145710275

Так что, прочитав это, вы узнали ещё один небольшой трюк: когда текст внутри контейнера с "ограниченной высотой" не может быть центрирован, можно попробовать изменить параметр height в TextStyle, чтобы добиться желаемого эффекта.

image-20220601151621858

Конечно, если вы удалите параметр height:50 у контейнера, то получится другой эффект.

Таким образом, параметр height и высота отрисованного текста находятся в пропорциональной зависимости, как показано ниже:

image-20220601151923432

Кроме того, в тексте помимо параметра height в TextStyle есть также параметр height в StrutStyle, который влияет на общую меру шрифта, то есть на высоту между вершинами (ascent) и нижними границами (descent).image-20220601152843273

Теперь скажите, чем отличается этот параметр height в StrutStyle от аналогичного параметра в TextStyle? Как показывает следующий пример:

  • Когда параметр forceStrutHeight в StrutStyle установлен в true, параметр height в TextStyle не применяется;
  • Установка параметра fontSize:50 в StrutStyle влияет на содержание по-разному по сравнению с установкой параметра fontSize:100 в TextStyle.

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

Так что, прочитав это, вы узнали ещё один небольшой трюк: установка параметра leading позволяет равномерно распределять высоту, поэтому он также используется для регулировки межстрочных интервалов.

image-20220601154712338

Для получения более подробной информации о высоте строки обратитесь к статье «Глубокое понимание «холодных» знаний о шрифтах в Flutter».

FontWeightЕщё одна важная информация о шрифтах — это параметр FontWeight. Наверняка многие знакомы с этим параметром; например, стандартное значение normal равно w400, а часто используемый параметр bold равен w700. Весь диапазон значений параметра FontWeight охватывает значения от 100 до 900. image-20220601155236983Итак, здесь возникает вопрос: можно ли найти соответствующую толщину шрифта (Weight) для каждого из этих значений в шрифте?

Ответ отрицательный, так как обычно, как показано на следующем рисунке, некоторые библиотеки шрифтов не поддерживают определенные значения Weight, например:

  • Шрифт Roboto не имеет w600
  • Шрифт PingFang не имеет значений выше w600

image-20220601162130629

Вы можете задаться вопросом, почему мы подробно рассматриваем FontWeight? Это связано с тем, что в Flutter 3.0 есть проблема с отображением китайских символов!

На следующем рисунке видно, что в Flutter 3.0 отображение китайских символов со значением веса от 100 до 500 некорректно, глазами можно заметить, что все эти значения отображаются как одинаковый вес.

image-20220601162935325

Эта проблема связана с тем, что при вызове метода onMatchFamilyStyleCharacter, реализация этого метода не выбирает наиболее подходящий шрифт, который соответствует TextStyle. Поэтому при вызове функции CTFontCreateWithFontDescriptor передается значение веса, но отсутствует имя семейства шрифтов (familyName). В результате функция CTFontCreateWithFontDescriptor возвращает по умолчанию Helvetica шрифт.Простое временное решение заключается в том, чтобы глобально установить fontFamilyFallback: ["Roboto"] или fontFamily: 'Roboto', что решает проблему, используя механизм fallback. Вы заметите, что ранее рассмотренные знания о шрифтах могут быть быстро применены здесь.image-20220601163255325

Поскольку китайский язык на iOS использует шрифт PingFang SC, достаточно использовать этот шрифт в качестве fallback для корректного отображения. Эта проблема проявляется на эмуляторах Android, устройствах iOS, Mac и других платформах, но на реальных устройствах Android она отсутствует. Я также сообщил о ней в #105014.

После добавления fallback эффект представлен на левой стороне приведённого выше рисунка. Какова же роль fallback?

Ранее мы уже говорили, что система использует различные библиотеки шрифтов для отображения нескольких языков, а когда нужный шрифт не находится, используется последовательность шрифтов, указанной в fallback, например:

Если требуемый шрифт не найден в fontFamily, поиск осуществляется в fontFamilyFallback. Если шрифт не найден ни там, ни там, возвращается дефолтный шрифт. Кроме того, в отношении FontWeight есть ещё один "сюрприз": на iOS, если пользователь включил "Жирный шрифт" в настройках доступности, то при использовании компонента Text, все шрифты автоматически становятся жирными с весом w700.

image-20220601164236038Это происходит потому что внутри Text используется проверка MediaQuery.boldTextOverride. Flutter получает информацию от пользователя iOS о том, что "Bold Font" активирован, и вынуждает установить fontWeight как FontWeight.bold. Однако, если вы используете RichText, такого поведения не будет.

Здесь снова можно использовать небольшой трюк: если вы хотите избежать влияния этих системных действий, вы можете глобально отключить его, используя вложенные MediaQuery. Аналогичным образом можно отключить textScaleFactor и platformBrightness.

return MediaQuery(
  data: MediaQueryData.fromWindow(WidgetsBinding.instance!.window).copyWith(boldText: false),
  child: MaterialApp(
    useInheritedMediaQuery: true,
  ),
);

image-20220531082324707

FontFeature

И последнее, поговорим о малоизвестном параметре FontFeature.

Что такое FontFeature? Проще говоря, это свойство, которое влияет на форму символов шрифта, аналогично font-feature-settings в области фронтенд-разработки. Он отличается от FontFamily тем, что используется для указания параметров формы символов внутри шрифта.

На следующем рисунке показана сравнительная отрисовка параметров frac (дробь) и tnum (табличные цифры). Такое поведение позволяет достичь специальных эффектов отображения без необходимости использования дополнительной библиотеки шрифтов. Также слово "Feature" может переводиться как "функциональность", поэтому можно сказать, что это характеристика шрифта.

image-20220601165224593

А зачем нужен FontFeature? Вот ещё одна хитрая идея: если цифры и текст расположены рядом и не выравниваются должным образом, вы можете использовать fontFeatures: [FontFeature('tnum')] для выравнивания.Например, на следующем рисунке слева показано состояние без применения fontFeatures, а справа — после применения FontFeature('tnum'). Разница довольно заметна.

image-20220601165855711

Для получения более подробной информации о FontFeature см. статью "Flutter: альтернативные способы работы со шрифтами: FontFeature".

3. Заключение

В заключение стоит отметить, что данная статья содержит достаточно плотную информацию, которая включает:

  • Основы шрифтов
  • Высоту строки
  • Вес шрифта
  • Возможности шрифтов

Из вышеперечисленных аспектов были рассмотрены "холодные" знания и небольшие хитрости по работе с шрифтами в Flutter, такие как решение проблем с отображением шрифтов при использовании нескольких языков, правильная настройка высоты строки и работа с цифровым контентом.

Если у вас есть вопросы по поводу шрифтов, пожалуйста, оставьте свои комментарии для обсуждения!

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master