Поделиться через


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страницы в собственном проекте выглядит следующим образом:

  1. Xamarin.Forms Добавьте пакет NuGet в собственный проект.
  2. Добавьте производную ContentPageстраницу и все зависимости в собственный проект.
  3. Вызовите метод Forms.Init .
  4. Создайте экземпляр производной ContentPageстраницы и преобразуйте его в соответствующий собственный тип с помощью одного из следующих методов расширения: CreateViewController для iOS, CreateSupportFragment для Android или CreateFrameworkElement для UWP.
  5. Перейдите к собственному представлению типа производной страницы с помощью собственного ContentPageAPI навигации.

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.