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

29r-ax3vqt5bmryx1jzpwin8smk.png


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


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

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


В 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 проекты.
Шрифт скачивать здесь
Иконки искать здесь

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

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

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

UIAppFonts

    Fonts/MaterialIcons-Regular.ttf


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

Usage:


arrow_back



arrow_back


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


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

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

public class GoogleMaterialFontBehavior: BehaviorBase
{
}


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

using System;
using Xamarin.Forms;

namespace MyApp.UserControls
{
    public class CustomLabel : Label
    {
        public event EventHandler 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
    {
        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 iconCodeDict = new Dictionary
        {
            {"3d_rotation", '\ue84d'},
            {"ac_unit", '\ueb3b'},
            {"access_alarm", '\ue190'},
        …
        }
    }
}


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

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


XAML:

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

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


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


    
        
    


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
Иконки искать здесь: https://jam-icons.com/

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

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

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

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

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



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

{"arrow-left", '\ue92b'}


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

© Habrahabr.ru