Как сделать переход между страницами xamarin

Обновлено: 04.07.2024

Как вы переключаетесь между страницами в формах Xamarin? Моя главная страница - ContentPage, и я не хочу переключаться на что-то вроде страницы с вкладками.

Я смог сделать это, найдя родителей элементов управления, которые должны запускать новую страницу, пока я не найду ContentPage, а затем не заменим Контент элементами управления для новой страницы. Но это кажется действительно неаккуратным.

Xamarin.Forms поддерживает несколько встроенных хостов навигации:

  • NavigationPage где скользит следующая страница,
  • TabbedPage тот, который тебе не нравится
  • CarouselPage , что позволяет переключаться влево и вправо на следующие / предыдущие страницы.

Кроме того, все страницы также поддерживают PushModalAsync() который просто выдвигает новую страницу поверх существующей.

В самом конце, если вы хотите убедиться, что пользователь не может вернуться на предыдущую страницу (с помощью жеста или кнопки возврата оборудования), вы можете оставить то же самое. Page отображается и заменить его Content ,

Предложенные варианты замены корневой страницы также работают, но вам придется обращаться с этим по-разному для каждой платформы.

В классе App вы можете установить MainPage на страницу навигации и установить корневую страницу для вашей ContentPage:

Затем в вашем первом вызове ContentPage:

Если ваш проект был настроен как проект форм PCL (и, скорее всего, как Shared Forms, но я этого не пробовал), существует класс App.cs, который выглядит следующим образом:

Вы можете изменить GetMainPage метод для возврата нового TabbedPaged или какой-либо другой страницы, которую вы определили в проекте

Оттуда вы можете добавить команды или обработчики событий для выполнения кода и сделать

Вставьте новую страницу в стек, затем удалите текущую страницу. Это приводит к переключению.

Сначала вы должны быть на навигационной странице:

Переключение контента не является идеальным, поскольку у вас есть только одна большая страница и один набор событий страницы, например, OnAppearing ect.

Если вы не хотите переходить на предыдущую страницу, т.е. не позволяете пользователю вернуться к экрану входа в систему после завершения авторизации, вы можете использовать;

Если вы хотите включить обратно функциональность, просто используйте

Похоже, что эта тема очень популярна, и будет грустно не упомянуть здесь, что есть альтернативный путь - ViewModel First Navigation , Большинство фреймворков MVVM там используют его, однако, если вы хотите понять, о чем это, продолжайте чтение.

Вся официальная документация Xamarin.Forms демонстрирует простое, но немного не MVVM-решение. Это потому что Page (Просмотр) ничего не должен знать о ViewModel и наоборот. Вот отличный пример этого нарушения:

Если у вас есть приложение на 2 страницы, этот подход может быть вам полезен. Однако, если вы работаете над решением для большого предприятия, вам лучше пойти с ViewModel First Navigation подход. Это немного более сложный, но гораздо более чистый подход, который позволяет вам перемещаться между ViewModels вместо навигации между Pages (Просмотры). Одним из преимуществ четкого разделения проблем является то, что вы можете легко передавать параметры следующему ViewModel или выполнить асинхронный код инициализации сразу после навигации. Теперь к деталям.

(Я постараюсь максимально упростить все примеры кода).

1. Прежде всего нам нужно место, где мы могли бы зарегистрировать все наши объекты и опционально определить их время жизни. Для этого мы можем использовать контейнер IOC, вы можете выбрать один самостоятельно. В этом примере я буду использовать Autofac (это один из самых быстрых доступных). Мы можем сохранить ссылку на это в App поэтому он будет доступен глобально (не очень хорошая идея, но необходимая для упрощения):

3. Для установки корневой страницы нам понадобится ViewModelLocator это установит BindingContext автоматически:

4.Наконец нам понадобится NavigationService это поддержит ViewModel First Navigation подход:

Как вы можете видеть, есть BaseViewModel - абстрактный базовый класс для всех ViewModels где вы можете определить методы, такие как InitializeAsync это будет выполнено сразу после навигации. А вот пример навигации:

Как вы понимаете, этот подход сложнее, сложнее в отладке и может привести к путанице. Однако есть много преимуществ, и вам не нужно реализовывать это самостоятельно, поскольку большинство сред MVVM поддерживают его "из коробки". Пример кода, который демонстрируется здесь, доступен на github.

Александр Алексеев

Правильный набор инструментов пользовательского интерфейса также может помочь дополнять Ваше приложение. При разработке нового приложения Xamarin.Forms ознакомьтесь с отполированными элементами управления, поставляемыми с Telerik UI для Xamarin. Если Вы еще не пользуетесь Telerik UI, всегда есть возможность ознакомиться с бесплатной версией. В любом случае Вы ведь ничего не потеряете.

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

1. Навигация по страницам

Почти каждое мобильное приложение представляет собой набор страниц, и навигация по ним является тем, из чего состоит опыт пользователя. Приложения Xamarin.Forms не являются исключением, и, к счастью, здесь есть простая встроенная модель навигации. Разработчики сшивают вместе стеки страниц, которые следуют за шаблоном Last-in-First-out (LIFO). Переход от одной страницы к другой помещает страницу в стек, а возвращение на предыдущую страницу выводит ее из стека. Все просто.

Для того чтобы использовать встроенную модель навигации Xamarin.Forms нужен всего лишь воспользоваться классом NavigationPage, который автоматически управляет стеком. С помощью класса NavigationPage добавьте панель навигации в верхнюю часть страницы, при необходимости заполните заголовок и значки. Кроме того, пользователи при переходе видят кнопку обратной навигации, которая настроена для каждой мобильной платформы.

Предпочтительно использовать класс NavigationPage с первой страницы Вашего приложения, которая определена в AppStart.cs в общем коде PCL. Вот так:

Итак, скажем, у меня есть приложение Xamarin.Forms, MainPage со следующей компоновкой:

и теперь после того, как пользователь нажимает на Button1 , я хочу, чтобы страница перешла к следующему макету:

Сложная часть заключается в том, что я хочу приятный анимированный переход, где:

  • Кнопки 3 и 4 сдвигаются с крайнего правого
  • Кнопки 1 и 2 перемещаются и масштабируются с легкостью в исходное положение слева.

Есть ли способ реализовать такое поведение в xamarin.forms xaml?

Единственный подход, который я могу сейчас придумать, – использовать AbsoluteLayout и установить абсолютные значения в пикселях для начальных позиций кнопки (или их контейнеров). И сделать переход, вызвав для каждого элемента TranslateTo и ScaleTo методы с соответствующими параметрами для установки целевого макета.

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

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

Наш первый опыт разработки на Xamarin: трудности и их решения

Продолжаем делиться с вами нашим опытом освоения Xamarin!

Xamarin application by Noveo

Навигация

Первое, с чем мы столкнулись, – навигация.

По дизайну главная страница нашего приложения – это Tabbed page, страница с вкладками. Первая страница – список сотрудников с возможностью переключения в режим “Только фото” – сетку с более крупными фотографиями. Содержимое второй вкладки – группированный по дате список новостей компании (дни рождения, новые сотрудники, “happy client’s letter” и др.). Последняя вкладка – личная информация пользователя приложения.



При этом с каждой вкладки возможна навигация на страницу деталей/редактирования. Проблема в том, что на всех платформах реализация стандартной Xamarin.Forms-страницы TabbedPage различается. Это, конечно, правильно, ведь нативные “вкладки” на iOS – это панель Tab Bar, которая располагается в нижней части экрана, на Android — это Tab Layout в верхней части экрана (при этом над самими вкладками может располагаться App Bar с дополнительными элементами управления/навигации), а на UWP – это Pivot контрол:

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/tabbed-page

Естественно, в нашем случае было бы более корректно использовать модель навигации на совершенно новую страницу “поверх вкладок”. К сожалению, на момент начала разработки приложения был только один способ – Modal Pages. Modal Pages – это страницы, которые содержат в себе некую автономную задачу, только по завершении которой можно перейти с этой страницы на другую.

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/modal

https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/navigation/modal

Да, такая концепция немного не соответствует нашим задачам. Тем более что, применяя этот подход, нам пришлось бы реализовывать нативные элементы управления навигацией на каждой такой странице вручную. Поэтому было решено оставить страницы деталей/редактирования внутри табов.

Здесь важно сделать вывод, который скорее относится не к Xamarin, а к кросс-платформенной разработке в общем: самое важное – это момент дизайна и проектирования приложения. Нужно проектировать приложения с учетом особенностей всех платформ, при этом необходимы не только знания UX для каждой платформы, но и возможности фреймворка, с помощью которого будет реализовано приложение.

В нашем случае были только макеты для Android-приложения. Столкнувшись с описанной выше проблемой, мы сразу поняли, что, используя Xamarin.Forms, следовать им в полной точности не получится, и мы будем делать упор на максимальную “UI-нативность” на всех платформах. Признаемся, это довольно-таки увлекательное занятие – часы обсуждений и рисования макетов на доске. С точки зрения outsource-разработки это, конечно, может вызвать некоторые трудности, но, к счастью, этот проект – внутренний, и у нас была полная свобода действий.

ListView & GridView

Первая вкладка нашего приложения, которая встречает пользователя при запуске приложения, – это список сотрудников компании. Его особенность заключается в том, что над ним расположен переключатель (switch), который превращает его из обычного списка в сетку (gridview) с более крупными фотографиями и дополнительными интерактивными элементами:

Xamarin application Noveo


И, как ни удивительно, в Xamarin.Forms нет привычного для UWP GridView. В принципе это понятно – GridView достаточно большой, тяжелый элемент с огромным количеством нюансов. Тем более, на iOS нет как такового GridView, и реализовать полноценный универсальный контрол для всех платформ достаточно проблематично. Хотя есть несколько сторонних библиотек с более-менее удовлетворительной реализацией… но со своими проблемами. У одной, например, не было реализации под UWP.

Сначала решили попробовать легкую реализацию – FlowListView. Данный контрол реализован на чистом Xamarin.Forms и представляет из себя обычный ListView, который сам рассчитывает и добавляет элементы внутрь строк списка. С его помощью мы быстро и легко реализовали прототип списка с динамической подменой шаблона (смена режимов просмотра). Мы протестировали его на эмуляторах/симуляторах, и поначалу ничего не предвещало проблем. Да, были лаги при скроллинге, но мы были уверены, что все можно оптимизировать.

Вообще, судя по многочисленным статьям и вопросам на StackOverflow, проблема скроллинга длинных списков Xamarin.Forms – одна из самых болезненных. В нашем случае наибольшая просадка производительности оказалась оказалась вызвана картинками (фото сотрудников) в ячейках. Попробовали заменить стандартный Image на реализацию FFImageLoading. Она позволяет производить фоновую загрузку изображений, управлять ошибками загрузки, добавлять placeholder, более гибко управлять кэшированием картинок, обрезать картинки до круга и многое другое. С её помощью производительность выросла, но недостаточно. Кроме того, наши коллеги из iOS-отдела потом подсказали, что на этой платформе обрезание картинок до круга – очень трудозатратная операция, и лучший вариант – использование круглых масок.

Следующим шагом оптимизации списка было применение механизма переиспользования ячеек. Стандартный ListView и его обертка FlowListView имеют встроенную реализацию повторного использования созданных ячеек. Реализован он, естественно, на уровне каждой из платформ отдельно нативными средствами. Благодаря ему ячейки не создаются каждый раз при необходимости их отображения. Подключили – на эмуляторах работало хорошо, на девайсах терпимо, но хотелось лучше.

Затем нужно было добавить анимацию выезжающих панелей у элементов режима “Только фото”. И тут проявилась несовместимость FlowListView и переиспользования ячеек в нашем случае. При повторном использовании открытые панели оставались открытыми у ячеек, для которых не была произведена анимация. Кроме того, список просел настолько, что уже не оставалось выбора, как реализовать нативные рендереры.

До этого нам не приходилось писать больших рендереров. Мы надеялись, что Xamarin.Forms максимально оградит нас от углубления в тонкости новых для нас платформ – iOS и Android. Но деваться было некуда.

Стоит отметить, что кастомные рендереры можно написать для любого UI-элемента в Xamarin.Forms. В случае со списком можно написать рендерер для ячейки (ViewCell) и/или для всего ListView. Нам же нужен был не список, а полноценный GridView. Поэтому в PCL был создан “голый” контрол с определенным набором пропертей, унаследованный от Xamarin.Forms.View, который на Android и UWP превратится [KC5] с помощью рендереров в GridView, а на iOS – в UICollectionView.

Результат – полностью нативные списки с плавным скроллингом на всех платформах.

Опишем реализацию немного подробнее.

В PCL, как обычно, нам нужен один класс:

Для iOS структура классов выглядит следующим образом:

EmployeesViewRenderer – сам рендерер:

Инициализация и кастомизация нативного элемента – как и раньше в переопределенном методе OnElementChanged. Для iOS в нашем случае он выглядит примерно так:

Метод SetNativeControl используется для выставления нативного элемента в рендерере (метод также присваивает переданный объект в свойство Control).

Как видно из метода OnElementChanged и сигнатуры самого EmployeesViewRenderer, EmployeesCollectionView – и есть наш нативный контрол, который является реализацией UIKit.UICollectionView:

Также необходимо реализовать собственный UICollectionViewSource:

public class EmployeesViewSource : UICollectionViewSource

В этом классе необходимо переопределить 2 метода:

  • RowsInSection – общее число строк коллекции.
  • GetCell – получение ячейки по заданному индексу. В нашем случае в зависимости от текущего режима возвращается переиспользуемая ячейка одного из двух типов.

Кроме того, для конфигурации коллекции в двух режимах нам потребовалось реализовать два класса, отвечающих за отображение ячеек:

и два класса, отвечающих за их размеры и расположение:

При смене режима коллекции происходит подмена делегата и перезагрузка всех данных UICollectionView для подмены ячеек:

Для Android потребовалось всего 3 сущности:

Как видно из сигнатуры класса, нативный элемент в случае с Android – SwipeRefreshLayout:

Поскольку наши списки должны поддерживать обновление данных по свайпу вниз, для Android наш класс рендерит не сам GridView, а GridView, завернутый в SwipeRefreshLayout. В iOS же за это поведение отвечает сам UICollectionView.

Смена режимов списка реализована следующим образом:

Как видно из кода, всё, что нужно для смены – пересчитать количество колонок и подменить адаптер. Адаптеров, как ни странно, у нас два:

В каждом необходимо переопределить следующие члены:

  • Count – общее число элементов коллекции;
  • GetItemId – id элемента коллекции (обычно – номер в коллекции);
  • this[int] – получение модели по номеру в коллекции;
  • GetView – получение заполненной данными ячейки коллекции.

Для UWP всё еще проще – всего 2 класса:

EmployeesViewRenderer рендерит нативный GridView. А для смены режимов достаточно подменить 3 свойства:

Эти свойства определены в ресурсах XAML части класса EmployeesGridView.

Единственный момент – у GridView в UWP нет возможности обновлять коллекцию по жесту из коробки, и обработку жестов для этих целей необходимо писать вручную. Мы выбрали более простой путь и добавили отдельную кнопку для обновления в нижний AppBar страницы:

Xamarin application by Noveo

Работа с изображениями

Само по себе управление отображением картинок в Xamarin и Xamarin.Forms не составляет большого труда. Как уже было описано раньше, очень сильно работу с ними упрощает библиотека FFImageLoading. Нативный процесс рендеринга на всех платформах отличается, и у каждой платформы свои механизмы его ускорения и управления отображением на разных разрешениях девайсов.

Всю основную работу с изображениями в Xamarin.Forms можно разбить на 4 категории:

1) Локальные изображения

При таком подходе изображения хранятся в платформо-зависимых проектах, так же, как они хранились бы в проектах на “родной” платформе. Используются механизмы управления нативными разрешениями, такими как iOS Retina или Android high-DPI версии изображений.

Например, для iOS мы использовали Asset Catalogs:



Для Android достаточно поместить изображения с соответствующими разрешениями в папки Resources/drawable, drawable-hdpi, drawable-xhdpi и др.

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

Такие изображения автоматически загружаются из сети по заданному URL. В PCL проекте для них достаточно передать в объект Image или CachedImage от FFImageLoading URL ресурса.

В нативных рендерерах свои механизмы: для iOS мы использовали нативную библиотеку SDWebImage, для Android – Square.Picasso.

4) Иконки приложений и SpashScreens.

С точки зрения разработки все просто – добавить нативными средствами различные картинки с нужными разрешениями.

Из-за необходимости 1го и 4го варианта изображений и таргетирования приложения под 3 платформы возникает “Ад картинок”. Одна и таже картинка может понадобиться в 10-15 различных вариантах, т.к. на каждой платформе свои требования и гайдлайны по разрешениям. Из-за этого возникает большая путаница и много рутинной работы.

На iOS итого возникла следующая ситуация:

Изначально, иконки приложений мы задавали в настройках проекта CompanyStaff.iOS.


После обновления очередной версии Xamarin.Forms иконки стали отображаться размыто. Решение – использовать Assets Catalogs. А там уже совсем другие разрешения:


И снова нужно нарезать новые картинки.

Нас спасли наши дизайнеры. Спасибо им огромное за оперативное “нарезание” тонны картинок по нашим запросам =)

Анимация

Xamarin.Forms включает в себя собственную инфраструктуру для реализации анимации. С её помощью можно легко создавать простые анимации (Translation, scale, rotation и др.) и их комбинации с возможностью задания переходных функций (easing functions).

Визуально Xamarin.Forms-анимация работает достаточно неплохо. Мы добавляли её в режим “Только фото” для выезжающих панелей на FlowListView, но, как было сказано раньше, пришлось заменить FlowListView на кастомные рендереры, и реализовать данные “открывашки” нативно.

На данный момент пример анимации, реализованной в PCL с помощью Xamarin.Forms, можно увидеть на странице Login при фокусе полей ввода.

Проблема такой анимации – невозможность сымитировать нативное поведение контролов для всех платформ одновременно. Например, простейший способ реализовать кликабельные элементы, которые содержат в себе только одно изображение, – добавить к картинке GestureRecognizer (название, видимо, позаимствовано у iOS). На всех форумах так и советуют. Но по дефолту при клике на картинку никакого “отклика” приложения на действие пользователя не происходит. Единственный способ реализовать нативное поведение – кастомные рендереры. Конечно, правильным подходом в данном случае было бы использовать кнопку, если бы не одно но – в Xamarin.Forms оказалось не так просто поместить картинку внутрь кнопки. Основная проблема – невозможность задать размер изображения внутри. Кроме того, стандартные рендеры по-разному отображают кнопки (добавляются дополнительные margins и paddings, расположение картинок и текста отличается). Это относится не только к кнопкам, но и к другим стандартным контролам. Для каждого элемента нужно искать свои пути решения.

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

Клавиатура

Про работу с экранной клавиатурой можно рассказывать много и долго. С ней возникают проблемы, вызванные не столько Xamarin.Forms, сколько самими платформами. В двух словах: их приходится решать теми же workaround-ами, что и при нативной разработке приложений, на каждой отдельной платформе.

Нативные библиотеки

На сегодняшний момент невозможно представить какое-либо внятное приложение, при написании которого не использовались бы сторонние компоненты. Под Xamarin, к счастью, уже реализовано множество таких компонентов, которые легко подключить в проект с помощью Nuget package manager. Здесь также есть небольшая проблема: эти компоненты обновляются немного позже, чем обновляются “оригиналы”.

Естественно, что нативных библиотек намного больше. Для случаев, когда без таких библиотек обойтись невозможно, в Xamarin есть механизмы подключения бинарных библиотек, написанных на Objective-C или Java. Для этого придется написать специальные биндинг-проекты для связи с Xamarin. Есть специальные утилиты для автоматизации этого процесса, но сами мы их не использовали, и, судя по отзывам, нет гарантии 100% точности.

Выводы

На текущий момент Xamarin, на наш взгляд, — довольно мощная технология для разработки мобильных приложений и является одним из лучших кроссплатформенных решений по совокупности факторов (производительность, процент общего кода, “нативность” и др.). Особенно радует тот факт, что разработчики самой платформы активно её развивают и улучшают, появляется множество новых внешних компонентов. Об этом говорит и рост (ну или стабильность :) ) интересующихся Xamarin:


И, как видно из графика, большой пик произошел в марте-апреле 2016 — когда Microsoft сделал Xamarin бесплатным. И это, конечно же, тоже круто.

Кроме того, не стоит думать, что Xamarin.Forms в действительности избавит вас от реализации платформо-специфичного кода хотя бы на 80%. Конечно, он идеально подходит для приложений с простым UI. С его помощью в очень короткие сроки можно реализовать макет приложения под несколько платформ. Но в дальнейшем на любое коммерческое приложение придется реализовывать кастомные рендереры для каждой ОС. Много рендереров. По крайней мере, пока.

В первой версии CS вышло примерно 65% общего кода. На наш взгляд, довольно неплохо. Производительность и внешний вид приложения при этом мало уступают “нативным” приложениям. Учитывая, что это наш первый опыт разработки на Xamarin, этот опыт можно считать крайне положительным. Темпы развития фреймворка дают явно понять, что технология никуда не пропадет, а будет лишь укрепляться — не только в сфере кроссплатформенной разработки, но и в мобильной в целом. Приятное ли впечатление осталось от этого опыта, и хотелось бы продолжать работать с Xamarin? Однозначно – да.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Читайте также: