WINDOWS 10 2015 年特別版
# UI 設計 - 適用于 Windows 10 的自適應應用
作者:[Clint Rutkas](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Clint+Rutka)?和?[Rajen Kishna](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Rajen+Kishna)?| Windows 2015
通過使用 Windows 10 的通用 Windows 平臺 (UWP),應用目前可在各種設備系列上運行,并在平臺控件的支持下,在不同大小的屏幕和窗口中實現自動縮放。這些設備系列如何支持用戶與您的應用程序之間的交互,您的應用程序如何響應并自適應于其運行所在的設備? 我們探討這些問題以及 Microsoft 在該平臺中提供的工具和資源,方便您不必再為了讓應用程序能夠運行在不同類型的設備上而編寫和維護復雜代碼。
讓我們先來探討可以用來優化不同設備系列的 UI 的響應技術。然后,再深入探討如何使您的應用自適應特定的設備功能。
在深入探討控件、API 和代碼之前,我們先花點時間探討一下目前談到的設備系列。簡而言之: 設備系列是指一組具有特定外形規格的設備,從 IoT 設備、智能手機、平板電腦和臺式電腦到 Xbox 游戲控制臺、大屏 Surface Hub 設備,甚至便攜式設備。應用將在所有這些設備系列中運行,而在設計您的應用時,考慮將運行該應用的設備系列是一件很重要的事情。
雖然有許多設備系列,UWP 卻設計為:讓 85% 的 API 可以完全供任何應用訪問,不受應用所運行位置的約束。此外,對于前 1,000 個應用,基礎通用 Windows API 集中的 API 數量占所有已使用的 API 總數的 96.2%。提供大部分功能且這些功能可作為 UWP 的一部分,而每個設備上專用的 API 可用于進一步定制您的應用。
## 歡迎回來,Windows
“如何在 Windows 中使用應用”中的一個最大的變化是您已經很熟悉了的:在窗口中運行應用。Windows 8 和 Windows 8.1 允許應用全屏運行,或同時并排顯示多達四個應用。相比而言,Windows 10 允許用戶隨意排列應用、重設應用大小和放置應用。Windows 10 中的新方法為用戶提供了更加靈活的 UI,但可能需要您最終做一些工作來優化它。Windows 10 在 XAML 中所做的改進中引入了一些可以在您的應用中實現響應技術的方法,所以無論屏幕或窗口大小如何,看上去都很好。我們來探討這三種方法。
VisualStateManager?在 Windows 10 中,VisualStateManager 類已擴展為兩種機制,用于在基于 XAML 的應用中實現響應式設計。新 VisualState.StateTriggers 和 VisualState.Setters API 允許您定義符合一定條件的視覺狀態。通過使用內置 AdaptiveTrigger 作為 VisualState 的 StateTrigger,并設置 MinWindowHeight 和 MinWindowWidth 屬性,視覺狀態就可以根據應用窗口的高度和寬度進行更改。您還可以對 Windows.UI.Xaml.StateTriggerBase 進行擴展以創建自己的觸發器,例如觸發設備系列或輸入類型。請看一看圖 1?中的代碼。
圖 1 創建自定義狀態觸發器
~~~
<Page>
? <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
??? <VisualStateManager.VisualStateGroups>
????? <VisualStateGroup>
??????? <VisualState>
????????? <VisualState.StateTriggers>
????????? <!-- VisualState to be triggered when window
??????????? width is >=720 effective pixels. -->
??????????? <AdaptiveTrigger MinWindowWidth="720" />
????????? </VisualState.StateTriggers>
????????? <VisualState.Setters>
??????????? <Setter Target="myPanel.Orientation"
??????????????????? Value="Horizontal" />
????????? </VisualState.Setters>
??????? </VisualState>
????? </VisualStateGroup>
??? </VisualStateManager.VisualStateGroups>
??? <StackPanel x:Name="myPanel" Orientation="Vertical">
????? <TextBlock Text="This is a block of text. It is text block 1\. "
???????????????? Style="{ThemeResource BodyTextBlockStyle}"/>
????? <TextBlock Text="This is a block of text. It is text block 2\. "
???????????????? Style="{ThemeResource BodyTextBlockStyle}"/>
????? <TextBlock Text="This is a block of text. It is text block 3\. "
???????????????? Style="{ThemeResource BodyTextBlockStyle}"/>
??? </StackPanel>
? </Grid>
</Page>
~~~
在圖 1?示例中,頁面將顯示三個在默認狀態下相互間堆疊的 TextBlock 元素。VisualStateManager 具有在 MinWindowWidth 值為 720 時定義的 AdaptiveTrigger,當窗口寬度至少為 720 個有效像素時,它將使 StackPanel 的方向變為水平。這樣的話,當用戶在手機或平板電腦設備上調整窗口大小或從縱向模式變為橫向模式時,就可以更好地利用額外的橫向空間。請記住,如果您同時定義了寬度和高度兩個屬性,則您的觸發器只有在同時滿足兩個條件的情況下才會被觸發。您可以探索 GitHub ([wndw.ms/XUneob](http://wndw.ms/XUneob)) 上的狀態觸發器示例,查看使用觸發器的更多方案,包括許多自定義觸發器的示例。
相對面板在圖 1?示例中,StateTrigger 用于改變 StackPanel 的 Orientation 屬性。XAML 中的許多容器元素與 StateTriggers 相結合,使您能夠采用多種方法來控制您的 UI,但卻沒辦法通過它們輕松創建一個元素布置位置彼此相關的復雜且反應靈敏的 UI。這也正是新的 RelativePanel 派上用場的原因。如圖 2?所示,您可以通過表達元素間的空間關系,來使用 RelativePanel 布置您的元素。這意味著,您可以輕松使用 RelativePanel 和 AdaptiveTriggers 來創建響應式 UI,您可在其中根據可用的屏幕空間對元素進行移動。
圖 2 使用 RelativePanel 表達空間關系
~~~
<RelativePanel BorderBrush="Gray" BorderThickness="10">
? <Rectangle x:Name="RedRect" Fill="Red" MinHeight="100" MinWidth="100"/>
? <Rectangle x:Name="BlueRect" Fill="Blue" MinHeight="100" MinWidth="100"
???????????? RelativePanel.RightOf="RedRect" />
? <!-- Width is not set on the green and yellow rectangles.
?????? It's determined by the RelativePanel properties. -->
? <Rectangle x:Name="GreenRect" Fill="Green"
???????????? MinHeight="100" Margin="0,5,0,0"
???????????? RelativePanel.Below="RedRect"
???????????? RelativePanel.AlignLeftWith="RedRect"
???????????? RelativePanel.AlignRightWith="BlueRect"/>
? <Rectangle Fill="Yellow" MinHeight="100"
???????????? RelativePanel.Below="GreenRect"
????? ???????RelativePanel.AlignLeftWith="BlueRect"
???????????? RelativePanel.AlignRightWithPanel="True"/>
</RelativePanel>
~~~
需要提醒的是,您使用的帶有附加屬性的語法中包含了額外的括號,如圖所示:
~~~
<VisualStateManager.VisualStateGroups>
? <VisualStateGroup>
??? <VisualState>
????? <VisualState.StateTriggers>
??????? <AdaptiveTrigger MinWindowWidth="720" />
????? </VisualState.StateTriggers>
????? <VisualState.Setters>
??????? <Setter Target="GreenRect.(RelativePanel.RightOf)"
??????????????? Value="BlueRect" />
????? </VisualState.Setters>
??? </VisualState>
~~~
您可以在 GitHub ([wndw.ms/cbdL0q](http://wndw.ms/cbdL0q)) 上的響應技術示例中查看其他一些使用 RelativePanel 的方案。
SplitView?應用窗口大小對應用頁面的影響不僅僅是對其顯示內容的影響;它可能需要導航元素響應窗口本身的大小變化。Windows 10 中引入的新 SplitView 控件通常用于創建頂級導航體驗 - 可以根據應用的窗口大小相應地對行為方式進行調整。請記住,雖然這是 SplitView 的常見用例之一,但卻不僅限于此用法。將 SplitView 分為兩個區域:窗格和內容。
控件上的一些屬性可以用來操控呈現。首先,DisplayMode 指定有關內容區域的窗格的呈現方式。有四種可用模式:Overlay、Inline、CompactOverlay 和 CompactInline。圖 3?顯示了在應用中呈現的 Inline、Overlay 和 CompactInline 模式的示例。

圖 3 DisplayMode 導航元素
PanePlacement 屬性將窗格顯示在內容區域的左側(默認)或右側。OpenPaneLength 屬性指定窗格完全展開時的寬度(默認為 320 個有效像素)。
請注意,SplitView 控件不包括用戶用以觸發面板狀態的內置 UI 元素,如經常出現在移動應用中的“漢堡”菜單。如果您想展示這一行為,則必須在您的應用中定義此 UI 元素,并提供觸發 SplitView 的 IsPaneOpen 屬性的代碼。
是否想探索 SplitView 提供的所有功能? 務必查看 GitHub ([wndw.ms/qAUVr9](http://wndw.ms/qAUVr9)) 上的 XAML 導航菜單示例。
## 引入后退按鈕
如果您開發適用于 Windows Phone 早期版本的應用,您可能會習慣于在每臺設備上設有一個硬件或軟件后退按鈕,讓用戶在您的應用中可以以后退的方式導航。然而,對于 Windows 8 和 8.1,您必須創建您自己的能夠實現后退導航的 UI。如果針對的是 Windows 10 應用中的多個設備系列,為了實現起來更加容易,有一種方式可以確保為所有用戶提供一致的后退導航機制。這有助于在您的應用運行過程中釋放一些 UI 空間。
若要為您的應用啟用系統后退按鈕,可以使用 SystemNavigationManager 類中的 AppViewBackButtonVisibility 屬性,即使是在沒有硬件或軟件后退按鈕的設備系列上(如筆記本電腦和臺式電腦)也是如此。只需將 SystemNavigationManager 用于當前視圖,并將后退按鈕設置為可見,如以下代碼所示:
~~~
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
? AppViewBackButtonVisibility.Visible;
~~~
SystemNavigationManager 類還展示了一個 BackRequested 事件,當用戶調用系統提供的用于后退導航的按鈕、手勢或語音命令時,該事件就會被觸發。這意味著,您可以處理這個單一事件,從而能夠在所有設備系列上一致地在您的應用中執行后退導航。
## Continuum 的優點
最后,但并非最不重要的是,我們要提到我們的一個個人收藏夾,供您參考: Windows 10 上的 Continuum。使用 Continuum,Windows 10 可以根據您想要執行的事項和執行的方式調整您的體驗。例如,如果您的應用運行在一個二合一的 Windows 電腦上,則在您的應用中實施 Continuum 可以讓用戶通過觸摸或使用鼠標和鍵盤來優化生產力。通過使用 UIViewSettings 類中的 UserInteractionMode 屬性,您的應用僅需借助一行代碼即可確定用戶是通過觸摸與視圖實現交互還是通過使用鼠標和鍵盤實現:
~~~
UIViewSettings.GetForCurrentView().UserInteractionMode;
// Returns UserInteractionMode.Mouse or UserInteractionMode.Touch
~~~
在檢測了交互模式之后,您可以優化應用的 UI,比如增加或減少邊距、顯示或隱藏復雜的功能等等。請查看 Lee McPherson 的 TechNet 文章“Windows 10 Apps: Leverage Continuum Feature to Change UI for Mouse/Keyboard Users Using Custom StateTrigger”(Windows 10 應用:利用 Continuum 功能為使用自定義 StateTrigger 的鼠標/鍵盤用戶更改 UI)([wndw.ms/y3gB0J](http://wndw.ms/y3gB0J)),此文章說明如何結合使用新的 StateTriggers 和 UserInteractionMode 構建您自己的自定義 Continuum StateTrigger。
## 自適應應用
能夠響應屏幕尺寸和方向變化的應用是很有用的,但為了實現受人關注的跨平臺功能,UWP 為開發人員提供了另外兩種類型的自適應行為:
* 通過檢測可用的 API 和資源,使自適應應用的版本響應不同版本的 UWP。例如,在繼續支持尚未升級的客戶的同時,您可能想讓您的應用使用一些較新的 API,而這些 API 僅存在于運行最新版本 UWP 的設備上。
* 平臺自適應應用可以對不同設備系列提供的獨特功能做出響應。因此,可以構建出可在所有設備系列上運行的應用,但當它在智能手機之類的移動設備上運行時,您可能想要使用一些特定于移動的 API。
如前所述,在使用 Windows 10 的情況下,絕大多數 UWP API 都完全可供任何應用使用,無論這些應用在什么樣的設備上運行。并且,與每個設備系列相關的特定 API 使開發人員可以進一步調整他們的應用。
自適應應用的基本思維模式是,您的應用會檢查它所需要的功能(或特性),并且只在該功能可用的情況下才使用。在過去,應用會檢查 OS 版本,然后再調用與該版本相關的 API。有了 Windows 10,您的應用就可以在運行時檢查某個類、方法、屬性、事件或 API 協定是否受當前操作系統的支持。如果支持,該應用將調用相應的 API。位于 Windows.Foundation.Metadata 命名空間中的 ApiInformation 類包含一些靜態方法(如 IsApiContractPresent、IsEventPresent 和IsMethodPresent),它們可用于查詢 API。例如:
~~~
using Windows.Foundation.Metadata;
if(ApiInformation.IsTypePresent("Windows.Media.Playlists.Playlist"))
{
? await myAwesomePlaylist.SaveAsAsync( ... );
}
~~~
此代碼有兩個作用。對是否存在 Playlist 類進行運行時檢查,然后在該類的一個實例調用 SaveAsAsync 方法。還要注意的是,通過使用 IsTypePresent API,可以輕松檢查當前操作系統上是否存在某個類型。在過去,這樣的檢查可能需要使用 LoadLibrary、GetProcAddress、QueryInterface、Reflection,或使用“動態”關鍵字等,具體取決于語言和框架。在進行方法調用時,還要注意強類型引用。當使用任何 Reflection 或“動態”時,您無法使用靜態編譯時診斷,該診斷可能會告知您是否拼錯了方法名等。
## 使用 API 協定進行檢測
API 協定本質上是一個 API 集。假設 API 協定可代表一個 API 集,其中包含兩個類、五個接口、一個結構、兩個枚舉等。我們將邏輯相關類型編組到一個 API 協定中。在許多方面,一個 API 協定代表一個功能 - 一個相關的 API 集可以一起提供某些特殊功能。Windows 10 往前的每個 Windows 運行時 API 都是某個 API 協定的成員。[msdn.com/dn706135](http://msdn.com/dn706135)?上的文檔介紹各種可用的 API 協定。您會看到,它們大多表示一組功能相關的 API。
使用 API 協定還為像您一樣的開發人員提供一些其他保障;最為重要的是,當平臺可實現某個 API 協定中的任一 API 時,該平臺一定可以實現該協定中的每個 API。換句話說,一個 API 協定就是一個原子單元,測試是否支持該 API 協定就相當于測試是否支持該集中的每一個 API。您的應用可以調用已檢測的 API 協定中的任何 API,而不必逐個檢查每個 API。
最大和最常用的 API 協定是 Windows.Foundation.UniversalApiContract。它包含了通用 Windows 平臺中幾乎所有的 API。如果您想查看當前 OS 是否支持 UniversalApiContract,您可以編寫以下代碼:
~~~
if (ApiInformation.IsApiContractPresent(
? "Windows.Foundation.UniversalApiContract"), 1, 0)
{
? // All APIs in the UniversalApiContract version 1.0 are available for use
}
~~~
目前 UniversalApiContract 唯一現存的版本是 1.0 版,因此該檢查略顯多此一舉。但 Windows 10 的未來更新版本中可能會引入其他 API,可能出現包括新的通用 API的 UniversalApiContract 2.0 版本。在將來,如果某個應用希望能在所有設備上運行,并且還希望使用新的 2.0 版本的 API,可以使用以下代碼:
~~~
if (ApiInformation.IsApiContractPresent(
? "Windows.Foundation.UniversalApiContract"), 2, 0)
{
? // This device supports all APIs in UniversalApiContract version 2.0
}
~~~
如果您的應用只需要從 2.0 版本調用單個方法,可以直接使用 IsMethodPresent 來檢查此方法。在這種情況下,您可以使用您認為是最簡單的任何方法。
除了 UniversalApiContract 之外,還有其他的 API 協定。大多數表示一個功能或一個 API 集:不是普遍存在于所有 Windows 10 平臺上,而是存在于一個或多個特定設備系列上。正如前面所提到的,您不再需要檢查設備的特定類型,然后推斷是否支持 API。只需檢查您的應用要使用的 API 集。
現在我可以重寫我原來的示例,用以檢查是否存在 Windows.Media.Playlists.PlaylistsContract,而不是僅僅檢查是否存在 Playlist 類:
~~~
if(ApiInformation.IsApiContractPresent(
? "Windows.Media.Playlists.PlaylistsContract"), 1, 0)
{
? // Now I can use all Playlist APIs
}
~~~
無論您的應用何時需要調用所有設備系列中并不存在的 API,您都必須添加一個對定義該 API 的相應擴展 SDK 的引用。在 Visual Studio 2015 中,前往“添加引用”對話框,然后打開“擴展”選項卡。在那里,您可以找到三個最重要的擴展: 移動擴展、桌面擴展以及 IoT 擴展。
但是,您的應用需要做的只是,檢查所需 API 協定是否存在,并有條件地調用相應的 API。沒有必要擔心設備的類型。現在的問題是: 我需要調用 Playlist API,但它不是一個普遍可用的 API。文檔 ([bit.ly/1QkYqky](http://bit.ly/1QkYqky)) 會告訴我此類在哪個 API 協定中。但哪個擴展 SDK 會定義它呢?
事實證明,Playlist 類(目前)僅在桌面設備上可用,而不適用于移動設備、Xbox 和其他設備系列。所以,您必須在任何原有代碼進行編譯之前將引用添加到桌面擴展 SDK 中。
Lucian Wischik 是 Visual Studio 團隊的成員和 MSDN 雜志的偶有撰稿人,他創建了一個可提供幫助的工具。該工具會在調用特定于平臺的 API 時分析您的應用代碼,驗證是否完成了與此相關的自適應檢查。如果未完成任何檢查,分析器就會發出警告,并提供便捷的“快速修復”項以將正確的檢查插入到代碼中,只需按下“Ctrl+Dot”或單擊燈泡圖標即可進行。(請參閱?[bit.ly/1JdXTeV](http://bit.ly/1JdXTeV),以了解詳細信息。) 分析器也可以通過 NuGet ([bit.ly/1KU9ozj](http://bit.ly/1KU9ozj)) 進行安裝。
我們來通過查看有關 Windows 10 自適應編碼的更完整示例進行總結。首先,以下是一些未正確自適應的代碼:
~~~
// This code will crash if called from IoT or Mobile
async private Task CreatePlaylist()
{
? StorageFolder storageFolder = KnownFolders.MusicLibrary;
? StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
? Windows.Media.Playlists.Playlist myAwesomePlaylist =
??? new Windows.Media.Playlists.Playlist();
? myAwesomePlaylist.Files.Add(pureRockFile);
? // Code will crash here as this is a Desktop-only call
? await myAwesomePlaylist.SaveAsAsync(KnownFolders.MusicLibrary,
??? "My Awesome Playlist", NameCollisionOption.ReplaceExisting);
}
~~~
現在,我們來看同樣的代碼,我們添加一行用來驗證目標設備上是否支持可選 API 的代碼行,然后再調用方法。這樣做可以防止發生運行時故障。請注意,您可能想進一步利用這個示例,在您的應用檢測到設備上不支持播放列表功能時,不顯示調用 CreatePlaylist 方法的 UI:
~~~
async private Task CreatePlaylist()
{
? StorageFolder storageFolder = KnownFolders.MusicLibrary;
? StorageFile pureRockFile = await storageFolder.CreateFileAsync("myJam.mp3");
? Windows.Media.Playlists.Playlist myAwesomePlaylist =
??? new Windows.Media.Playlists.Playlist();
? myAwesomePlaylist.Files.Add(pureRockFile);
? // Now I'm a safe call! Cache this value if this will be queried a lot
? if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
??? "Windows.Media.Playlists.Playlist"))
? {
????? await myAwesomePlaylist.SaveAsAsync(
??????? KnownFolders.MusicLibrary, "My Awesome Playlist",
??????? NameCollisionOption.ReplaceExisting);
? }
}
~~~
最后,以下是一個代碼示例,看上去可以訪問許多移動設備上顯示的專用照相機按鈕:
~~~
// Note: Cache the value instead of querying it more than once
bool isHardwareButtonsAPIPresent =
? Windows.Foundation.Metadata.ApiInformation.IsTypePresent(
? "Windows.Phone.UI.Input.HardwareButtons");
if (isHardwareButtonsAPIPresent)
{
? Windows.Phone.UI.Input.HardwareButtons.CameraPressed +=
??? HardwareButtons_CameraPressed;
}
~~~
請注意檢測步驟。如果我在使用臺式電腦時,對 CameraPressed 事件直接引用了 HardwareButtons 對象,而沒有檢查 HardwareButtons 是否存在,這可能會導致我的應用發生故障。
還有許多關于 Windows 10 中響應式 UI 和自適應應用的內容。您是否想了解更多? 請查看在 Build 2015 會議 ([wndw.ms/IgNy0I](http://wndw.ms/IgNy0I)) 上 Brent Rector 做出的有關 API 協定的精彩演講,并且務必要觀看內容詳盡的有關自適應代碼的 Microsoft Virtual Academy 視頻 ([bit.ly/1OhZWGs](http://bit.ly/1OhZWGs)),視頻中涵蓋了本主題的更多詳細信息。
* * *
Clint Rutkas?*是一位專注于開發人員平臺的 Windows 資深產品經理。他在 Microsoft 的“Halo at 343 Industries”和第 9 頻道從事工作,并構建出一些使用 Windows 技術的瘋狂項目,如計算機控制的迪斯科舞池、一個自定義的 Ford Mustang、T 恤射擊機器人等。*
Rajen Kishna?*目前在華盛頓雷德蒙德擔任 Microsoft 的 Windows 平臺開發人員市場營銷團隊的資深產品營銷經理。此前,他在荷蘭擔任 Microsoft 的顧問和技術推廣員。*
- 介紹
- Microsoft .NET - .NET 和通用 Windows 平臺開發
- 圖形和動畫 - Windows 組合支持 10 倍縮放
- 應用生命周期 - 通過后臺任務和擴展執行使應用處于活動狀態
- 通知 - Windows 10 中的自適應和交互式通知
- 應用集成 - 在 Windows 10 上鏈接和集成應用
- Visual Studio 工具 - NuGet 功能增強了 Windows 10 的開發功能
- UI 設計 - 通用 Windows 應用的響應式設計
- UI 設計 - 適用于 Windows 10 的自適應應用
- 數字墨跡 - Windows 10 中的墨跡交互
- 游戲開發 - 使用 Unity 為通用 Windows 平臺編寫游戲
- 結束語 - 歡迎使用 Windows 10 應用開發
- 編者寄語 - 從 3.0 開始的發展之路