При разработке мобильного приложения есть масса моментов, на которые необходимо обращать внимание. Это и выбор технологии, на которой оно будет написано, и разработка архитектуры приложения, и, собственно, написание кода. Рано или поздно наступает момент, когда костяк приложения есть, вся логика прописана и приложение , в общем-то, работает, но… нет внешнего вида. Тут стоит задуматься о графических ресурсах, которые будут использованы, поскольку графика составляет львиную долю размера итоговой сборки, будь то .apk на Android или .ipa на iOS. Сборки огромных размеров в принципе ожидаемы для мобильных игр, уже сейчас из PlayMarket порой приходится загружать объемы данных вплоть до 2 Гб и хорошо, если во время загрузки есть возможность подключиться к Wi-Fi или мобильный оператор предоставляет скоростное безлимитное подключение. Но для игр это ожидаемо, а бизнес-приложение, обладающее таким размером, невольно вызывает вопрос “Откуда столько?”. Одной из причин большого размера сборки бизнес-приложения может стать значительное количество иконок и картинок, которые в нем приходится отображать. А также не следует забывать о том, что большое количество графики пропорционально влияет на быстродействие приложения.


При создании графической составляющей приложения часто возникает серьезная проблема. Мобильных устройств существует великое множество начиная с часов и заканчивая планшетами, и разрешения их экранов очень разнятся. Из-за этого зачастую приходится включать в сборку графические ресурсы отдельными файлами для каждого из существующих типов. По 5 копий для Android и по 3 для iOS. Это существенно влияет на размер итоговой сборки, которую Вы будете выкладывать в сторы.

О том, что можно сделать для того, чтобы не попасть в такую ситуацию, мы расскажем в этой статье.


Сравнение форматов PNG и SVG


Основное различие между форматами PNG и SVG заключается в том, что PNG — формат растровой графики, а SVG — векторной.

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

  1. Растровая графика позволяет создавать рисунки практически любой сложности, без ощутимых потерь в размере файла;
  2. Если не требуется масштабирование изображения, то скорость обработки сложных изображений весьма высока;
  3. Растровое представление изображений естественно для большинства устройств ввода-вывода.

Однако, есть и минусы.

Например, растровое изображение невозможно идеально масштабировать. При увеличении небольшой картинки изображение «мылится» и видно пиксели из которых оно состоит.



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

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

Также, будучи языком разметки, SVG позволяет внутри документа применять фильтры (например, размытие, выдавливание и т.д.). Они объявляются тегами, за визуализацию которых отвечает средство просмотра, а значит, они не влияют на размер исходного файла.


Помимо перечисленного, формат SVG имеет еще ряд преимуществ:

  1. Будучи векторным форматом, SVG позволяет масштабировать любую часть изображения без потерь в качестве;
  2. В SVG-документ можно вставлять растровую графику;
  3. Легко интегрируется с HTML- и XHTML-документами.

Однако, есть и недостатки данного формата:

  1. Чем больше в изображении мелких деталей, тем быстрее растет размер SVG-данных. В ряде случаев, SVG не только не дает преимуществ, но и проигрывает растру;
  2. Сложность использования в картографических приложениях, поскольку для корректного отображения малой части изображения, требуется прочитать документ целиком.

В случае с разработкой мобильных приложений на платформе Xamarin есть еще один недостаток.

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

Работа с форматом SVG в Xamarin.Android


Как уже было сказано выше, Xamarin “из коробки” не поддерживает работу с форматом SVG. Чтобы решить эту проблему – можно использовать сторонние библиотеки или VectorDrawable. В последнее время разработчики все чаще отдают предпочтение последним, так как большая часть библиотек для Xamarin ориентирована на кроссплатформенное использование в Xamarin.Forms, а решения для нативного андроида либо больше не поддерживаются их разработчиками и устарели, либо возникают серьезные проблемы при попытке создать на их основе библиотеку для Xamarin. В связи с этим здесь рассмотрим использование VectorDrawable.


Для реализации данного подхода потребуется использовать Vector Asset Studio. Найти ее достаточно просто для этого всего лишь нужна Android Studio. Итак, начнем:

  1. Создаем новый проект в Android Studio с помощью File -> New -> New Project;
    Поскольку все, для чего нам понадобится этот проект — это использование Vector Asset Studio, то его можно создать пустым.
  2. Правый клик на папке drawable -> New -> Vector Asset;

    Это откроет Asset Studio. Она предоставляет возможность конвертировать иконки из Material Design, а также файлы SVG и PSD, в xml-файлы. Таким образом мы сможем использовать их в приложении.
  3. Окно Asset Studio

    Здесь мы выбираем, что именно нужно сделать, размер итогового изображения и настраиваем прозрачность.
  4. По нажатию на кнопку Next открывается окно сохранения итогового файла

    После его сохранения мы сможем использовать его в своем проекте Xamarin.Android.

Достаточно емкая подготовительная работа, требующая наличия установленной Android Studio. К сожалению, на момент написания статьи нам не удалось найти корректно работающих онлайн-конвертеров. Некоторые при попытке конвертации представленного файла SVG выдавали ошибку, некоторые указывали, что лучше воспользоваться Vector Asset Studio.


Как бы то ни было, мы получили требуемый файл и теперь можем использовать его в своем проекте. Не будем описывать процесс создания проекта Xamarin.Android, перейдем сразу к сути.
Первым делом мы поместили полученный файл в папку drawable проекта, затем в качестве демонстрации работы с этим файлом, создали 3 ImageView на экране.

код здесь
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android        ="http://schemas.android.com/apk/res/android"
	xmlns:app            ="http://schemas.android.com/apk/res-auto"
	android:layout_width ="match_parent"
	android:layout_height="match_parent"
	android:minWidth="25px"
	android:minHeight="25px">
	<ImageView
		android:layout_width="150dp"
		android:layout_height="150dp"
		android:layout_alignParentLeft="true"
		android:id="@+id/imageView1"
		android:adjustViewBounds="true"
		app:srcCompat="@drawable/question_svg"
		android:background="@android:color/holo_red_dark"/>
	<ImageView
		android:layout_width="150dp"
		android:layout_height="150dp"
		android:layout_alignParentRight="true"
		android:id="@+id/imageView2"
		android:adjustViewBounds="true"
		app:src="@drawable/question"
		android:background="@android:color/holo_red_dark"/>
	<ImageView
		android:layout_width="150dp"
		android:layout_height="150dp"
		android:id="@+id/imageView3"
		android:adjustViewBounds="true"
		android:layout_marginTop="30dp"
		app:srcCompat="@drawable/question"
		android:layout_below="@id/imageView1"/>
</RelativeLayout>


В первом ImageView мы попытались присвоить оригинальный файл SVG в свойстве android:src, во втором — конвертированный XML-файл в том же свойстве, а в третьем — в свойстве app:srcCompat (обратите внимание, что это пространство имен необходимо прописать как указано в коде).
Результаты были заметны уже в дизайнере, до запуска приложения — единственно верный способ корректно отобразить наш файл, приведен в третьем ImageView.


Теперь необходимо убедиться, что изображение отображается корректно на разных устройствах. Для сравнения мы выбрали Huawei Honor 20 Pro и Nexus 5. А также, вместо некорректных способов отображения указали нужный и изменили размеры ImageView на 150х150 dp, 200x200 dp и 300х300 dp.

Результаты работы

Huawei Honor 20 Pro



Nexus 5




Как видно, качество изображений одинаковое, а значит, мы добились нужного результата.


Полученный файл используется из кода так же, как и обычно
image.SetImageResource(Resource.Drawable.<имя файла>);

Работа с форматом SVG в Xamarin.iOS


В случае с Xamarin.iOS дело обстоит так же, как и с Xamarin.Android, в том смысле, что “из коробки” работа с SVG-файлами не поддерживается. Однако, для ее реализации не требуется особых усилий. В случае нативной iOS-разработки для использования SVG требуется подключить SVGKit. Он позволяет обрабатывать такие файлы, не прибегая к сторонним решениям. В случае с Xamarin.iOS мы можем использовать библиотеку SVGKit.Binding. Это С# обертка над оригинальной SVGKit.


Все, что нам потребуется это подключить NuGet-пакет в наш проект. На момент написания статьи последняя версия — 1.0.4.


К сожалению, судя по всему, поддержка этой библиотеки прекращена, но для использования в проекте она вполне годится.

Здесь реализован класс SVGKImage (собственно SVG-изображение) и SVGKFastImageView (контрол для отображения таких изображений).

код для использования
void CreateControls()
{
	var image_bundle_resource = new SVGKImage("SVGImages/question.svg");
	img1 = new SVGKFastImageView(image_bundle_resource);
	Add(img1);
	img1.Frame = new CGRect(View.Frame.Width / 4, 50, View.Frame.Width / 2, View.Frame.Width / 2);

	var image_content = new SVGKImage(Path.Combine(NSBundle.MainBundle.BundlePath, "SVGImages/question1.svg"));
	img2 = new SVGKFastImageView(image_content);
	Add(img2);
	img2.Frame = new CGRect(5, img1.Frame.Bottom + 5, View.Frame.Width - 5, View.Frame.Width - 5);
}

Для создания SVGKImage в библиотеке реализовано два варианта в зависимости от BuildAction файла — BundleResource (в коде обозначен image_bundle_resource) и Content (в коде обозначен image_content).
К сожалению, в библиотеке есть ряд недостатков:

  1. У SVGKFastImageView не поддерживается инициализация без предоставления SVGKImage — выдается ошибка, указывающая на необходимость использования приведенного в коде конструктора;
  2. Нет возможности использования SVG-файлов в других контролах. Однако, если в этом нет необходимости, то использовать ее можно.

Итог работы программы


В случае если для приложения критично использование SVG-файлов не только в ImageView, можно воспользоваться другими библиотеками. Например, FFImageLoading. Это мощная библиотека, позволяющая не только выгружать SVG-изображения в родной для Xamarin.iOS UIImage, но и делать это напрямую в нужный контрол, экономя время на разработку. Также имеется функционал для загрузки изображений из интернета, но рассматривать его в этой статье мы не будем.


Для использования библиотеки понадобится подключить в проект два NuGet-пакета — Xamarin.FFImageLoading и Xamarin.FFImageLoading.SVG.


В тестовом приложении мы использовали метод, загружающий SVG-изображение в UIImageView напрямую и выгружающий изображение в родной UIImage.

Пример кода
private async Task CreateControls()
{
    img1 = new UIImageView();
    Add(img1);
    img1.Frame = new CGRect(View.Frame.Width / 4, 50, View.Frame.Width/2, View.Frame.Width/2);

    ImageService.Instance
                .LoadFile("SVGImages/question.svg")
                .WithCustomDataResolver(new SvgDataResolver((int)img1.Frame.Width, 0, true))
                .Into(img1);

    var button = new UIButton() { BackgroundColor = UIColor.Red};
    Add(button);
    button.Frame = new CGRect(View.Frame.Width/2 - 25, img1.Frame.Bottom + 20, 50, 50);

    UIImage imageSVG = await ImageService.Instance
                .LoadFile("SVGImages/question.svg")
                .WithCustomDataResolver(new SvgDataResolver((int)View.Frame.Width, 0, true))
                .AsUIImageAsync();
    if(imageSVG != null)
        button.SetBackgroundImage(imageSVG, UIControlState.Normal);
}


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

Пример кода
    var svgString = @"<svg><rect width=""30"" height=""30"" style=""fill:blue"" /></svg>";

    UIImage img = await ImageService.Instance
		.LoadString(svgString)
		.WithCustomDataResolver(new SvgDataResolver(64, 0, true))
		.AsUIImageAsync();


В библиотеке также имеется еще ряд функционала, однако рассматривать его здесь мы не будем. Единственное, о чем действительно стоит упомянуть, данная библиотека может быть использована и в Xamarin.Android проектах. Для использования в проектах Xamarin.Forms есть аналоги этой библиотеки, о работе с которыми мы расскажем далее.

Работа с форматом SVG в Xamarin.Forms


По большому счету необязательно искать библиотеки для форм, можно просто подключить известные в проекты платформ и долго, и муторно переписывать рендеры для каждого контрола, которым вы захотите воспользоваться. К счастью, есть решение, о котором мы расскажем.


Это уже описанные библиотеки Xamarin.FFImageLoading и Xamarin.FFImageLoading.SVG, точнее их вариант для Xamarin.Forms (Xamarin.FFImageLoading.Forms и Xamarin.FFImageLoading.SVG.Forms). Как при работе с большинством библиотек в Xamarin.Forms перед тем, как ее использовать, необходима небольшая стартовая настройка проекта:

  1. Во все проекты (PCL и проекты платформ) необходимо добавить два NuGet-пакета — Xamarin.FFImageLoading.Forms и Xamarin.FFImageLoading.SVG.Forms;
  2. Прописать в AppDelegate.cs iOS-проекта:
    public override bool FinishedLaunching( UIApplication app, NSDictionary options )
    {
        global::Xamarin.Forms.Forms.Init();
        FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
        CachedImageRenderer.InitImageSourceHandler();
        var ignore = typeof(SvgCachedImage);
        LoadApplication(new App());
        return base.FinishedLaunching(app, options);
    }
  3. Прописать в MainActivity.cs Android-проекта:
    protected override void OnCreate( Bundle savedInstanceState )
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;
        base.OnCreate(savedInstanceState);
    
        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
        CachedImageRenderer.InitImageViewHandler();
        var ignore = typeof(SvgCachedImage);
        LoadApplication(new App());
    }
    
После этого библиотекой можно пользоваться.
В библиотеке реализованы обращения к SVG-ресурсам, находящимся как в платформенных проектах, так и EmbeddedResouce PCL проекта. Для отображения SVG-картинок используется SvgCachedImage. В отличие от оригинального контрола Image, использующегося в Xamarin.Forms и принимающего в себя ImageSource, SvgCachedImage работает с SvgImageSource.

ВАЖНО!
Перед использованием пространства имен svg, показанного далее, необходимо его объявить:

xmlns:svg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"


Предоставить его можно несколькими способами:

  1. Указать в XAML-файле имя SVG-файла, находящегося в ресурсах проектов платформ:
    <svg:SvgCachedImage
                Source="question.svg"
                WidthRequest="100"
                HeightRequest="100"/>
    
  2. Указать в XAML-файле полный путь к файлу SVG, являющегося встроенным ресурсом PCL-проекта:

    <svg:SvgCachedImage
                Source="resource://SVGFormsTest.SVG.question1.svg"
                WidthRequest="100"
                HeightRequest="100"/>
    

    При загрузке из встроенных ресурсов можно указать имя другой сборки:

    resource://SVGFormsTest.SVG.question1.svg?assembly=[ASSEMBLY FULL NAME]
    
  3. При работе из кода сначала даем контролы имя:

    <svg:SvgCachedImage
                x:Name="image"
                WidthRequest="100"
                HeightRequest="100"/>
    

    Затем, в зависимости от того какой ресурс используется, выбираем один из вариантов:

    • для использования встроенных ресурсов
      image.Source = SvgImageSource.FromResource("SVGFormsTest.SVG.question1.svg");

    • для использования платформенных ресурсов

      image.Source = SvgImageSource.FromFile("question.svg");

  4. С помощью Binding есть 2 варианта:

    • хранить в переменной модели, на которую осуществлена привязка самого SvgImageSource. В этом случае со стороны кода ничего не изменяется, а в XAML-файле остается стандартная привязка:

      <svg:SvgCachedImage
                  Source="{Binding Source}"
                  WidthRequest="100"
                  HeightRequest="100"/>
      
    • хранить в переменной модели строку с путем к файлу либо ко встроенному ресурсу. В этом случае необходимо использование конвертера, предоставляемого библиотекой. Для этого его нужно добавить в ресурсы страницы:

      <ContentPage.Resources>
              <ResourceDictionary>
                  <svg:SvgImageSourceConverter
                      x:Key="SourceConverter"/>
              </ResourceDictionary>
          </ContentPage.Resources>
      

      затем указать необходимость его использования при привязке:

      <svg:SvgCachedImage
                  Source="{Binding Source1, Converter={StaticResource SourceConverter}}"
                  WidthRequest="100"
                  HeightRequest="100"/>
      

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

      public MainVM()
      {
          source1 = "question.svg";
      }
      
      string source1;
      public string Source1
      {
          get => source1;
          set
          {
              source1 = value;
          }
      }
      

      или путь к данным встроенного ресурса:

      public MainVM()
      {
          source1 = "resource://SVGFormsTest.SVG.question1.svg";
      }
      
      string source1;
      public string Source1
      {
          get => source1;
          set
          {
              source1 = value;
          }
      }
      

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

Недостатки и исключения


Недостатки использования SVG-ресурсов в проектах, прямо вытекают из недостатков самого формата. К примеру, если графическая составляющая вашего приложения очень сложна и в ней должны использоваться изображения с множеством мелких деталей, градиентом, большим количеством цветовых переходов, и таких изображений не одно и не два, то использовать их, как SVG-ресурсы может быть невыгодно. Кроме того, PNG-файлы готовы к отображению быстрее, чем SVG, поэтому для использования на экране загрузки приложения подходят больше.


Еще у SVG-ресурсов есть незначительный минус. Их нельзя предоставить в качестве иконки приложения, так как в соответствии с гайд-лайнами Google и Apple в качестве иконки могут использоваться только PNG и JPEG файлы.