- PVSM.RU - https://www.pvsm.ru -

Xamarin.Forms — удобное использование иконочных шрифтов в приложении

Xamarin.Forms — удобное использование иконочных шрифтов в приложении - 1

Постановка задачи

Для отображения иконок в приложении Xamarin.Forms можно использовать изображения в различных форматах, например png, svg или шрифты ttf. Чаще всего для добавления стандартных иконок удобен шрифт с иконками, например google material icons. Шрифт с иконками имеет размер около 200КБ и удобство использования здесь обычно важнее экономии на размере приложения. Иконки будут хорошо смотреться при любом разрешении экрана и будут чёрно-белыми.

Для использования иконок есть готовые nuget-пакеты. Я долгое время использовал iconize (nuget — www.nuget.org/packages/Xam.Plugin.Iconize [1]; git — github.com/jsmarcus/Iconize [2]). Он позволяет подключать более десяти шрифтов, добавляет новые контролы, такие как IconButton, IconImage, IconLabel и т.п. Но тут есть обычные аргументы против готовых библиотек: лишний функционал, лишний размер файлов, не полностью устраивает поведение, баги и т.п. Поэтому в определённый момент решил отказаться от готовой библиотеки и заменить ее на простейший велосипед из пары классов + шрифт.

Иконка из шрифта ttf используется в xaml следующим образом(iOS):

<Label Text="" FontFamily="MaterialIcons-Regular"/>

В C#:

var label = new Label { Text ="ue5d2", FontFamily = "MaterialIcons-Regular" };

Перечислю возникающие проблемы.

1. разный формат записи кода иконки в XAML и C# ("" / "ue5d2")

2. код иконки не ассоциируется с самой иконкой, хотелось бы использовать что-то вроде «arrow_back», как в iconize

3. если приложение будет работать на android и iOS, то по-разному надо писать название шрифта — «MaterialIcons-Regular.ttf#MaterialIcons-Regular» в андроид и «MaterialIcons-Regular» в iOS

Описываемое здесь решение требует наличия события TextChanged у элемента управления Label. Добавим и эту проблему в список:

4. у контрола Label отсутствует событие TextChanged. Это нам нужно, чтобы отслеживать изменения текста метки и использовать binding в xaml

Решение

Для примера возьмём шрифт google material icons. Сначала добавим его в андроид и iOS проекты.
Шрифт скачивать здесь [3]
Иконки искать здесь [4]

Скачиваем файл шрифта — MaterialIcons-Regular.ttf.

Android:
Добавляем MaterialIcons-Regular.ttf в папку Assets и выбираем ему «Build action» AndroidAsset.

iOS:
Добавляем MaterialIcons-Regular.ttf в папку ResourcesFonts и выбираем ему «Build action» BundleResource, затем прописываем шрифт в info.plist:

<key>UIAppFonts</key>
<array>
    <string>Fonts/MaterialIcons-Regular.ttf</string>
</array>

Теперь предположим, мы хотим иконку со стрелкой влево в нашем приложении. Заходим на страницу с поисковиком иконок: https://material.io/resources/icons/ [4], вводим «arrow» в левом верхнем углу, выбираем стрелку влево в результатах. Теперь нажимаем на панель «Selected Icon» чтобы увидеть код иконки… а его там нет:

Usage:
<!--For modern browsers-->
<i class="material-icons">
arrow_back
</i>
<!-- For IE9 or below. -->
<i class="material-icons">
arrow_back
</i>

Да и не очень удобно каждый раз переводить названия в коды, а потом в коде приложения разбираться что это за иконка. Удобнее было бы использовать понятное имя иконки — «arrow_back» вот таким образом:

<Label Text="arrow_back">

Для решения будем использовать так называемые behavior, в русском переводе «Реакции на события Xamarin.Forms» (https://docs.microsoft.com/ru-ru/xamarin/xamarin-forms/app-fundamentals/behaviors/ [5])

Для этого создадим класс GoogleMaterialFontBehavior:

public class GoogleMaterialFontBehavior: BehaviorBase<CustomLabel>
{
}

Наш behavior будет реагировать на изменение текста метки, поэтому придётся также доработать Label:

using System;
using Xamarin.Forms;

namespace MyApp.UserControls
{
    public class CustomLabel : Label
    {
        public event EventHandler<EventArgs> TextChanged;

        public static readonly new BindableProperty TextProperty =
            BindableProperty.Create(
                propertyName: nameof(Text),
                returnType: typeof(string),
                declaringType: typeof(CustomLabel),
                defaultValue: "", 
                defaultBindingMode: BindingMode.OneWay, 
                propertyChanged: TextChangedHandler);

        public new string Text
        {
            get => (string)GetValue(TextProperty);
            set
            {
                base.Text = value;
                SetValue(TextProperty, value);
            }
        }

        private static void TextChangedHandler(BindableObject bindable, object oldValue, object newValue)
        {
            var control = bindable as CustomLabel;
            control.TextChanged?.Invoke(control, new EventArgs());
        }
    }
}

В классе GoogleMaterialFontBehavior надо:

  • переопределить методы OnAttachedTo/OnDetachingFrom
  • добавить обработчик изменения текста: HandleTextChanged
  • добавить имя шрифта fontFamily
  • а также добавить справочник кодов символов iconCodeDict

namespace MyApp.Behaviors
{
    public class GoogleMaterialFontBehavior: BehaviorBase<CustomLabel>
    {
        protected override void OnAttachedTo(CustomLabel bindable)
        {
            HandleTextChanged(bindable, null);
            bindable.TextChanged += HandleTextChanged;
            base.OnAttachedTo(bindable);
        }

        protected override void OnDetachingFrom(CustomLabel bindable)
        {
            bindable.TextChanged -= HandleTextChanged;
            base.OnDetachingFrom(bindable);
        }

        private void HandleTextChanged(object sender, EventArgs e)
        {
            var label = (CustomLabel)sender;

            if (label?.Text?.Length >= 2
                && iconCodeDict.TryGetValue(label.Text, out var icon))
            {
                label.FontFamily = fontFamily;
                label.Text = icon.ToString();
            }
        }

        //

        private static readonly string fontFamily = Device.RuntimePlatform == Device.Android ? "MaterialIcons-Regular.ttf#MaterialIcons-Regular" : "MaterialIcons-Regular";

        private static readonly Dictionary<string, char> iconCodeDict = new Dictionary<string, char>
        {
            {"3d_rotation", 'ue84d'},
            {"ac_unit", 'ueb3b'},
            {"access_alarm", 'ue190'},
        …
        }
    }
}

А теперь про сам справочник iconCodeDict. Он будет содержать пары имя-код для символов шрифта. В случае с google material icons эти данные лежат в git-репозитории в отдельном файле: github.com/google/material-design-icons/blob/master/iconfont/codepoints [6]

Пример использования

XAML:

в начале страницы добавить 2 неймспейса:

xmlns:uc="clr-namespace:MyApp.UserControls"
xmlns:b="clr-namespace:MyApp.Behaviors"

и выводим иконку:

<uc:CustomLabel Text="arrow_back" FontSize="30">
    <Label.Behaviors>
        <b:GoogleMaterialFontBehavior />
    </Label.Behaviors>
</uc:CustomLabel>

C#:

var label = new CustomLabel();
label.Behaviors.Add (new GoogleMaterialFontBehavior());
label.Text = "arrow_back";

Дополнительно

Также мне очень понравился шрифт Jam icons. Для него создадим класс JamFontBehavior по аналогии с GoogleMaterialFontBehavior. Другими в нём будут: fontFamily и iconCodeDict.

Шрифт скачивать здесь: https://github.com/michaelampr/jam/blob/master/fonts/jam-icons.ttf [7]
Иконки искать здесь: https://jam-icons.com/ [8]

Очень удобный поисковик. Находите иконку, жмёте на нее и имя в буфере обмена. Осталось просто вставить его в код.

Алгоритм такой же, как для иконок от google, кроме получения кодов иконок.

Коды надо взять из файла svg, находящегося там же [9].

Данные легко вытащить при помощи текстового редактора, например sublime. Выделяете строку «data-tags», нажимаете Alt+F3, включается мультикурсор. Далее shift+home, ctrl+c, ctrl+a, ctrl+v. И так далее. Можно при помощи Excel, кому что привычнее.

Например, стрелка влево выглядит в svg-файле так:

<glyph unicode="" glyph-name="arrow-left" data-tags="arrow-left" d="M358.997 398.635l168.533-168.533c7.15-7.611 11.543-17.885 11.543-29.185 0-23.564-19.103-42.667-42.667-42.667-11.311 0-21.594 4.401-29.229 11.585l0.022-0.020-241.365 241.323c-7.749 7.706-12.545 18.376-12.545 30.165s4.796 22.459 12.544 30.163l241.367 241.367c7.769 8.036 18.647 13.026 30.69 13.026 23.564 0 42.667-19.103 42.667-42.667 0-12.043-4.989-22.92-13.014-30.678l-168.545-168.545h409.003c23.564 0 42.667-19.103 42.667-42.667s-19.103-42.667-42.667-42.667v0h-409.003z" />

После очистки текста получаем содержимое полей glyph-name и unicode:

{"arrow-left", 'ue92b'}

и добавляем в словарь iconCodeDict

Автор: aleks42

Источник [10]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/krossplatformennaya-razrabotka/330028

Ссылки в тексте:

[1] www.nuget.org/packages/Xam.Plugin.Iconize: https://www.nuget.org/packages/Xam.Plugin.Iconize/

[2] github.com/jsmarcus/Iconize: https://github.com/jsmarcus/Iconize

[3] Шрифт скачивать здесь: https://github.com/google/material-design-icons/blob/master/iconfont/MaterialIcons-Regular.ttf

[4] Иконки искать здесь: https://material.io/resources/icons/

[5] https://docs.microsoft.com/ru-ru/xamarin/xamarin-forms/app-fundamentals/behaviors/: https://docs.microsoft.com/ru-ru/xamarin/xamarin-forms/app-fundamentals/behaviors/

[6] github.com/google/material-design-icons/blob/master/iconfont/codepoints: https://github.com/google/material-design-icons/blob/master/iconfont/codepoints

[7] https://github.com/michaelampr/jam/blob/master/fonts/jam-icons.ttf: https://github.com/michaelampr/jam/blob/master/fonts/jam-icons.ttf

[8] https://jam-icons.com/: https://jam-icons.com/

[9] там же: https://raw.githubusercontent.com/michaelampr/jam/master/fonts/jam-icons.svg

[10] Источник: https://habr.com/ru/post/467423/?utm_source=habrahabr&utm_medium=rss&utm_campaign=467423