Постановка задачи
Для отображения иконок в приложении 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):
<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 проекты.
Шрифт скачивать здесь
Иконки искать здесь
Скачиваем файл шрифта — MaterialIcons-Regular.ttf.
Android:
Добавляем MaterialIcons-Regular.ttf в папку Assets и выбираем ему «Build action» AndroidAsset.
iOS:
Добавляем MaterialIcons-Regular.ttf в папку Resources\Fonts и выбираем ему «Build action» BundleResource, затем прописываем шрифт в info.plist:
<key>UIAppFonts</key>
<array>
<string>Fonts/MaterialIcons-Regular.ttf</string>
</array>
Теперь предположим, мы хотим иконку со стрелкой влево в нашем приложении. Заходим на страницу с поисковиком иконок: https://material.io/resources/icons/, вводим «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/)
Для этого создадим класс 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
Пример использования
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
Иконки искать здесь: https://jam-icons.com/
Очень удобный поисковик. Находите иконку, жмёте на нее и имя в буфере обмена. Осталось просто вставить его в код.
Алгоритм такой же, как для иконок от google, кроме получения кодов иконок.
Коды надо взять из файла svg, находящегося там же.
Данные легко вытащить при помощи текстового редактора, например 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