Xamarin.Forms 殼層導覽

Download Sample 下載範例

Xamarin.Forms Shell 包含 URI 型瀏覽體驗,其使用路由巡覽至應用程式中的任何頁面,而不需要遵循設定的瀏覽階層。 此外,這也可讓您回溯導覽,而不需要造訪導覽堆疊上的所有頁面。

類別 Shell 會定義下列導覽相關屬性:

BackButtonBehaviorCurrentItemCurrentState 屬性都以 BindableProperty 物件為後盾,也就是說,這些屬性可以是資料繫結的目標。

導覽是透過從 Shell 類別叫用 GoToAsync 方法來執行。 當導覽即將執行時, Navigating 會引發事件,並在 Navigated 巡覽完成時引發事件。

注意

流覽仍然可以使用 Navigation 屬性在 Shell 應用程式中的頁面之間執行。 如需詳細資訊,請參閱階層式導覽

路由

導覽是在 Shell 應用程式中,透過指定要導覽的 URI 執行的。 導覽 URL 可以有三個元件:

  • 「路線」,定義作為殼層視覺階層中現有內容的路徑。
  • 「頁面」。 Shell 視覺階層中不存在的頁面可以從 Shell 應用程式中的任何位置推送到導覽堆疊上。 例如,詳細資料頁面未定義於殼層視覺階層,但可以視需要推送至導覽堆疊。
  • 一或多個「查詢參數」。 查詢參數是可在導覽時傳遞至目的地頁面的參數。

導覽 URI 包括所有這三個元件時,結構為://route/page?queryParameters

註冊路線

路由可以透過其Route屬性定義於 、TabBarTabShellContent 物件上FlyoutItem

<Shell ...>
    <FlyoutItem ...
                Route="animals">
        <Tab ...
             Route="domestic">
            <ShellContent ...
                          Route="cats" />
            <ShellContent ...
                          Route="dogs" />
        </Tab>
        <ShellContent ...
                      Route="monkeys" />
        <ShellContent ...
                      Route="elephants" />  
        <ShellContent ...
                      Route="bears" />
    </FlyoutItem>
    <ShellContent ...
                  Route="about" />                  
    ...
</Shell>

注意

Shell 階層中的所有項目有與其相關聯的路由。 如果您未設定路由,則會在運行時間產生一個路由。 但是,不保證產生的路由在不同的應用程式工作階段之間都一致。

上述範例會建立下列路由階層,可用於程式設計導覽:

animals
  domestic
    cats
    dogs
  monkeys
  elephants
  bears
about

若要導覽至 dogs 路由的 ShellContent 物件,絕對路由 URI 為 //animals/domestic/dogs。 同樣地,若要導覽至 about 路由的 ShellContent 物件,絕對路由 URI 為 //about

警告

如果偵測到重複的路由,將會在應用程式啟動時擲回 ArgumentException。 如果階層中有兩個以上的同層級路由共用一個路由名稱,也會擲回此例外狀況。

註冊詳細數據頁面路由

Shell 子類別建構函式中,或在叫用路線之前執行的任何其他位置中,可以針對任何未在殼層視覺階層中呈現的詳細資料頁面,明確地註冊其他路線。 這會使用 Routing.RegisterRoute 方法來完成:

Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));

本範例會將子類別中 Shell 未定義的詳細數據頁面註冊為路由。 然後,您可以從應用程式內的任何位置瀏覽這些詳細數據頁面,使用 URI 型導覽。 這類頁面的路由稱為全域路由

警告

如果 Routing.RegisterRoute 方法嘗試向兩個以上的不同類型註冊相同的路由,則會擲回 ArgumentException

或者,如果需要,可以在不同的路由階層註冊頁面:

Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));

此範例會啟用內容相關式頁面導覽,其中,從 monkeys 路由的頁面導覽至 details 路由會顯示 MonkeyDetailPage。 同樣地,從 elephants 路由的頁面導覽至 details 路由會顯示 ElephantDetailPage。 如需詳細資訊,請參閱 內容導覽

注意

如果需要,已使用 Routing.RegisterRoute 方法註冊其路由的頁面可使用 Routing.UnRegisterRoute 方法取消註冊。

執行導覽

若要執行導覽,必須先取得 Shell 子類別的參考。 將 App.Current.MainPage 屬性轉換為 Shell 物件,或透 過Shell.Current 屬性可以取得這個參考。 接著,可以對 Shell 物件呼叫 GoToAsync 方法來執行導覽。 此方法會導覽至 ShellNavigationState,並在導覽動畫完成後傳回將會完成的 TaskShellNavigationState 物件是由 GoToAsync 方法,從 stringUri 所建構,且其 Location 屬性設為 stringUri 引數。

重要

從 Shell 視覺階層導覽至路由時,不會建立導覽堆疊。 但是,導覽至不在 Shell 視覺階層中的頁面時,則會建立導覽堆疊。

物件的目前導覽狀態 Shell 可以透過 Shell.Current.CurrentState 屬性擷取,其中包含屬性中顯示路由的 Location URI。

絕對路由

將有效的絕對 URI 指定為 GoToAsync 方法的引數可執行導覽:

await Shell.Current.GoToAsync("//animals/monkeys");

此範例會導覽至 monkeys 路由的頁面,並在 ShellContent 物件上定義路由。 表示 monkeys 路由的 ShellContent 物件為 FlyoutItem 物件子系,其路由是 animals

相對路由

將有效的相對 URI 指定為 GoToAsync 方法的引數也可以執行導覽。 路由系統將會嘗試比對 ShellContent 物件的 URI。 因此,如果應用程式中的所有路由都是唯一的,則只能藉由將唯一路由名稱指定為相對 URI 來執行導覽。

支援下列相對路由格式:

格式 描述
route 路由階層會從目前的位置向上搜尋指定的路由。 比對頁面將會推送至流覽堆疊。
/route 路由階層會從指定的路由搜尋,從目前位置向下搜尋。 比對頁面將會推送至流覽堆疊。
//route 路由階層會從目前的位置向上搜尋指定的路由。 比對頁面將會取代瀏覽堆疊。
///route 路由階層會從目前的位置向下搜尋指定的路由。 比對頁面將會取代瀏覽堆疊。

下列範例會巡覽至路由的頁面 monkeydetails

await Shell.Current.GoToAsync("monkeydetails");

在此範例中,路由 monkeyDetails 會搜尋階層,直到找到相符頁面為止。 找到頁面時,會推送至瀏覽堆疊。

關聯式導覽

相對路由可啟用關聯式導覽。 例如,請考慮下列路由階層:

monkeys
  details
bears
  details

顯示 monkeys 路由已註冊的頁面時,導覽至 details 路由將會顯示 monkeys/details 路由已註冊的頁面。 同樣地,顯示 bears 路由已註冊的頁面時,導覽至 details 路由將會顯示 bears/details 路由已註冊的頁面。 如需如何在此範例中註冊路由的相關資訊,請參閱註冊頁面路由

回溯導覽

您可以指定 ".." 作為 GoToAsync 方法的引數,以執行回溯導覽:

await Shell.Current.GoToAsync("..");

使用 “..” 的向後流覽也可以與路由結合:

await Shell.Current.GoToAsync("../route");

在此範例中,會執行向後流覽,然後流覽至指定的路由。

重要

只有在向後巡覽將您放在路由階層中目前位置巡覽至指定的路由時,才能往回巡覽至指定的路由。

同樣地,可以多次向後巡覽,然後巡覽至指定的路由:

await Shell.Current.GoToAsync("../../route");

在此範例中,會執行兩次向後流覽,然後巡覽至指定的路由。

此外,在向後巡覽時,可以透過查詢屬性傳遞數據:

await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");

在此範例中,會執行向後流覽,並將查詢參數值傳遞至上一頁的查詢參數。

注意

查詢參數可以附加至任何向後流覽要求。

如需在巡覽時傳遞數據的詳細資訊,請參閱 傳遞數據

無效的路由

下列路由格式無效:

格式 說明
//page 或 ///page 全域路由目前不得為導覽堆疊上的唯一頁面。 因此,不支援以絕對路由傳送至全域路由。

使用這些路由格式會導致 Exception 擲回 。

警告

嘗試導覽至不存在的路由會導致擲回 ArgumentException 例外狀況。

為導覽偵錯

部分 Shell 類別是以 DebuggerDisplayAttribute 裝飾,以指定偵錯工具如何顯示類別或欄位。 顯示與導覽要求相關的資料有助於為導覽要求偵錯。 例如,下列螢幕擷取畫面顯示 Shell.Current 物件的 CurrentItemCurrentState 屬性:

Screenshot of debugger

在此範例中,CurrentItem 屬性屬於 FlyoutItem 類型,可顯示 FlyoutItem 物件的標題和路由。 同樣地,CurrentState 屬性屬於 ShellNavigationState 類型,可顯示 Shell 應用程式內顯示之路由的 URI。

類別 TabStack 定義 型 IReadOnlyList<Page>別 的屬性,代表 中的 Tab目前流覽堆疊。 此類別也會提供下列可覆寫的導覽方法:

  • GetNavigationStack會傳 IReadOnlyList<Page>回 ,目前流覽堆疊。
  • OnInsertPageBefore,會在呼叫 INavigation.InsertPageBefore 時呼叫。
  • OnPopAsync,傳回 Task<Page>,會在呼叫 INavigation.PopAsync 時呼叫。
  • OnPopToRootAsync,傳回 Task,會在呼叫 INavigation.OnPopToRootAsync 時呼叫。
  • OnPushAsync,傳回 Task,會在呼叫 INavigation.PushAsync 時呼叫。
  • OnRemovePage,會在呼叫 INavigation.RemovePage 時呼叫。

下列範例示範如何覆寫 OnRemovePage 方法:

public class MyTab : Tab
{
    protected override void OnRemovePage(Page page)
    {
        base.OnRemovePage(page);

        // Custom logic
    }
}

在此範例中, MyTab 應該在Shell視覺階層中取用物件, Tab 而不是物件。

類別 ShellNavigating 定義事件,因為程式設計導覽或用戶互動而即將執行導覽時引發此事件。 隨附 Navigating 事件的 ShellNavigatingEventArgs 物件會提供下列屬性:

屬性 類型​ 描述
Current ShellNavigationState 目前頁面的 URI。
Source ShellNavigationSource 發生導覽的類型。
Target ShellNavigationState 代表導覽目的地的 URI。
CanCancel bool 指出它是否可以取消導覽的值。
Cancelled bool 值,指出流覽是否已取消。

此外,類別 ShellNavigatingEventArgs 提供 Cancel 可用來取消流覽的方法,以及 GetDeferral 傳回 ShellNavigatingDeferral 可用來完成流覽之令牌的方法。 如需瀏覽延遲的詳細資訊,請參閱 瀏覽延遲

類別 Shell 也會定義 Navigated 事件,此事件會在流覽完成時引發。 隨附 Navigated 事件的 ShellNavigatedEventArgs 物件會提供下列屬性:

屬性 類型​ 描述
Current ShellNavigationState 目前頁面的 URI。
Previous ShellNavigationState 上一頁的 URI。
Source ShellNavigationSource 發生導覽的類型。

重要

當事件引發時,NavigatingOnNavigating呼叫 方法。 同樣地, OnNavigated 當事件引發時 Navigated ,會呼叫 方法。 您可以在子類別中 Shell 覆寫這兩種方法,以攔截流覽要求。

例如,ShellNavigatedEventArgsShellNavigatingEventArgs 類別都擁有 ShellNavigationSource 類型的 Source 屬性。 此列舉提供下列值:

  • Unknown
  • Push
  • Pop
  • PopToRoot
  • Insert
  • Remove
  • ShellItemChanged
  • ShellSectionChanged
  • ShellContentChanged

因此,可以在覆寫中 OnNavigating 攔截流覽,而且可以根據流覽來源執行動作。 例如,下列的程式碼會示範如何在未儲存頁面上的資料時,取消向後導覽:

protected override void OnNavigating(ShellNavigatingEventArgs args)
{
    base.OnNavigating(args);

    // Cancel any back navigation.
    if (args.Source == ShellNavigationSource.Pop)
    {
        args.Cancel();
    }
// }

您可以根據使用者選擇攔截和完成或取消殼層流覽。 藉由覆寫OnNavigatingShell類別中的方法,以及在 物件上ShellNavigatingEventArgs呼叫 GetDeferral 方法,即可達成此目的。 這個方法會傳 ShellNavigatingDeferral 回具有 Complete 方法的令牌,可用來完成流覽要求:

public MyShell : Shell
{
    // ...
    protected override async void OnNavigating(ShellNavigatingEventArgs args)
    {
        base.OnNavigating(args);

        ShellNavigatingDeferral token = args.GetDeferral();

        var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
        if (result != "Yes")
        {
            args.Cancel();
        }
        token.Complete();
    }    
}

在此範例中,會顯示一個動作表,邀請使用者完成流覽要求,或取消它。 藉由叫用 Cancel 物件上的 ShellNavigatingEventArgs 方法來取消流覽。 巡覽是藉由 Complete 叫用 物件上 ShellNavigatingDeferral 方法所擷取 GetDeferral 之令牌上的 ShellNavigatingEventArgs 方法來完成。

警告

如果使用者嘗試在有擱置的瀏覽延遲時流覽,方法 GoToAsync 會擲回 InvalidOperationException

傳遞資料

執行以 URI 為基礎的程式設計導覽時,數據可以當做查詢參數傳遞。 這是藉由在路由後面附加 ? ,後面接著查詢參數標識碼 和 =值來達成此目的。 例如,當使用者在 ElephantsPage 上選取大象時,範例應用程式中會執行下列程式碼:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}

此程式碼範例會在 CollectionView 中擷取目前選取的大象,並導覽至 elephantdetails 路由,將 elephantName 作為查詢參數傳遞。

接收導覽資料的方法有兩種:

  1. 類別,表示要巡覽的頁面,或頁面 的 BindingContext類別,可以使用 每個查詢參數的 QueryPropertyAttribute 裝飾。 如需詳細資訊,請參閱 使用查詢屬性處理導覽數據。
  2. 表示所瀏覽頁面的類別,或頁面 的 BindingContext類別,可以實 IQueryAttributable 作 介面。 如需詳細資訊,請參閱 使用單一方法處理導覽數據。

使用查詢屬性屬性處理導覽數據

使用每個查詢參數的 裝飾接收類別 QueryPropertyAttribute ,即可接收導覽數據:

[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            LoadAnimal(value);
        }
    }
    ...

    void LoadAnimal(string name)
    {
        try
        {
            Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
            BindingContext = animal;
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }    
}

的第一個自變數QueryPropertyAttribute會指定將接收數據的屬性名稱,而第二個自變數則指定查詢參數標識符。因此,QueryPropertyAttribute上述範例中的 會Name指定 屬性會從方法呼叫中的 GoToAsync URI 接收查詢參數中name傳遞的數據。 屬性 setter 會Name呼叫 LoadAnimal 方法來擷nameAnimal 的物件,並將它設定為BindingContext頁面的 。

注意

透過 QueryPropertyAttribute 接收的查詢參數值會自動譯碼 URL。

使用單一方法處理導覽數據

藉由在接收類別上實 IQueryAttributable 作 介面,即可接收導覽數據。 介面 IQueryAttributable 指定實作類別必須實 ApplyQueryAttributes 作 方法。 這個方法具有 query 類型 IDictionary<string, string>為 的自變數,其中包含瀏覽期間傳遞的任何數據。 字典中的每個索引鍵都是查詢參數標識碼,其值是查詢參數值。 使用此方法的優點是,瀏覽數據可以使用單一方法來處理,當您有多個需要整體處理的瀏覽數據專案時,這非常有用。

下列範例顯示實作 介面的 IQueryAttributable 檢視模型類別:

public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Monkey { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        // The query parameter requires URL decoding.
        string name = HttpUtility.UrlDecode(query["name"]);
        LoadAnimal(name);
    }

    void LoadAnimal(string name)
    {
        try
        {
            Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
            OnPropertyChanged("Monkey");
        }
        catch (Exception)
        {
            Console.WriteLine("Failed to load animal.");
        }
    }
    ...
}

在此範例中,ApplyQueryAttributes方法會從方法呼叫中的 GoToAsync URI 擷取查詢參數的值name。 然後, LoadAnimal 會呼叫 方法來擷取 Animal 物件,其中其設定為系結數據之 Monkey 屬性的值。

重要

透過介面接收的 IQueryAttributable 查詢參數值不會自動譯碼 URL。

傳遞和處理多個查詢參數

使用連接 &多個查詢參數,即可傳遞多個查詢參數。 例如,下列程式代碼會傳遞兩個數據項:

async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
    string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
    string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
    await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}

此程式代碼範例會擷取 中目前選取的 CollectionView大象,並巡覽至 elephantdetails 路由,傳遞 elephantNameelephantLocation 作為查詢參數。

若要接收多個數據項,代表所巡覽頁面的 類別,或頁面 的 BindingContext類別,可以使用 每個查詢參數的 QueryPropertyAttribute 裝飾:

[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
    public string Name
    {
        set
        {
            // Custom logic
        }
    }

    public string Location
    {
        set
        {
            // Custom logic
        }
    }
    ...    
}

在這裡範例中,類別會針對 QueryPropertyAttribute 每個查詢參數使用 裝飾 。 第一個 QueryPropertyAttribute 指定 Name 屬性將接收傳入 name 查詢參數的數據,而第二個 QueryPropertyAttribute 指定 Location 屬性將接收傳入 location 查詢參數的數據。 在這兩種情況下,查詢參數值都會在方法呼叫的 GoToAsync URI 中指定。

或者,藉由在代表所瀏覽頁面的 類別上實 IQueryAttributable 作 介面,或頁面 的 BindingContext類別,即可處理導覽數據:

public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
    public Animal Elephant { get; private set; }

    public void ApplyQueryAttributes(IDictionary<string, string> query)
    {
        string name = HttpUtility.UrlDecode(query["name"]);
        string location = HttpUtility.UrlDecode(query["location"]);
        ...        
    }
    ...
}

在此範例中ApplyQueryAttributes,方法會從方法呼叫中的 GoToAsync URI 擷取 和 location 查詢參數的值name

上一頁按鈕行為

您可以將附加屬性設定 BackButtonBehaviorBackButtonBehavior 物件,以重新定義返回按鈕的外觀和行為。 類別 BackButtonBehavior 會定義下列屬性:

  • Command,屬於 ICommand 類型,會在按 [上一頁] 按鈕時執行。
  • CommandParameter,屬於 object 類型,這是傳遞至 Command 的參數。
  • IconOverride,屬於 ImageSource 類型,這是用於上一頁按鈕的圖示。
  • IsEnabled,屬於 boolean 類型,可指出是否啟用上一頁按鈕。 預設值是 true
  • TextOverride,屬於 string 類型,這是用於上一頁按鈕的文字。

所有這些屬性都以 BindableProperty 物件為後盾,也就是說,這些屬性可以是資料繫結的目標。

下列程式代碼顯示重新定義返回按鈕外觀和行為的範例:

<ContentPage ...>    
    <Shell.BackButtonBehavior>
        <BackButtonBehavior Command="{Binding BackCommand}"
                            IconOverride="back.png" />   
    </Shell.BackButtonBehavior>
    ...
</ContentPage>

對等的 C# 程式碼為:

Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
    Command = new Command(() =>
    {
        ...
    }),
    IconOverride = "back.png"
});

Command 屬性會設定要按 [上一頁] 按鈕時執行的 ICommand,而 IconOverride 屬性則會設定為用於 [上一頁] 按鈕的圖示:

Screenshot of a Shell back button icon override, on iOS and Android