Windows10 UWP开发 - 响应式设计

时间:2021-06-13 03:44:04
 

Windows10 UWP开发 - 响应式设计

本篇随笔与大家简单讨论一下在开发适配不同分辨率、宽高比的Windows10 Universal App布局时的可行方式与小技巧。经验均从实践中总结,可能有诸多不完善和浅薄之处,欢迎读者严格指正。另外本文也只是抛砖引玉之用,希望能收获更多更好的实战经验。

自适配的必要性

说了这么多,我们首先可能会问了,为什么要做响应式设计?其原因有以下两点:

Windows10的跨平台性

Windows10是微软宣称可以统一运行于PC&平板&手机&Xbox等诸多平台的操作系统,当然这也是universal一词的由来。如果你希望你的应用不仅仅局限于某一个平台,那么做一些响应式布局就非常必要了。

Win10 UWP的容器窗口可自定义大小

即使你的应用仅针对windows rt的平板用户,win10对于uwp的全新处理方式也让开发者不能免于分辨率适配。下图分别是Win8.1和Win10的应用商店应用对比:
Windows10 UWP开发 - 响应式设计  Windows10 UWP开发 - 响应式设计
不难发现,为了增强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的,故直接将其从上到下排列即可。

Windows10 UWP开发 - 响应式设计

在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>

实现效果如下:

Windows10 UWP开发 - 响应式设计

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值改变)不会触发该事件。另外需要注意的一点是,在事件中写入的代码不能直接/间接修改自身相关的尺寸,否则引发雪崩效应,可能带来*。

LayoutUpdated也可以用于尺寸变化检测,但是它是一个更普遍的事件:在布局树上的元素发生改变就会触发该事件,且该事件并不指定触发对象,如果跟踪该事件将发现无论窗口发生怎么样的改变,都将触发海量的LayoutUpdated事件。故将代码写于其中并不明智。

ActualSize的获取时机

在根据尺寸动态布局的的代码中少不了获取ActualSize,但需要明确的是在布局刚做出改变时ActualSize是不会生效的,直接以该值作为基准势必导致错误。解决方案有2:1.如上文所言,将事件写于SizeChanged中,或将部分代码依赖该事件触发;2.强制调用UpdateLayout()方法,同步更新界面,从而能保证在该调用后获得的实际尺寸为真实尺寸。

总结

布局方法千千万万,本文只从最小的几个地方入手简要介绍,若需做出真正用户友好、贴近业务需求的响应式布局,还需综合应用各种布局手段,了解各手段间优劣以及其最适合的应用场景,协同作用以创造好的布局。