При разработке мобильного приложения есть масса моментов, на которые необходимо обращать внимание. Это и выбор технологии, на которой оно будет написано, и разработка архитектуры приложения, и, собственно, написание кода. Рано или поздно наступает момент, когда костяк приложения есть, вся логика прописана и приложение , в общем-то, работает, но… нет внешнего вида. Тут стоит задуматься о графических ресурсах, которые будут использованы, поскольку графика составляет львиную долю размера итоговой сборки, будь то .apk на Android или .ipa на iOS. Сборки огромных размеров в принципе ожидаемы для мобильных игр, уже сейчас из PlayMarket порой приходится загружать объемы данных вплоть до 2 Гб и хорошо, если во время загрузки есть возможность подключиться к Wi-Fi или мобильный оператор предоставляет скоростное безлимитное подключение. Но для игр это ожидаемо, а бизнес-приложение, обладающее таким размером, невольно вызывает вопрос “Откуда столько?”. Одной из причин большого размера сборки бизнес-приложения может стать значительное количество иконок и картинок, которые в нем приходится отображать. А также не следует забывать о том, что большое количество графики пропорционально влияет на быстродействие приложения.
При создании графической составляющей приложения часто возникает серьезная проблема. Мобильных устройств существует великое множество начиная с часов и заканчивая планшетами, и разрешения их экранов очень разнятся. Из-за этого зачастую приходится включать в сборку графические ресурсы отдельными файлами для каждого из существующих типов. По 5 копий для Android и по 3 для iOS. Это существенно влияет на размер итоговой сборки, которую Вы будете выкладывать в сторы.
О том, что можно сделать для того, чтобы не попасть в такую ситуацию, мы расскажем в этой статье.
Сравнение форматов PNG и SVG
Основное различие между форматами PNG и SVG заключается в том, что PNG — формат растровой графики, а SVG — векторной.
Растровое изображение представляет собой сетку пикселей на мониторе и используется повсеместно, будь то небольшие иконки или огромные баннеры. Среди преимуществ этого типа графики также следует отметить следующее:
- Растровая графика позволяет создавать рисунки практически любой сложности, без ощутимых потерь в размере файла;
- Если не требуется масштабирование изображения, то скорость обработки сложных изображений весьма высока;
- Растровое представление изображений естественно для большинства устройств ввода-вывода.
Однако, есть и минусы.
Например, растровое изображение невозможно идеально масштабировать. При увеличении небольшой картинки изображение «мылится» и видно пиксели из которых оно состоит.
Также, простые изображения, состоящие из большого количества точек — имеют большой размер. В связи с этим простые рисунки рекомендуется хранить в векторном формате. Все вышеперечисленное справедливо как для формата PNG, так и для любого другого формата растровой графики.
Формат SVG, в свою очередь, является не совсем изображением. Согласно статье на википедии SVG является языком разметки масштабируемой векторной графики, что позволяет читать и при необходимости редактировать файл.
Также, будучи языком разметки, SVG позволяет внутри документа применять фильтры (например, размытие, выдавливание и т.д.). Они объявляются тегами, за визуализацию которых отвечает средство просмотра, а значит, они не влияют на размер исходного файла.
Помимо перечисленного, формат SVG имеет еще ряд преимуществ:
- Будучи векторным форматом, SVG позволяет масштабировать любую часть изображения без потерь в качестве;
- В SVG-документ можно вставлять растровую графику;
- Легко интегрируется с HTML- и XHTML-документами.
Однако, есть и недостатки данного формата:
- Чем больше в изображении мелких деталей, тем быстрее растет размер SVG-данных. В ряде случаев, SVG не только не дает преимуществ, но и проигрывает растру;
- Сложность использования в картографических приложениях, поскольку для корректного отображения малой части изображения, требуется прочитать документ целиком.
В случае с разработкой мобильных приложений на платформе Xamarin есть еще один недостаток.
Для корректной работы с этим форматом необходимо подключать дополнительные библиотеки, либо искать обходные пути.
Работа с форматом SVG в Xamarin.Android
Как уже было сказано выше, Xamarin “из коробки” не поддерживает работу с форматом SVG. Чтобы решить эту проблему – можно использовать сторонние библиотеки или VectorDrawable. В последнее время разработчики все чаще отдают предпочтение последним, так как большая часть библиотек для Xamarin ориентирована на кроссплатформенное использование в Xamarin.Forms, а решения для нативного андроида либо больше не поддерживаются их разработчиками и устарели, либо возникают серьезные проблемы при попытке создать на их основе библиотеку для Xamarin. В связи с этим здесь рассмотрим использование VectorDrawable.
Для реализации данного подхода потребуется использовать Vector Asset Studio. Найти ее достаточно просто для этого всего лишь нужна Android Studio. Итак, начнем:
- Создаем новый проект в Android Studio с помощью File -> New -> New Project;
Поскольку все, для чего нам понадобится этот проект — это использование Vector Asset Studio, то его можно создать пустым. - Правый клик на папке drawable -> New -> Vector Asset;
Это откроет Asset Studio. Она предоставляет возможность конвертировать иконки из Material Design, а также файлы SVG и PSD, в xml-файлы. Таким образом мы сможем использовать их в приложении. - Окно Asset Studio
Здесь мы выбираем, что именно нужно сделать, размер итогового изображения и настраиваем прозрачность. - По нажатию на кнопку 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);
}
К сожалению, в библиотеке есть ряд недостатков:
- У SVGKFastImageView не поддерживается инициализация без предоставления SVGKImage — выдается ошибка, указывающая на необходимость использования приведенного в коде конструктора;
- Нет возможности использования 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 перед тем, как ее использовать, необходима небольшая стартовая настройка проекта:
- Во все проекты (PCL и проекты платформ) необходимо добавить два NuGet-пакета — Xamarin.FFImageLoading.Forms и Xamarin.FFImageLoading.SVG.Forms;
- Прописать в 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); }
- Прописать в 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.
xmlns:svg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"
Предоставить его можно несколькими способами:
- Указать в XAML-файле имя SVG-файла, находящегося в ресурсах проектов платформ:
<svg:SvgCachedImage Source="question.svg" WidthRequest="100" HeightRequest="100"/>
- Указать в XAML-файле полный путь к файлу SVG, являющегося встроенным ресурсом PCL-проекта:
<svg:SvgCachedImage Source="resource://SVGFormsTest.SVG.question1.svg" WidthRequest="100" HeightRequest="100"/>
При загрузке из встроенных ресурсов можно указать имя другой сборки:
resource://SVGFormsTest.SVG.question1.svg?assembly=[ASSEMBLY FULL NAME]
- При работе из кода сначала даем контролы имя:
<svg:SvgCachedImage x:Name="image" WidthRequest="100" HeightRequest="100"/>
Затем, в зависимости от того какой ресурс используется, выбираем один из вариантов:
- для использования встроенных ресурсов
image.Source = SvgImageSource.FromResource("SVGFormsTest.SVG.question1.svg");
- для использования платформенных ресурсов
image.Source = SvgImageSource.FromFile("question.svg");
- для использования встроенных ресурсов
- С помощью 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; } }
Однако, при предоставлении пути к файлу встроенного ресурса в качестве строки конвертер не срабатывает и изображение не загружается. Разумеется, можно создать свой конвертер, наследовать его от предоставленного библиотекой и обработать этот случай.
- хранить в переменной модели, на которую осуществлена привязка самого SvgImageSource. В этом случае со стороны кода ничего не изменяется, а в XAML-файле остается стандартная привязка:
Недостатки и исключения
Недостатки использования SVG-ресурсов в проектах, прямо вытекают из недостатков самого формата. К примеру, если графическая составляющая вашего приложения очень сложна и в ней должны использоваться изображения с множеством мелких деталей, градиентом, большим количеством цветовых переходов, и таких изображений не одно и не два, то использовать их, как SVG-ресурсы может быть невыгодно. Кроме того, PNG-файлы готовы к отображению быстрее, чем SVG, поэтому для использования на экране загрузки приложения подходят больше.
Еще у SVG-ресурсов есть незначительный минус. Их нельзя предоставить в качестве иконки приложения, так как в соответствии с гайд-лайнами Google и Apple в качестве иконки могут использоваться только PNG и JPEG файлы.
AlexNovikovAlex
Я бы и рад вам лайк поставить, но к сожалению не хватает рейтинга.