Windows10 UWP開發 - 響應式設計
本篇隨筆與大家簡單討論一下在開發適配不同分辨率、寬高比的Windows10 Universal App布局時的可行方式與小技巧。經驗均從實踐中總結,可能有諸多不完善和淺薄之處,歡迎讀者嚴格指正。另外本文也只是拋磚引玉之用,希望能收獲更多更好的實戰經驗。
自適配的必要性
說了這么多,我們首先可能會問了,為什么要做響應式設計?其原因有以下兩點:
Windows10的跨平台性
Windows10是微軟宣稱可以統一運行於PC&平板&手機&Xbox等諸多平台的操作系統,當然這也是universal一詞的由來。如果你希望你的應用不僅僅局限於某一個平台,那么做一些響應式布局就非常必要了。
Win10 UWP的容器窗口可自定義大小
即使你的應用僅針對windows rt的平板用戶,win10對於uwp的全新處理方式也讓開發者不能免於分辨率適配。下圖分別是Win8.1和Win10的應用商店應用對比:
不難發現,為了增強store app的實用度,桌面環境下的uwp是可以自定義窗口尺寸的。為了讓應用別輕易被用戶玩崩壞,整理整理儀容還是相當有必要的。
廢話少說,接下來,我們來看看手中能用於做響應式設計的手段吧。
自適配布局的方法
與網頁設計類比
不知道讀者們有多少有html+css布局經歷,這一套東西盡管在布局上不算完美,但用起來也算得心應手,熟悉后能使網頁布局有基本良好的適應性。
所以這里我就先說說網頁前端布局與XAML布局的區別與聯系吧。好消息是,一言以蔽之:幾乎HTML能做的、XAML都可以。具體地說,幾種在網頁布局上常見手法xaml實現如下:
順序布局
也就是沒有布局咯…對於結構簡單的內容呈現(比如本篇博客),用這種方式足矣。如同下面的代碼模型一樣
<!DOCTYPE html> <html> <body> <p style="font-size:70px">I am title</p> <div style="width:100px;height:100px;background:rgb(230,230,250);display:inline-block;"></div> <span style="font-size:30px">Inline text</span> <div style="font-size:32px"> The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. </div> </body> </html>
所有的元素從上到下、左到右排列,排列不下的則進入下一行。該效果使用HTML實現非常簡單,由於大部分html元素是inline的,故直接將其從上到下排列即可。
在XAML中,事情會麻煩一點,由於所有元素均不占位而浮動於界面上,故需要借助其他控件的幫助:Grid可用來從上到下安排元素,而對於同一行的元素則可以用stackpanel安排。如果需要響應度更高的布局如當寬度不夠時自動換行等特性,則需配合ListView與ItemsWrapGrid進行布局。詳細代碼模型見下,十分簡單,故不再贅述。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Orientation="Vertical" Margin="70,70,70,10"> <TextBlock Text="I am title." FontSize="70"/> <StackPanel Orientation="Horizontal"> <Grid Background="Lavender" Width="100" Height="100"/> <TextBlock Text="Inline text." VerticalAlignment="Bottom" FontSize="30"/> </StackPanel> <TextBlock Text="The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog." TextWrapping="Wrap" FontSize="32"/> </StackPanel> </Grid>
Absolute大法
有一種說法叫以不變應萬變。
不管在什么情況下,這種布局方式是通配而普遍的:設計時固定頁面尺寸,按照固定位置對每一個元素進行布局,如果框架過小則使用滾動條瀏覽,框架過大則自動居中。反映在html中,則是無處不在的position:absolute。
這樣的優點在於方便設計,能最大程度(或者說,絲毫不差)復現designer的設計,且節省工作量(如對於電腦設計一個1366x768的布局后通用全部窗口大小)。缺點則很明顯:缺乏靈活性,用戶不友好,故只對於特定業務適用。
<div style="width:500px;height:500px;background:rgb(230,230,250);position:relative;"> <div style="width:130px;height:130px;background:#332CC7;position:absolute;left:20px;top:20px;"> </div> <div style="width:310px;height:310px;background:#4CA5FF;position:absolute;left:170px;top:170px;"> </div> <div style="width:310px;position:absolute;left:170px;top:75px;font-size:28px;"> The quick brown fox jumps over the lazy dog. </div> <div style="width:150px;position:absolute;left:20px;top:170px;font-size:28px;"> The quick brown fox jumps over the lazy dog. </div> </div>
UWP利用Canvas控件實現絕對布局,其內部元素均利用Canvas.left/Canvas.top與頂部對齊。
<Canvas Background="Lavender" Margin="70,70,70,70" Width="500" Height="500"> <Grid Background="#FF332CC7" Width="130" Height="130" Canvas.Left="20" Canvas.Top="20"> </Grid> <TextBlock Canvas.Top="75" Canvas.Left="170" Width="310" Text="The quick brown fox jumps over the lazy dog. " TextWrapping="Wrap" FontSize="28"/> <Grid Background="#FF4CA5FF" Width="310" Height="310" Canvas.Left="170" Canvas.Top="170"> </Grid> <TextBlock Canvas.Top="170" Canvas.Left="20" Width="150" Text="The quick brown fox jumps over the lazy dog. " TextWrapping="Wrap" FontSize="28"/> </Canvas>
流式布局
流式布局是網頁設計中挺老的概念了,將布局分解成區塊,區塊大小與區塊間距均按頁面總體尺寸百分比變化,從而適應不同分辨率的窗口。而對於區塊內部細節,則可根據內容和需求進行定制。
該布局方式在xaml中實現十分簡單,也並非windows10的新東西,只需要在Grid的寬高定義時用*標明百分比即可。
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid.ColumnDefinitions> <ColumnDefinition Width="5*"/> <ColumnDefinition Width="90*"/> <ColumnDefinition Width="5*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="5*"/> <RowDefinition Height="90*"/> <RowDefinition Height="5*"/> </Grid.RowDefinitions> <Grid Grid.Column="1" Grid.Row="1" Background="Lavender"> <Grid.ColumnDefinitions> <ColumnDefinition Width="35*"/> <ColumnDefinition Width="10"/> <ColumnDefinition Width="64*"/> </Grid.ColumnDefinitions> <Grid Grid.Column="1" Background="White"> </Grid> <Grid Grid.Column="2"> <Grid.RowDefinitions> <RowDefinition Height="50*"/> <RowDefinition Height="10"/> <RowDefinition Height="50*"/> </Grid.RowDefinitions> <Grid Grid.Row="1" Background="White"> </Grid> </Grid> </Grid> </Grid>
實現效果如下:
XAML獨具特色的新方式
XAML自身一些具有特色的屬性/控件能進一步幫忙簡化響應式布局,力求做到不用代碼。
Viewbox
Viewbox是一個控件,會將其容納的對象渲染成指定的寬高,在布局很難做出改變且需要適應的變化並不大時可以使用該方法。
關於其具體使用的tips&tricks我們會另起一篇日志介紹。
Rendertransform
為UIElement的一個屬性,會將對象在渲染層面按某種規則進行變化,如拉伸/翻轉等。需要注意的是,所謂渲染層面,表現在與該控件有關的屬性是不會察覺的:如width/height屬性,將並不會知曉作用於其上的拉神操作。
如下面這段代碼將伸縮Grid的寬高:
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <Grid Height="500" Width="450" HorizontalAlignment="Left" VerticalAlignment="Top"> <Grid.RenderTransform> <ScaleTransform x:Name="scaler" CenterX="0.5" CenterY="0" ScaleX="1.3" ScaleY="1.3" /> </Grid.RenderTransform> </Grid> </Canvas>
配合着canvas.left/top屬性以及一些代碼,將相當容易地自制一個viewbox。
RelativePanel+VisualStateManager
二者均為Windows10引入的新特性,前者用於允許其子元素按相對位置(附着下方、附着於上方、左右對齊等方式進行排版且在布局有變時自動調整保證其滿足給定的位置關系);后者則定義多組VisualState,每一個state對應頁面上不同控件的一些屬性值,而在對應的state條件滿足時自動將屬性切換至目標值。此兩點結合,將可以在很多情況下不用額外的C#代碼實現響應布局。
關於此工具的具體用法,在之前發布的針對Build2015文章UWP?UWP! - Build 2015有些啥?(2)中已經提及,此處不再贅述。
Code動態調整
上面的方法都不好使?那就用code吧,麻煩是麻煩一點,其使用起來是最自由和可定制的,只要開發者心中有布局目標,就能滿足一切排版需求。但是在用Code調整布局的過程中,還是有一些注意事項值得嘮叨嘮叨的:
事件驅動動態調整
顯然,調整布局的code是事件驅動而不是一直運行的,那么寫在什么事件里呢?首先大家應該能想到SizeChanged。是的,SizeChanged事件發生於對應目標的尺寸發生變化之后,也就是說,在該事件被觸發時,保證能獲取正確的寬高、從而根據獲取到的數據進行動態布局,故將代碼寫進該事件是完全正確的選擇。需要注意的是,位置的變化(如canvas.left值改變)不會觸發該事件。另外需要注意的一點是,在事件中寫入的代碼不能直接/間接修改自身相關的尺寸,否則引發雪崩效應,可能帶來stackoverflow。
LayoutUpdated也可以用於尺寸變化檢測,但是它是一個更普遍的事件:在布局樹上的元素發生改變就會觸發該事件,且該事件並不指定觸發對象,如果跟蹤該事件將發現無論窗口發生怎么樣的改變,都將觸發海量的LayoutUpdated事件。故將代碼寫於其中並不明智。
ActualSize的獲取時機
在根據尺寸動態布局的的代碼中少不了獲取ActualSize,但需要明確的是在布局剛做出改變時ActualSize是不會生效的,直接以該值作為基准勢必導致錯誤。解決方案有2:1.如上文所言,將事件寫於SizeChanged中,或將部分代碼依賴該事件觸發;2.強制調用UpdateLayout()方法,同步更新界面,從而能保證在該調用后獲得的實際尺寸為真實尺寸。
總結
布局方法千千萬萬,本文只從最小的幾個地方入手簡要介紹,若需做出真正用戶友好、貼近業務需求的響應式布局,還需綜合應用各種布局手段,了解各手段間優劣以及其最適合的應用場景,協同作用以創造好的布局。