В конце мая Microsoft выпустила в релиз Xamarin.Forms Shell — оболочку нацеленную на упрощение создания кроссплатформенных мобильных приложений и включающий в себя следующий функционал: боковое меню, вкладки, навигация, поиск.
Давайте начнем с создания пустого проекта Xamarin.Forms в Visual Studio 2019. Обратите внимание, на данный момент Shell официально поддерживает только 2 платформы: iOS и Android, UWP еще в стадии разработки. Рекомендую сразу же обновить все nuget пакеты в решении.
Далее создадим производный от Shell класс AppShell, для этого в добавим XAML-файл в общий проект со следующим содержимым:
AppShell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="HelloShell.AppShell">
</Shell>
AppShell.xaml.cs
namespace HelloShell
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}
после чего в файле App.xaml.cs указываем что в качестве MainPage у нас будет выступать AppShell:
public App()
{
InitializeComponent();
//MainPage = new MainPage();
MainPage = new AppShell();
}
и пару ContentPage страниц: Page1 и Page2. Так же в нашем тестовом приложении будут использоваться изображения, поэтому добавим их в платформозависимые проекты, для андройд в папку Resources=>drawable, а для ios в папку Resources.
Боковое меню
Боковое меню (часто его называют гамбургер меню) представляет из себя выезжающее меню, которое можно вызвать по нажатию на кнопку или специальным жестом и включает в себя заголовок (Header), список страниц (Flyout Items) и меню (Flyout Menu)
AppShell.xaml
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:HelloShell"
x:Class="HelloShell.AppShell">
<Shell.FlyoutHeader>
<StackLayout BackgroundColor="White" Padding="10">
<Image HeightRequest="100" Source="xamarin.png" />
<Label Text="Header" />
<Label Text="Привет!"/>
</StackLayout>
</Shell.FlyoutHeader>
<FlyoutItem Title="MainPage" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:MainPage}"/>
</FlyoutItem>
<FlyoutItem Title="Page1" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:Page1}"/>
</FlyoutItem>
<FlyoutItem Title="Page2" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:Page2}"/>
</FlyoutItem>
<MenuItem Clicked="MenuItem_Clicked" Text="Меню" IconImageSource="item.png" />
</Shell>
AppShell.xaml.cs
namespace HelloShell
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
private async void MenuItem_Clicked(object sender, System.EventArgs e)
{
await DisplayAlert("","Привет!","OK");
}
}
}
Вкладки
Xamarin.Forms Shell в качестве корневого шаблона может поддерживать нижние и верхние вкладки, а так же их комбинацию:
AppShell.xaml.cs
<?xml version="1.0" encoding="utf-8" ?>
<Shell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:HelloShell"
x:Class="HelloShell.AppShell">
<TabBar>
<Tab Title="MainPage" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:MainPage}" />
</Tab>
<Tab Title="Page1" Icon="xamarin.png">
<ShellContent Title="Main Page"
Icon="xamarin.png"
ContentTemplate="{DataTemplate pages:MainPage}" />
<ShellContent Title="Page2"
Icon="xamarin.png"
ContentTemplate="{DataTemplate pages:Page2}" />
</Tab>
<Tab IsEnabled="False" Title="Page2" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:Page2}" />
</Tab>
</TabBar>
</Shell>
Как видите, сделать это довольно просто. Еще одним преимуществом является эффективная загрузка страниц, которая позволяет инициализировать страницу только когда на нее переходит пользователь, что существенно ускоряет запуск приложения.
Навигация
Xamarin.Forms предоставляет улучшенную навигацию по интерфейсу на основе URI, позволяя переходить на любую страницу в приложении без соблюдения строгой иерархии и переходить назад без необходимости прохода всех страниц в стеке навигации. Чтобы навигация работала, страницу нужно зарегистрировать, сделать это можно в разметке XAML в FlyoutItem, Tab и ShellContent с помощью свойства Route
<Shell ...>
<FlyoutItem ...
Route="page1">
<Tab ...
Route="page2">
<ShellContent ...
Route="mainpage" />
<ShellContent ...
Route="page3" />
</Tab>
<ShellContent ...
Route="page4" />
<ShellContent ...
Route="page5" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>
или в коде
Routing.RegisterRoute("page1", typeof(Page1));
навигация осуществляется с помощью команды
await Shell.Current.GoToAsync("//page2");
В качестве примера внесем изменения в следующие файлы:
AppShell.xaml
<TabBar>
<Tab Route="main" Title="MainPage" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:MainPage}" />
</Tab>
<Tab Title="Page1" Icon="xamarin.png">
<ShellContent Title="Main Page"
Icon="xamarin.png"
ContentTemplate="{DataTemplate pages:MainPage}" />
<ShellContent Route="page2" Title="Page2"
Icon="xamarin.png"
ContentTemplate="{DataTemplate pages:Page2}" />
</Tab>
<Tab IsEnabled="False" Title="Page2" Icon="xamarin.png">
<ShellContent ContentTemplate="{DataTemplate pages:Page2}" />
</Tab>
</TabBar>
MainPage.xaml
<StackLayout VerticalOptions="CenterAndExpand">
<Button Text="Page2" Clicked="ToPage2" />
</StackLayout>
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private async void ToPage2(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//page2");
}
}
Page2.xaml
<ContentPage.Content>
<StackLayout VerticalOptions="CenterAndExpand">
<Button Text="Назад" Clicked="Back" />
</StackLayout>
</ContentPage.Content>
Page2.xaml.cs
public partial class Page2 : ContentPage
{
public Page2()
{
InitializeComponent();
}
private async void Back(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("//main");
}
}
Поиск
Xamarin.Forms Shell имеет встроенные функции поиска, предоставляемые классом SearchHandler. Чтобы добавить функцию поиска на страницу, мы создадим класс PetSearchHandler производный от SearchHandler и переопределим методы OnQueryChanged и OnItemSelected. Метод OnQueryChanged срабатывает при вводе пользователем текста в поисковое поле и принимает два аргумента: oldValue и newValue, которые содержат предыдущий и новый поисковый запрос соответственно.
Метод SelectedItem выполняется в момент выбора пользователем результата поиска и принимает в качестве параметров объект, в данном случае Animal.
Для примера создадим модель Animal
Models/Animal.cs
public class Animal
{
public string Name { get; set; }
public string ImageUrl { get; set; }
}
Класс PetData который будет содержать коллекцию наших любимых кошечек и собачек
Data/PetData.cs
public static class PetData
{
public static IList<Animal> Pets { get; private set; }
static PetData()
{
Pets = new List<Animal>();
Pets.Add(new Animal
{
Name = "Afghan Hound",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/6/69/Afghane.jpg"
});
Pets.Add(new Animal
{
Name = "Alpine Dachsbracke",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Alpejski_gończy_krótkonożny_g99.jpg/320px-Alpejski_gończy_krótkonożny_g99.jpg"
});
Pets.Add(new Animal
{
Name = "American Bulldog",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/5/5e/American_Bulldog_600.jpg"
});
Pets.Add(new Animal
{
Name = "Abyssinian",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9b/Gustav_chocolate.jpg/168px-Gustav_chocolate.jpg"
});
Pets.Add(new Animal
{
Name = "Arabian Mau",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/d/d3/Bex_Arabian_Mau.jpg"
});
Pets.Add(new Animal
{
Name = "Bengal",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/Paintedcats_Red_Star_standing.jpg/187px-Paintedcats_Red_Star_standing.jpg"
});
Pets.Add(new Animal
{
Name = "Burmese",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/0/04/Blissandlucky11.jpg"
});
Pets.Add(new Animal
{
Name = "Cyprus",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/CyprusShorthair.jpg/320px-CyprusShorthair.jpg"
});
Pets.Add(new Animal
{
Name = "German Rex",
ImageUrl = "https://upload.wikimedia.org/wikipedia/commons/c/c7/German_rex_harry_%28cropped%29.jpg"
});
}
}
PetSearchHandler.cs
public class PetSearchHandler : SearchHandler
{
protected override void OnQueryChanged(string oldValue, string newValue)
{
base.OnQueryChanged(oldValue, newValue);
if (string.IsNullOrWhiteSpace(newValue))
{
ItemsSource = null;
}
else
{
ItemsSource = PetData.Pets
.Where(pet => pet.Name.ToLower().Contains(newValue.ToLower()))
.ToList<Animal>();
}
}
protected override async void OnItemSelected(object item)
{
base.OnItemSelected(item);
var pet = item as Animal;
if (pet is null) return;
await App.Current.MainPage.DisplayAlert("Вы выбрали",pet.Name,"ok");
}
}
Добавим страницу Pets в которой зададим наш PetSearchHandler
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
x:Class="HelloShell.Pets">
<Shell.SearchHandler>
<controls:PetSearchHandler Placeholder="Enter search term"
ShowsResults="true"
DisplayMemberName="Name" />
</Shell.SearchHandler>
<ContentPage.Content>
В итоге у нас должна получиться страница с поисковым полем в навигационном баре, при вводе поисковой фразы отображается простой список из названий питомцев.
При желании мы легко можем настроить содержимое ячейки списка, добавив туда картинку и несколько текстовых меток:
<Shell.SearchHandler>
<controls:PetSearchHandler Placeholder="Enter search term"
ShowsResults="true">
<controls:PetSearchHandler.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" Orientation="Horizontal">
<Image Source="{Binding ImageUrl}"
Aspect="AspectFill"
HeightRequest="40"
WidthRequest="40" />
<Label Text="{Binding Name}"
FontAttributes="Bold" />
</StackLayout>
</DataTemplate>
</controls:PetSearchHandler.ItemTemplate>
</controls:PetSearchHandler>
</Shell.SearchHandler>
Автор: Александр