Статьи

Начало работы с Xamarin.Forms: параметры макета

Когда дело доходит до проектирования и размещения экранов вашего приложения, у вас есть два основных варианта: написание кода или использование XAML. Если вы когда-либо занимались разработкой WPF (Windows Presentation Foundation) или Silverlight, то вы, вероятно, уже знакомы с XAML. XAML — это расширяемый язык разметки приложений, который был создан, чтобы помочь определить внешний вид приложения без необходимости обрабатывать все это в коде. Xamarin.Forms работает с обоими вариантами. В конечном итоге вам решать, какой вариант вы предпочитаете.

Важно отметить, что XAML, используемый для Xamarin.Forms, не совместим с другими формами инструментов XAML и XAML.

Если вы тот человек, который любит быть в коде и не хочет иметь ничего общего с какой-либо разметкой или дизайнером, то вам, вероятно, будет очень удобно с этой опцией. Вы программно создаете экземпляры различных типов объектов View и добавляете их непосредственно на Page или в Layout на Page . Вот простой пример создания класса SimplePage , создания нескольких объектов View и добавления их на Page через объект StackLayout .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SamplePage : ContentPage {
    public SamplePage()
    {
        Padding = new Thickness(20);
 
        var label = new Label
        {
            Text = «I am a simple page»,
            BackgroundColor = Color.Blue,
            Font = Font.SystemFontOfSize(30),
            WidthRequest = 150,
            HeightRequest = 40
        };
 
        var button = new Button {
            Text = «I have a button»,
            BackgroundColor = Color.Red,
            Font = Font.SystemFontOfSize( 20 ),
            WidthRequest = 200,
            HeightRequest = 200
        };
 
        var entry = new Entry {
            Placeholder = «I have a entry box»,
            BackgroundColor = Color.Green,
            WidthRequest = 200,
            HeightRequest = 150
        };
 
         
 
        Content = new StackLayout {
            Spacing = 10,
            Children = {button, entry, label}
        };
    }
}

Как вы можете видеть, объекты View имеют ряд общих свойств, которые вы можете использовать для установки текста, цветов, интервалов, высоты, ширины и т. Д. Все, что вам нужно сделать сейчас, — это изменить метод GetMainPage в App класс, чтобы вернуть новый экземпляр класса SamplePage , и все SamplePage .

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

Если вы предпочитаете отделять внешний вид вашего приложения от логики и реализации, тогда XAML может быть просто подходом. XAML позволяет вам создавать весь макет вашего приложения в специализированном формате XML, который Xamarin может преобразовывать в страницы, макеты, представления и ячейки, и отображать их пользователю. Если вы никогда раньше не использовали XAML, это может занять некоторое время. Однако, как только вы это освоите, это может быть довольно приятно.

Чтобы использовать XAML в сочетании с Xamarin.Forms, вам необходимо создать свой проект с помощью шаблона Blank App (Xamarin.Forms Portable), чтобы весь код Xamarin.Forms можно было разделить на его собственную dll.

В примере кода предыдущего раздела вы создали в коде очень простой класс ContentPage . Чтобы создать тот же ContentPage с использованием XAML, щелкните правой кнопкой мыши проект PCL и выберите « Добавить»> «Новый элемент» . В диалоговом окне « Добавить новый элемент » выберите шаблон страницы Forms Xaml и замените содержимое по умолчанию следующим текстом :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version=»1.0″ encoding=»utf-8″ ?>
<ContentPage xmlns=»http://xamarin.com/schemas/2014/forms»
                       xmlns:x=»http://schemas.microsoft.com/winfx/2009/xaml»
                       x:Class=»SampleFormsXAMLApp.SampleXAMLPage»
             Padding=»30″>
  <StackLayout Spacing=»10″>
    <Label Text=»I am a simple Page»
           BackgroundColor=»Blue»
           Font=»30″
           WidthRequest=»150″
           HeightRequest=»40″/>
     
    <Button Text=»I have a button»
            BackgroundColor=»Red»
            Font=»20″
            WidthRequest=»200″
            HeightRequest=»200″/>
 
    <Entry Placeholder=»I have a entry box»
           BackgroundColor=»Green»
           WidthRequest=»200″
           HeightRequest=»150″/>
  </StackLayout>
     
</ContentPage>

Если вы запустите свое приложение, вы должны увидеть тот же экран, что и в примере кода. Типы Page , Layout и View отображаются на элементы XML, а свойства являются атрибутами элемента. Вы можете использовать любую опцию для создания полностью настраиваемых кроссплатформенных пользовательских интерфейсов.

Вы можете создавать приложения, в которых вы создаете объекты View для своих объектов Page и явно устанавливаете их свойства, но это быстро становится громоздким. Когда вы явно устанавливаете свойства в своем коде XAML, вы больше не можете повторно использовать эту Page XAML для чего-либо еще. Другими словами, вы должны создавать новые страницы XAML для каждого варианта, который вам нужен. У кого есть время для этого?

Разве не было бы неплохо, если бы вы могли создавать повторно используемые страницы XAML без кода пользовательского интерфейса и хранить все логически разделенными? Конечно. Добро пожаловать в MVVM.

Model-View-ViewMode l — это архитектурный шаблон, созданный с учетом XAML. По своей сути он разделяет основную концепцию других архитектурных шаблонов, таких как MVP и MVC. Он был разработан для отделения данных, уровня модели, от представления и уровня представления. Каналом между ними является ViewModel . Модель представления — это класс, который облегчает связь между уровнями модели и представления посредством механизма, известного как привязка данных . Привязка данных лежит в основе шаблона MVVM и осуществляется через сам XAML. Давайте посмотрим на пример.

Начните с создания нового приложения Xamarin.Forms, выбрав шаблон проекта Blank App (Xamarin.Forms Portable) и присвоив ему имя MyRecipeBox .

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

В проекте MyRecipeBox создайте новую папку и назовите ее « Модели» . Это не является обязательным требованием, оно просто добавляет в проект некоторую организацию, которая всегда помогает по мере роста. В папке Models добавьте новый класс и назовите его Recipe . Замените реализацию по умолчанию следующим:

1
2
3
4
5
6
7
8
public class Recipe
{
    public string Name { get;
    public string Description { get;
    public TimeSpan PrepTime { get;
    public TimeSpan CookingTime { get;
    public List<string> Directions { get;
}

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

Создайте новую папку в проекте MyRecipeBox и назовите ее ViewModels . В папке ViewModels создайте новый класс и назовите его RecipeViewModel . При принятии шаблона MVVM в .NET для ViewModels обычно характерно то, что они реализуют интерфейс INotifyPropertyChanged . Этот интерфейс используется для того, чтобы позволить другим частям кода подписаться на события и включить привязку данных. Замените реализацию по RecipeViewModel класса RecipeViewModel следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class RecipeViewModel : INotifyPropertyChanged {
    private Recipe _recipe;
    public event PropertyChangedEventHandler PropertyChanged;
 
    public RecipeViewModel( Recipe recipe ) {
        _recipe = recipe;
        Directions = new ObservableCollection<string>(_recipe.Directions);
    }
     
    public ObservableCollection<string> Directions { get;
 
    public string Name {
        get { return _recipe != null ?
        set {
            if ( _recipe != null ) {
                _recipe.Name = value;
 
                if ( PropertyChanged != null ) {
                    PropertyChanged( this, new PropertyChangedEventArgs( «Name» ) );
                }
            }
        }
    }
 
    public string Description {
        get { return _recipe != null ?
        set {
            if ( _recipe != null ) {
                _recipe.Description = value;
 
                if ( PropertyChanged != null ) {
                    PropertyChanged( this, new PropertyChangedEventArgs( «Description» ) );
                }
            }
        }
    }
 
    public string PrepTime {
        get { return _recipe != null ?
        set {
            if ( _recipe != null ) {
                _recipe.PrepTime = TimeSpan.Parse(value);
 
                if ( PropertyChanged != null ) {
                    PropertyChanged(this, new PropertyChangedEventArgs(«PrepTime»));
                }
            }
        }
    }
 
    public string CookingTime {
        get { return _recipe != null ?
        set {
            if ( _recipe != null ) {
                _recipe.CookingTime = TimeSpan.Parse(value);
 
                if ( PropertyChanged != null ) {
                    PropertyChanged(this, new PropertyChangedEventArgs(«CookingTime»));
                }
            }
        }
    }
}

Возможно, вы заметили, что RecipeViewModel реализует интерфейс RecipeViewModel . Если вы углубитесь в этот интерфейс, вы увидите, что он содержит одно свойство, которое необходимо реализовать.

1
2
3
4
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}

Класс RecipleViewModel принимает экземпляр класса Recipe а затем предоставляет только четыре его свойства. Получатели, связанные с этими свойствами, просто возвращают данные в самом экземпляре Recipe . С другой стороны, сеттеры проверяют, не является ли PropertyChanged null . PropertyChanged будет null если на это событие нет подписчиков. В этом случае ничего не происходит. Если PropertyChanged не имеет значение null , вызывается событие, и каждый подписчик события получает информацию об изменении этой модели представления.

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

Пришло время создать страницу, которая показывает пользователю данные рецепта и использует привязку данных для обновления пользовательского интерфейса. Начните с создания папки Views в проекте MyRecipeBox . В папке Views добавьте новый Формирует страницу Xaml и назовите ее RecipeSummaryPage .

Замените XAML по умолчанию в файле следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<?xml version=»1.0″ encoding=»utf-8″ ?>
<ContentPage xmlns=»http://xamarin.com/schemas/2014/forms»
                       xmlns:x=»http://schemas.microsoft.com/winfx/2009/xaml»
                       x:Class=»MyRecipeBox.Views.RecipeSummaryPage»
             Title=»{Binding Name}»
             Padding=»30″>
  <StackLayout Spacing=»10″ VerticalOptions=»FillAndExpand»>
    <Label Text=»Recipe Name» TextColor=»Red»/>
    <Label Text=»{Binding Name}» />
    <Label Text=»Recipe Description» TextColor=»Red»/>
    <Label Text=»{Binding Description}»/>
    <Label Text=»Total Prep Time» TextColor=»Red»/>
    <Label Text=»{Binding PrepTime}»/>
    <Label Text=»Total Cook Time» TextColor=»Red»/>
    <Label Text=»{Binding CookTime}»/>
    <Label Text=»Directions» TextColor=»Red»/>
    <ListView ItemsSource=»{Binding Directions}»>
  </StackLayout>
</ContentPage>

Как вы можете видеть, привязка создается путем размещения некоторого форматированного текста там, где вы хотите, чтобы появлялись связанные данные. Синтаксис для выполнения этого "{Binding xxxxx}" , где xxxxx — это имя свойства, с которым вы хотите связать. Наконец, вам может быть интересно, как вы связываете созданную модель представления с этим представлением.

Если вы нажмете маленькую стрелку рядом с файлом RecipeSummaryPage.xaml , вы увидите, что появится другой файл, RecipleSummaryPage.xaml.cs . Это код файла, который содержит код C # для запуска этой страницы. Вам нужно изменить конструктор этого класса, чтобы он выглядел так:

1
2
3
4
5
6
public RecipeSummaryPage(RecipeViewModel recipeViewModel)
{
    InitializeComponent();
 
    this.BindingContext = recipeViewModel;
}

Свойство BindingContext — это место, где вам нужно назначить модель представления для создания вышеупомянутой привязки. Для этого передайте экземпляр вашего RecipeViewModel в конструктор.

Чтобы увидеть результаты нашего труда на экране, вам нужно сделать одно небольшое изменение, чтобы это сработало. В файле App.cs в проекте MyRecipeBox обновите метод GetMainPage как показано ниже.

01
02
03
04
05
06
07
08
09
10
public static Page GetMainPage() {
    var recipe = new Recipe {
        Name = «Toast»,
        Description = «It’s toast, are you kidding?»,
        PrepTime = new TimeSpan( 0, 0, 15 ),
        CookingTime = new TimeSpan( 0, 2, 0 ),
        Directions = new List<string>{«Grab bread», «Put bread in toaster», «Eat toast»}
    };
    return new RecipeSummaryPage( new RecipeViewModel( recipe ) );
}

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

На следующем и последнем шаге мы создадим и отобразим список объектов Recipe которые пользователь может щелкнуть, чтобы перенести их на страницу с подробностями. Начнем с создания новой модели представления, содержащей список объектов Recipe . Добавьте новый класс в папку ViewModels и назовите его RecipeListViewModel . Его реализация выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class RecipeListViewModel
{
    public ObservableCollection<Recipe> Recipes { get;
 
    public RecipeListViewModel( ) {
        Recipes = new ObservableCollection<Recipe>();
 
        Recipes.Add(new Recipe {
            Name = «Toast»,
            Description = «Are you kidding? It’s toast.»,
            CookingTime = new TimeSpan(0, 2, 0),
            PrepTime = new TimeSpan(0, 0, 15),
            Directions = new List<string> {
                «Pick up bread»,
                «Put break in toaster»,
                «Eat Toast»
            }
        });
 
        Recipes.Add(new Recipe
        {
            Name = «Cereal»,
            Description = «You know, the breakfast stuff.»,
            CookingTime = TimeSpan.Zero,
            PrepTime = new TimeSpan(0, 1, 0),
            Directions = new List<string> {
                «Put cereal in bowl»,
                «Put milk in bowl»,
                «Put spoon in bowl»,
                «Put spoon in mouth»
            }
        });
 
        Recipes.Add(new Recipe
        {
            Name = «Sandwich»,
            Description = «Bread and stuff. YUM!»,
            CookingTime = TimeSpan.Zero,
            PrepTime = new TimeSpan(0, 5, 0),
            Directions = new List<string> {
                «Get 2 slices of bread»,
                «Put cheese between break slices»,
                «Put ham between break slices»,
                «Enjoy»
            }
        });
    }
}

Возможно, вы заметили, что мы жестко закодировали рецепты в классе RecipeListViewModel . В реальном приложении рецепты будут получены из веб-службы или базы данных.

Создайте новую страницу для отображения списка рецептов. В папке Views создайте новую страницу формы Xaml и назовите эту RecipleListPage . Замените его содержимое следующим:

01
02
03
04
05
06
07
08
09
10
11
12
13
<?xml version=»1.0″ encoding=»utf-8″ ?>
<ContentPage xmlns=»http://xamarin.com/schemas/2014/forms»
                       xmlns:x=»http://schemas.microsoft.com/winfx/2009/xaml»
                       x:Class=»MyRecipeBox.Views.RecipeListPage»
             Title=»Recipes»>
  <ListView x:Name=»recipeList» ItemsSource=»{Binding Recipes}» ItemTapped=»OnItemSelected»>
    <ListView.ItemTemplate>
      <DataTemplate>
        <TextCell Text=»{Binding Name}»/>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

Этот XAML очень похож на предыдущий пример. Однако на этот раз у вас есть только представление списка на странице. При использовании привязки данных в ListView вам нужно немного углубиться, чтобы выполнить фактическую привязку. Сначала вы связываете полный список со свойством ItemsSource объекта ListView а затем вам нужно определить Template и DataTemplate объекта ListView как TextCell и связать этот TextCell с отдельным свойством экземпляра Recipe вы хотите отобразить. Это то, что выводит названия рецептов на экран.

Вы также можете видеть, что есть Name связанное с ListView , recipeList , который пригодится чуть позже, а также обработчик событий. В этом случае, когда пользователь касается элемента в ListView , ItemTapped событие ItemTapped . Вы подписались на это событие и будете использовать метод с именем OnItemSelected для его обработки.

На следующем шаге нам нужно выполнить некоторую разводку в файле RecipeListPage.xaml.cs, чтобы установить BindingContext нашей новой страницы, а также реализовать OnItemSelected события OnItemSelected .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public partial class RecipeListPage
{
    public RecipeListPage()
    {
        InitializeComponent();
 
        this.BindingContext = new RecipeListViewModel( );
    }
 
    public void OnItemSelected(object sender, ItemTappedEventArgs args)
    {
        var recipe = args.Item as Recipe;
        if (recipe == null)
            return;
 
        Navigation.PushAsync(new RecipeSummaryPage(new RecipeViewModel(recipe)));
        // Reset the selected item
        recipeList.SelectedItem = null;
    }
}

Свойство BindingContext будет просто установлено на новый экземпляр RecipleListViewModel который вы создали ранее. Метод обработчика событий немного отличается. Во-первых, вам нужно проверить, что выбранный предмет является рецептом, что выполняется в следующих строках:

1
2
3
var recipe = args.Item as Recipe;
if (recipe == null)
    return;

Если выбранный элемент является объектом Recipe , то вы используете свойство Navigation для добавления нового экземпляра RecipleSummaryPage в текущий NavigationView . Наконец, вам нужно убедиться, что ни один элемент в списке не выбран.

1
2
3
Navigation.PushAsync(new RecipeSummaryPage(new RecipeViewModel(recipe)));
// Reset the selected item
recipeList.SelectedItem = null;

Доступ к ListView осуществляется через Name которое было ему присвоено ранее. Вы можете получить доступ к любому View на странице, назначив Name View и ссылаясь на него по имени в коде.

Последнее изменение, которое нам нужно сделать, — обновить метод GetMainPage в файле App.cs. как показано ниже:

1
2
3
public static Page GetMainPage() {
    return new NavigationPage(new RecipeListPage());
}

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

Нажав на одну из строк в списке, вы попадете на страницу с кратким описанием рецепта, как вы видели раньше.

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

Если вы хотите узнать больше о Xamarin, ознакомьтесь с нашим курсом Создание многоплатформенных приложений на C # в Xamarin .

В ходе курса вы узнаете, как создать кроссплатформенное приложение из единой кодовой базы, которая будет работать на трех совершенно разных платформах: iOS, Android и Windows Phone 8. Думаешь, это невозможно? Через некоторое время вы будете делать это самостоятельно. Давай приступим к работе.

Вы можете воспользоваться бесплатной 14-дневной пробной версией подписки Tuts +. Для начала ознакомьтесь с нашими вариантами подписки или, если вы заинтересованы в этом курсе, вы можете приобрести его отдельно за 15 долларов! Вот предварительный просмотр, чтобы вы начали: