Xamarin.Forms в Xamarin Native Projects
Как правило, Xamarin.Forms приложение включает одну или несколько страниц, производных от ContentPage
них, и эти страницы разделяются всеми платформами в проекте библиотеки .NET Standard или общем проекте. Однако Native Forms позволяет ContentPage
добавлять производные страницы непосредственно в собственные приложения Xamarin.iOS, Xamarin.Android и UWP. По сравнению с тем, что собственный проект использует ContentPage
производные страницы из проекта библиотеки .NET Standard или общего проекта, преимущество добавления страниц непосредственно в собственные проекты заключается в том, что страницы можно расширить с помощью собственных представлений. Затем собственные представления можно назвать в XAML и x:Name
ссылаться на нее из кодовой части. Дополнительные сведения о собственных представлениях см. в разделе "Собственные представления".
Процесс использования производной Xamarin.FormsContentPage
страницы в собственном проекте выглядит следующим образом:
- Xamarin.Forms Добавьте пакет NuGet в собственный проект.
- Добавьте производную
ContentPage
страницу и все зависимости в собственный проект. - Вызовите метод
Forms.Init
. - Создайте экземпляр производной
ContentPage
страницы и преобразуйте его в соответствующий собственный тип с помощью одного из следующих методов расширения:CreateViewController
для iOS,CreateSupportFragment
для Android илиCreateFrameworkElement
для UWP. - Перейдите к собственному представлению типа производной страницы с помощью собственного
ContentPage
API навигации.
Xamarin.Forms необходимо инициализировать путем вызова Forms.Init
метода, прежде чем собственный проект может создать производную ContentPage
страницу. Выбор этого в первую очередь зависит от того, когда это удобнее всего в потоке приложений , он может выполняться при запуске приложения или непосредственно перед ContentPage
созданием производной страницы. В этой статье и сопутствующих примерах приложений Forms.Init
метод вызывается при запуске приложения.
Примечание.
Пример решения приложения NativeForms не содержит Xamarin.Forms проектов. Вместо этого он состоит из проекта Xamarin.iOS, проекта Xamarin.Android и проекта UWP. Каждый проект — это собственный проект, использующий Собственные формы для использования ContentPage
производных страниц. Однако нет причин, почему собственные проекты не могли использовать ContentPage
производные страницы из проекта библиотеки .NET Standard или общего проекта.
При использовании машинных форм функции, Xamarin.Forms такие как DependencyService
, MessagingCenter
и подсистема привязки данных, все еще работают. Однако навигация по страницам должна выполняться с помощью собственного API навигации.
iOS
В iOS FinishedLaunching
переопределение в AppDelegate
классе обычно является местом для выполнения связанных с приложением задач запуска приложения. Он вызывается после запуска приложения и обычно переопределяется для настройки главного окна и контроллера представления. В следующем примере кода показан AppDelegate
класс в примере приложения:
[Register("AppDelegate")]
public class AppDelegate : UIApplicationDelegate
{
public static AppDelegate Instance;
UIWindow _window;
AppNavigationController _navigation;
public static string FolderPath { get; private set; }
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
Forms.Init();
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
Instance = this;
_window = new UIWindow(UIScreen.MainScreen.Bounds);
UINavigationBar.Appearance.SetTitleTextAttributes(new UITextAttributes
{
TextColor = UIColor.Black
});
FolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
NotesPage notesPage = new NotesPage()
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
UIViewController notesPageController = notesPage.CreateViewController();
notesPageController.Title = "Notes";
_navigation = new AppNavigationController(notesPageController);
_window.RootViewController = _navigation;
_window.MakeKeyAndVisible();
notesPage.Parent = null;
return true;
}
// ...
}
Метод FinishedLaunching
выполняет следующие задачи:
- Xamarin.Forms инициализируется путем
Forms.Init
вызова метода. - Создается новый
Xamarin.Forms.Application
объект, а его словарь ресурсов на уровне приложения имеет значениеResourceDictionary
, определенное в XAML. - Ссылка на
AppDelegate
класс хранится вstatic
Instance
поле. Это позволяет другим классам вызывать методы, определенные вAppDelegate
классе. - Создается
UIWindow
основной контейнер для представлений в собственных приложениях iOS. - Свойство
FolderPath
инициализировано в путь на устройстве, где будут храниться данные заметок. NotesPage
Создается объект, который является производной страницей, определенной Xamarin.FormsContentPage
в XAML, и его родительский объект имеет ранее созданныйXamarin.Forms.Application
объект.- Объект
NotesPage
преобразуется вUIViewController
CreateViewController
метод расширения. - Свойство
Title
UIViewController
набора, которое будет отображаться в объектеUINavigationBar
. - Создается
AppNavigationController
для управления иерархической навигацией. Это пользовательский класс контроллера навигации, производный отUINavigationController
. ОбъектAppNavigationController
управляет стеком контроллеров представления иUIViewController
передается в конструктор изначально приAppNavigationController
загрузке. - Объект
AppNavigationController
задается как верхний уровеньUIViewController
дляUIWindow
приложения, иUIWindow
он устанавливается в качестве окна ключа для приложения и становится видимым. - Свойство
Parent
NotesPage
объекта имеетnull
значение , чтобы предотвратить утечку памяти.
FinishedLaunching
После выполнения метода будет отображаться пользовательский интерфейс, определенный в Xamarin.FormsNotesPage
классе, как показано на следующем снимке экрана:
Внимание
Все ContentPage
производные страницы могут использовать ресурсы, определенные на уровне ResourceDictionary
приложения, при условии, что Parent
свойству страницы присвоено Application
значение объекта.
Взаимодействие с пользовательским интерфейсом, например нажатием кнопки мыши + Button
, приведет к следующему обработчику событий в NotesPage
выполнении кода:
void OnNoteAddedClicked(object sender, EventArgs e)
{
AppDelegate.Instance.NavigateToNoteEntryPage(new Note());
}
Поле static
AppDelegate.Instance
позволяет AppDelegate.NavigateToNoteEntryPage
вызывать метод, который показан в следующем примере кода:
public void NavigateToNoteEntryPage(Note note)
{
NoteEntryPage noteEntryPage = new NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
var noteEntryViewController = noteEntryPage.CreateViewController();
noteEntryViewController.Title = "Note Entry";
_navigation.PushViewController(noteEntryViewController, true);
noteEntryPage.Parent = null;
}
Метод NavigateToNoteEntryPage
преобразует производную Xamarin.FormsContentPage
страницу UIViewController
CreateViewController
в метод расширения и задает Title
свойство объекта UIViewController
. Затем он UIViewController
передается AppNavigationController
методом PushViewController
. Поэтому пользовательский интерфейс, определенный Xamarin.FormsNoteEntryPage
в классе, будет отображаться, как показано на следующем снимке экрана:
NoteEntryPage
Когда отображается, обратная навигация будет отображаться UIViewController
для NoteEntryPage
класса из AppNavigationController
класса, возвращая пользователя UIViewController
в NotesPage
класс. Однако всплывающее UIViewController
окно из собственного стека навигации iOS не автоматически удаляет UIViewController
и присоединенный Page
объект. AppNavigationController
Поэтому класс переопределяет PopViewController
метод, чтобы удалить контроллеры представления на обратной навигации:
public class AppNavigationController : UINavigationController
{
//...
public override UIViewController PopViewController(bool animated)
{
UIViewController topView = TopViewController;
if (topView != null)
{
// Dispose of ViewController on back navigation.
topView.Dispose();
}
return base.PopViewController(animated);
}
}
Переопределение PopViewController
вызывает Dispose
метод в UIViewController
объекте, который был получен из собственного стека навигации iOS. Сбой этого приведет к UIViewController
потере и присоединенному Page
объекту.
Внимание
Потерянные объекты не могут быть собраны мусором и поэтому приводят к утечке памяти.
Android
В Android OnCreate
переопределение в MainActivity
классе обычно является местом выполнения связанных с приложением задач запуска приложения. В следующем примере кода показан MainActivity
класс в примере приложения:
public class MainActivity : AppCompatActivity
{
public static string FolderPath { get; private set; }
public static MainActivity Instance;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
Forms.Init(this, bundle);
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
Instance = this;
SetContentView(Resource.Layout.Main);
var toolbar = FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
SupportActionBar.Title = "Notes";
FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
NotesPage notesPage = new NotesPage()
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
AndroidX.Fragment.App.Fragment notesPageFragment = notesPage.CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.Replace(Resource.Id.fragment_frame_layout, mainPage)
.Commit();
//...
notesPage.Parent = null;
}
...
}
Метод OnCreate
выполняет следующие задачи:
- Xamarin.Forms инициализируется путем
Forms.Init
вызова метода. - Создается новый
Xamarin.Forms.Application
объект, а его словарь ресурсов на уровне приложения имеет значениеResourceDictionary
, определенное в XAML. - Ссылка на
MainActivity
класс хранится вstatic
Instance
поле. Это позволяет другим классам вызывать методы, определенные вMainActivity
классе. - Содержимое
Activity
устанавливается из ресурса макета. В примере приложения макет состоит изLinearLayout
макета, содержащего объектToolbar
, иFrameLayout
объект для действия в качестве контейнера фрагментов. - Извлекается
Toolbar
и задается в качестве панели действий дляActivity
панели действий, а заголовок панели действий задан. - Свойство
FolderPath
инициализировано в путь на устройстве, где будут храниться данные заметок. NotesPage
Создается объект, который является производной страницей, определенной Xamarin.FormsContentPage
в XAML, и его родительский объект имеет ранее созданныйXamarin.Forms.Application
объект.- Объект
NotesPage
преобразуется вFragment
CreateSupportFragment
метод расширения. - Класс
SupportFragmentManager
создает и фиксируетFrameLayout
транзакцию, которая заменяет экземпляр классомFragment
NotesPage
. - Свойство
Parent
NotesPage
объекта имеетnull
значение , чтобы предотвратить утечку памяти.
Дополнительные сведения об фрагментах см. в разделе "Фрагменты".
OnCreate
После выполнения метода будет отображаться пользовательский интерфейс, определенный в Xamarin.FormsNotesPage
классе, как показано на следующем снимке экрана:
Внимание
Все ContentPage
производные страницы могут использовать ресурсы, определенные на уровне ResourceDictionary
приложения, при условии, что Parent
свойству страницы присвоено Application
значение объекта.
Взаимодействие с пользовательским интерфейсом, например нажатием кнопки мыши + Button
, приведет к следующему обработчику событий в NotesPage
выполнении кода:
void OnNoteAddedClicked(object sender, EventArgs e)
{
MainActivity.Instance.NavigateToNoteEntryPage(new Note());
}
Поле static
MainActivity.Instance
позволяет MainActivity.NavigateToNoteEntryPage
вызывать метод, который показан в следующем примере кода:
public void NavigateToNoteEntryPage(Note note)
{
NoteEntryPage noteEntryPage = new NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
AndroidX.Fragment.App.Fragment noteEntryFragment = noteEntryPage.CreateSupportFragment(this);
SupportFragmentManager
.BeginTransaction()
.AddToBackStack(null)
.Replace(Resource.Id.fragment_frame_layout, noteEntryFragment)
.Commit();
noteEntryPage.Parent = null;
}
Метод NavigateToNoteEntryPage
преобразует производную Xamarin.FormsContentPage
страницу Fragment
CreateSupportFragment
в метод расширения и добавляет его в Fragment
стек обратной части фрагмента. Поэтому пользовательский интерфейс, определенный в нем Xamarin.FormsNoteEntryPage
, будет отображаться, как показано на следующем снимке экрана:
NoteEntryPage
При отображении нажатие стрелки назад появляется Fragment
для NoteEntryPage
стека фрагмента, возвращая пользователя Fragment
в NotesPage
класс.
Включение поддержки обратной навигации
Класс SupportFragmentManager
имеет событие, которое запускается всякий BackStackChanged
раз, когда содержимое фрагмента изменяется. Метод OnCreate
в MainActivity
классе содержит анонимный обработчик событий для этого события:
SupportFragmentManager.BackStackChanged += (sender, e) =>
{
bool hasBack = SupportFragmentManager.BackStackEntryCount > 0;
SupportActionBar.SetHomeButtonEnabled(hasBack);
SupportActionBar.SetDisplayHomeAsUpEnabled(hasBack);
SupportActionBar.Title = hasBack ? "Note Entry" : "Notes";
};
Этот обработчик событий отображает кнопку "Назад" на панели действий, если в стеке фрагмента есть один или несколько Fragment
экземпляров. Ответ на касание кнопки "Назад" обрабатывается переопределением OnOptionsItemSelected
:
public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)
{
if (item.ItemId == global::Android.Resource.Id.Home && SupportFragmentManager.BackStackEntryCount > 0)
{
SupportFragmentManager.PopBackStack();
return true;
}
return base.OnOptionsItemSelected(item);
}
OnOptionsItemSelected
Переопределение вызывается всякий раз, когда выбран элемент в меню параметров. Эта реализация выводит текущий фрагмент из стека обратной части фрагмента, если выбрана кнопка "Назад", а на стеке фрагмента есть один или несколько Fragment
экземпляров.
Несколько действий
Если приложение состоит из нескольких действий, ContentPage
производные страницы могут быть внедрены в каждую из действий. В этом сценарии Forms.Init
метод должен вызываться только в OnCreate
переопределении первого Activity
, который внедряет Xamarin.FormsContentPage
объект. Однако это оказывает следующее влияние:
- Значение
Xamarin.Forms.Color.Accent
будет взято изActivity
вызываемогоForms.Init
метода. - Значение
Xamarin.Forms.Application.Current
будет связано сActivity
вызываемого методомForms.Init
.
Выбрать файл
При внедрении производной ContentPage
страницы, которая использует WebView
кнопку HTML "Выбрать файл", Activity
необходимо переопределить OnActivityResult
метод:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
ActivityResultCallbackRegistry.InvokeCallback(requestCode, resultCode, data);
}
UWP
В UWP собственный App
класс обычно является местом для выполнения связанных с приложением задач запуска приложения. Xamarin.Forms обычно инициализируется в Xamarin.Forms приложениях UWP в OnLaunched
переопределении в собственном App
классе, чтобы передать LaunchActivatedEventArgs
аргумент методу Forms.Init
. По этой причине собственные приложения UWP, использующие производную Xamarin.FormsContentPage
страницу, могут наиболее легко вызывать Forms.Init
метод из App.OnLaunched
метода:
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
// ...
Xamarin.Forms.Forms.Init(e);
// Create app-level resource dictionary.
Xamarin.Forms.Application.Current = new Xamarin.Forms.Application();
Xamarin.Forms.Application.Current.Resources = new MyDictionary();
// ...
}
Кроме того, OnLaunched
метод также может создать любой словарь ресурсов на уровне приложения, необходимый приложению.
По умолчанию собственный App
класс запускает MainPage
класс как первую страницу приложения. В следующем примере кода показан MainPage
класс в примере приложения:
public sealed partial class MainPage : Page
{
NotesPage notesPage;
NoteEntryPage noteEntryPage;
public static MainPage Instance;
public static string FolderPath { get; private set; }
public MainPage()
{
this.NavigationCacheMode = NavigationCacheMode.Enabled;
Instance = this;
FolderPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData));
notesPage = new Notes.UWP.Views.NotesPage
{
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
this.Content = notesPage.CreateFrameworkElement();
// ...
notesPage.Parent = null;
}
// ...
}
Конструктор MainPage
выполняет следующие задачи:
- Кэширование включено для страницы, чтобы новый
MainPage
объект не был создан при переходе пользователя на страницу. - Ссылка на
MainPage
класс хранится вstatic
Instance
поле. Это позволяет другим классам вызывать методы, определенные вMainPage
классе. - Свойство
FolderPath
инициализировано в путь на устройстве, где будут храниться данные заметок. NotesPage
Создается объект, который является производной страницей, определенной Xamarin.FormsContentPage
в XAML, и его родительский объект имеет ранее созданныйXamarin.Forms.Application
объект.- Объект
NotesPage
преобразуется вFrameworkElement
CreateFrameworkElement
метод расширения, а затем задает содержимоеMainPage
класса. - Свойство
Parent
NotesPage
объекта имеетnull
значение , чтобы предотвратить утечку памяти.
MainPage
После выполнения конструктора будет отображаться пользовательский интерфейс, определенный в Xamarin.FormsNotesPage
классе, как показано на следующем снимке экрана:
Внимание
Все ContentPage
производные страницы могут использовать ресурсы, определенные на уровне ResourceDictionary
приложения, при условии, что Parent
свойству страницы присвоено Application
значение объекта.
Взаимодействие с пользовательским интерфейсом, например нажатием кнопки мыши + Button
, приведет к следующему обработчику событий в NotesPage
выполнении кода:
void OnNoteAddedClicked(object sender, EventArgs e)
{
MainPage.Instance.NavigateToNoteEntryPage(new Note());
}
Поле static
MainPage.Instance
позволяет MainPage.NavigateToNoteEntryPage
вызывать метод, который показан в следующем примере кода:
public void NavigateToNoteEntryPage(Note note)
{
noteEntryPage = new Notes.UWP.Views.NoteEntryPage
{
BindingContext = note,
// Set the parent so that the app-level resource dictionary can be located.
Parent = Xamarin.Forms.Application.Current
};
this.Frame.Navigate(noteEntryPage);
noteEntryPage.Parent = null;
}
Навигация в UWP обычно выполняется с Frame.Navigate
помощью метода, который принимает Page
аргумент. Xamarin.Forms определяет Frame.Navigate
метод расширения, который принимает экземпляр производной ContentPage
страницы. Поэтому при NavigateToNoteEntryPage
выполнении метода будет отображаться пользовательский интерфейс, определенный в нем Xamarin.FormsNoteEntryPage
, как показано на следующем снимке экрана:
NoteEntryPage
При отображении нажатие стрелки назад появляется FrameworkElement
NoteEntryPage
из стека обратного приложения, возвращая пользователя FrameworkElement
в NotesPage
класс.
Включение поддержки изменения размера страниц
При изменении размера Xamarin.Forms окна приложения UWP содержимое также должно быть изменено. Это достигается путем регистрации обработчика событий для Loaded
события в конструкторе MainPage
:
public MainPage()
{
// ...
this.Loaded += OnMainPageLoaded;
// ...
}
Событие Loaded
запускается при отображении, отрисовке и готовности к взаимодействию и выполнении OnMainPageLoaded
метода в ответ:
void OnMainPageLoaded(object sender, RoutedEventArgs e)
{
this.Frame.SizeChanged += (o, args) =>
{
if (noteEntryPage != null)
noteEntryPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
else
notesPage.Layout(new Xamarin.Forms.Rectangle(0, 0, args.NewSize.Width, args.NewSize.Height));
};
}
Метод OnMainPageLoaded
регистрирует анонимный обработчик событий для Frame.SizeChanged
события, который вызывается при ActualHeight
ActualWidth
изменении свойств или свойств в событии Frame
. В ответ содержимое Xamarin.Forms активной страницы изменяется, вызывая Layout
метод.
Включение поддержки обратной навигации
В UWP приложения должны включить обратную навигацию для всех аппаратных и программных кнопок назад в разных форм-факторах устройства. Это можно сделать, зарегистрируя обработчик событий для BackRequested
события, который можно выполнить в конструкторе MainPage
:
public MainPage()
{
// ...
SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}
При запуске приложения метод извлекает SystemNavigationManager
объект, GetForCurrentView
связанный с текущим представлением, а затем регистрирует обработчик событий для BackRequested
события. Приложение получает это событие только в том случае, если это приложение переднего плана и в ответ вызывает OnBackRequested
обработчик событий:
void OnBackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
e.Handled = true;
rootFrame.GoBack();
noteEntryPage = null;
}
}
Обработчик OnBackRequested
событий вызывает GoBack
метод в корневом кадре приложения и задает BackRequestedEventArgs.Handled
свойство, чтобы true
пометить событие как обработанное. Неспособность пометить событие как обработанное может привести к тому, что событие игнорируется.
Приложение выбирает, следует ли отображать кнопку "Назад" в строке заголовка. Это достигается путем задания AppViewBackButtonVisibility
свойства одному из AppViewBackButtonVisibility
значений перечисления в App
классе:
void OnNavigated(object sender, NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}
Обработчик OnNavigated
событий, который выполняется в ответ на Navigated
запуск события, обновляет видимость кнопки обратной строки заголовка при переходе на страницу. Это гарантирует, что кнопка обратной строки заголовка отображается, если стек задней части приложения не пуст или удален из строки заголовка, если стек обратной части приложения пуст.
Дополнительные сведения о поддержке обратной навигации в UWP см . в журнале навигации и обратной навигации для приложений UWP.