Xamarin.Forms 在 Xamarin 原生專案中

Download Sample 下載範例

一般而言, 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. 使用原生導覽 API 瀏覽至衍生頁面的 ContentPage原生類型表示法。

Xamarin.Forms 必須先呼叫 Forms.Init 方法,才能建構 ContentPage衍生頁面的原生專案。 選擇執行此動作的時機主要取決於應用程式流程中最方便的時機– 它可以在應用程式啟動時執行,或是在建構衍生頁面之前 ContentPage執行。 在本文中,以及隨附的範例應用程式,會在 Forms.Init 應用程式啟動時呼叫 方法。

注意

NativeForms 範例應用程式解決方案不包含任何Xamarin.Forms專案。 而是由 Xamarin.iOS 專案、Xamarin.Android 專案和 UWP 專案所組成。 每個專案都是使用原生窗體來取 ContentPage用衍生頁面的原生專案。 不過,原生項目無法從 .NET Standard 連結庫專案或共用專案取用 ContentPage衍生頁面的原因。

使用原生表單時,Xamarin.Forms、、 MessagingCenter和數據系結引擎等DependencyService功能仍可正常運作。 不過,頁面導覽必須使用原生導覽 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 參考會儲存在 staticInstance 欄位中。 這是為其他類別提供機制,以呼叫 類別中 AppDelegate 定義的方法。
  • UIWindow建立 ,這是原生 iOS 應用程式中檢視的主要容器。
  • 屬性 FolderPath 會初始化為將儲存記事數據之裝置上的路徑。
  • 系統會 NotesPage 建立 物件,這是 Xamarin.FormsContentPageXAML 中定義的衍生頁面,且其父系設定為先前建立 Xamarin.Forms.Application 的物件。
  • 物件NotesPage會使用CreateViewController擴充方法轉換成 UIViewController
  • TitleUIViewController 屬性已設定,其會顯示在 UINavigationBar上。
  • AppNavigationController會建立 來管理階層式導覽。 這是衍生自 UINavigationController的自定義導覽控制器類別。 物件 AppNavigationController 會管理檢視控制器的堆疊,而 UIViewController 傳入建構函式的 一開始會在載入時 AppNavigationController 呈現。
  • 物件 AppNavigationController 會設定為的最上層 UIViewControllerUIWindow,而 UIWindow 會設定為應用程式的索引鍵視窗,並顯示。
  • 對象的 Parent 屬性 NotesPage 設定為 null,以防止記憶體流失。

FinishedLaunching執行 方法之後,類別中Xamarin.FormsNotesPage定義的UI將會顯示,如下列螢幕快照所示:

Screenshot shows a Notes screen on a mobile device.

重要

所有 ContentPage衍生的頁面都可以取用應用層級 ResourceDictionary中定義的資源,前提是 Parent 頁面的 屬性設定為 Application 物件。

例如,藉由點 +Button選 來與UI互動,會導致程式代碼後置執行中的 NotesPage 下列事件處理程式:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    AppDelegate.Instance.NavigateToNoteEntryPage(new Note());
}

欄位 staticAppDelegate.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使用CreateViewController擴充方法將 衍生頁面UIViewController轉換成 ,並設定 TitleUIViewController屬性。 UIViewController然後,PushViewController方法會推送至 AppNavigationController 。 因此,將會顯示 類別中 Xamarin.FormsNoteEntryPage 定義的UI,如下列螢幕快照所示:

Screenshot shows a Note Entry on a mobile device.

NoteEntryPage顯示 時,傳回瀏覽會從 AppNavigationController中彈出 NoteEntryPageUIViewController 類別的 ,並將使用者傳回 給 UIViewController 類別的 NotesPage 。 不過,從iOS原生流覽堆疊快 UIViewController 顯 不會自動處置 UIViewController 和附加 Page 物件。 因此,類別 AppNavigationControllerPopViewController 覆寫 方法,以在向後瀏覽時處置檢視控制器:

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從 iOS 原生瀏覽堆疊彈出的物件上UIViewController呼叫 Dispose 方法。 若無法這麼做,會導致 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 參考會儲存在 staticInstance 欄位中。 這是為其他類別提供機制,以呼叫 類別中 MainActivity 定義的方法。
  • 內容 Activity 是從版面配置資源設定。 在範例應用程式中,版面配置包含 LinearLayoutToolbar的 ,以及 FrameLayout 做為片段容器的 。
  • Toolbar會擷取 並設定為的動作列Activity,並設定動作列標題。
  • 屬性 FolderPath 會初始化為將儲存記事數據之裝置上的路徑。
  • 系統會 NotesPage 建立 物件,這是 Xamarin.FormsContentPageXAML 中定義的衍生頁面,且其父系設定為先前建立 Xamarin.Forms.Application 的物件。
  • 物件NotesPage會使用CreateSupportFragment擴充方法轉換成 Fragment
  • 類別 SupportFragmentManager 會建立並認可交易,該交易會將 FrameLayout 實例 Fragment 取代為 類別的 NotesPage
  • 對象的 Parent 屬性 NotesPage 設定為 null,以防止記憶體流失。

如需片段的詳細資訊,請參閱 片段

OnCreate執行 方法之後,類別中Xamarin.FormsNotesPage定義的UI將會顯示,如下列螢幕快照所示:

Screenshot shows a Notes screen on a mobile device with a blue banner and colored note text.

重要

所有 ContentPage衍生的頁面都可以取用應用層級 ResourceDictionary中定義的資源,前提是 Parent 頁面的 屬性設定為 Application 物件。

例如,藉由點 +Button選 來與UI互動,會導致程式代碼後置執行中的 NotesPage 下列事件處理程式:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    MainActivity.Instance.NavigateToNoteEntryPage(new Note());
}

欄位 staticMainActivity.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使用CreateSupportFragment擴充方法將 衍生頁面Fragment轉換成 ,並將 新增Fragment至片段返回堆疊。 因此,將會顯示 中 Xamarin.FormsNoteEntryPage 定義的UI,如下列螢幕快照所示:

Screenshot shows a Note Entry on a mobile device with a blue banner.

NoteEntryPage顯示時,點選返回箭號會從片段傳回的堆疊中彈出 NoteEntryPageFragment 的 ,並將使用者傳回 給 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只有在第一個Activity內嵌Xamarin.FormsContentPage的 覆寫中OnCreate,才需要呼叫 方法。 不過,這有下列影響:

  • 的值 Xamarin.Forms.Color.Accent 會取自 Activity 呼叫 方法的 Forms.Init
  • 的值 Xamarin.Forms.Application.Current 將會與 Activity 呼叫 方法的 Forms.Init 相關聯。

選擇檔案

在內嵌 ContentPage使用需要支援 HTML [選擇檔案] 按鈕的 衍生頁面 WebView 時, 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通常會在 UWP 應用程式中OnLaunched,在Xamarin.Forms原生App類別的覆寫中初始化 ,以將 LaunchActivatedEventArgs 自變數傳遞至 Forms.Init 方法。 基於這個理由,取用Xamarin.FormsContentPage衍生頁面的原生 UWP 應用程式最容易從 App.OnLaunched 方法呼叫 Forms.Init 方法:

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 參考會儲存在 staticInstance 欄位中。 這是為其他類別提供機制,以呼叫 類別中 MainPage 定義的方法。
  • 屬性 FolderPath 會初始化為將儲存記事數據之裝置上的路徑。
  • 系統會 NotesPage 建立 物件,這是 Xamarin.FormsContentPageXAML 中定義的衍生頁面,且其父系設定為先前建立 Xamarin.Forms.Application 的物件。
  • 物件NotesPage會使用CreateFrameworkElement擴充方法轉換成 FrameworkElement ,然後設定為 類別的內容MainPage
  • 對象的 Parent 屬性 NotesPage 設定為 null,以防止記憶體流失。

執行建 MainPage 構函式之後,類別中 Xamarin.FormsNotesPage 定義的UI將會顯示,如下列螢幕快照所示:

Screenshot shows a Notes page with notes and date/times.

重要

所有 ContentPage衍生的頁面都可以取用應用層級 ResourceDictionary中定義的資源,前提是 Parent 頁面的 屬性設定為 Application 物件。

例如,藉由點 +Button選 來與UI互動,會導致程式代碼後置執行中的 NotesPage 下列事件處理程式:

void OnNoteAddedClicked(object sender, EventArgs e)
{
    MainPage.Instance.NavigateToNoteEntryPage(new Note());
}

欄位 staticMainPage.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 定義的UI,如下列螢幕快照所示:

Screenshot shows a Notes page with a text box with a note entered.

NoteEntryPage顯示 時,點選 [上一頁] 箭號會從應用程式內傳回堆疊彈出 FrameworkElementNoteEntryPage 的 ,並將使用者傳回 給 FrameworkElement 類別的 NotesPage

啟用頁面重設大小支援

當UWP應用程式視窗重設大小時, Xamarin.Forms 也應該調整內容的大小。 這可藉由在建構函式中MainPage註冊 事件的事件處理程式Loaded來完成:

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匿名事件處理程式,當 或 ActualWidth 屬性在 上Frame變更時ActualHeight,就會引發此事件處理程式。 回應中, Xamarin.Forms 使用中頁面的內容會藉由呼叫 Layout 方法來重設大小。

啟用返回瀏覽支援

在 UWP 上,應用程式必須針對不同裝置尺寸的所有硬體和軟體返回按鈕啟用返回流覽。 這可以藉由註冊 事件的事件處理程式 BackRequested 來完成,這可以在建構函式中 MainPage 執行:

public MainPage()
{
    // ...
    SystemNavigationManager.GetForCurrentView().BackRequested += OnBackRequested;
}

啟動應用程式時, GetForCurrentView 方法會擷取 SystemNavigationManager 與目前檢視相關聯的物件,然後註冊事件的事件處理程式 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.Handledtrue ,將 事件標示為已處理。 無法將事件標示為已處理,可能會導致事件遭到忽略。

應用程式會選擇是否要在標題列上顯示返回按鈕。 這可藉由將 AppViewBackButtonVisibility 屬性設定為 類別中的App其中AppViewBackButtonVisibility一個列舉值來達成:

void OnNavigated(object sender, NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
        ((Frame)sender).CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}

OnNavigated事件處理程式會在發生頁面導覽時更新標題列返回按鈕的可見度,而事件處理程式是針對事件引發而執行的Navigated。 這可確保如果應用程式內返回堆疊不是空白,或如果應用程式內返回堆疊是空的,則會從標題列移除標題列返回按鈕。

如需 UWP 上傳回瀏覽支援的詳細資訊,請參閱 UWP 應用程式的瀏覽歷程記錄和向後流覽。