Design and UI > Layout

Page layout

在UWP应用中,一个Page常常有导航、命令以及内容元素。应用可以有多个Page,通过Frame组织在一起。Frame的直接上级就是跟视窗操作相关的Window。

如何在不同的页面中导航,时页面布局中首先需要考虑的事情。有两个常用的模式:左部导航或者顶部导航。Navigation design basics for UWP apps

对于超过五个页面的,建议使用左部导航nav pane。左部导航通常有个菜单键,可以用来控制导航栏的显示大小(收起还是折叠)。

顶部导航适合页面比较少的情况,和左部导航不同的是,顶部导航不可以收起。

对于每个页面,通常会有一个命令栏command bar,来放置页面常用的命令。命令栏可以放置在页面的顶部或者底部。

不同的页面,对内容的需求是不一样的。有几种常见的内容排布方式:

  • 初始页:也就是应用打开时显示的页面
  • 集合页:将内容以集合的方式显示,通常采用listview和gridview来呈现
  • 主辅页:页面分为主辅部分,主部显示集合,辅部显示细节master/details
  • 表单页:页面由多个输入控件form组成,主要用于收集数据

Sample apps列举了一些应用示例。

Screen sizes and breakpoints

从设计上UWP应用需要适应不同的屏幕尺寸,最基本的,要考虑以下屏幕尺寸(也叫breakpoints尺寸分隔)

  • Small(不超过640px)
  • Medium(641px到1007px)
  • Large(1008px往上)

值得注意的是,因为有标题栏这些部件的存在,屏幕尺寸不直接等于应用内容尺寸。

分隔点更详细介绍:

  • Small,常见的窗口大小:320x569、360x640、480x854
    • 屏幕尺寸在4英寸到6英寸的手机
    • 或者20英寸到65英寸的电视
  • Medium,常见的窗口大小:960x540
    • 7英寸到12英寸的大屏手机或者平板
  • Large,常见的窗口大小:1024x640 1366x768,1920x1080
    • 13英寸或者更大的计算机屏幕

虽然电视的屏幕很大,可能也有很高的物理分辨率。但是由于观看距离远,所以采用的逻辑分辨率比较低,保证App在远处操作的时候容易被看清。

关于Windows自带的DPI缩放,可以在 Settings > Display > Scale and layout中查看。

一些推荐:

  • Small
    • 将页面的左右外边距设置成12px,跟窗口边缘产生一定差距
    • 将命令栏图标放置在页面底部
    • 用单栏或者单区域显示内容
    • 将搜索栏折叠成图标,采用点击触发
    • 如果采用了navigation pane ,将其设置成收缩模式(只显示图标)
    • 如果采用了主辅结构,建议使用stacked presentation mode
  • Medium
    • 将页面的左右外边距设置成24px
    • 将命令栏图标放置在页面顶部
    • 最多使用两栏或者双区域显示
    • 显示搜索框
    • 如果采用了navigation pane ,将其设置成silver mode
    • 建议对TV采用特殊考虑TV experiences
  • Large
    • 将页面的左右外边距设置成24px
    • 将命令栏图标放置在页面顶部
    • 最多使用三栏或者三区域显示
    • 如果采用了navigation pane ,将其展开成最大化模式

Continuum for Phones,可以把手机连到一个屏幕上,所以手机可以使用的屏幕大小会改变。

Responsive design techniques

UWP支持响应式设计以适应不同设备:

  • 在屏幕尺寸吃紧的情况下,将导航部分收起
  • 更好匹配设备,比如使用设备上的摄像和感知设备
  • 重排UI控件,方便用户输入

很多UWP controls已经自带响应式设计。

响应式设计通常有几个技术方法:

  • Reposition,也就是重排控件
  • Resize,调整控件本身或者边距的大小
  • Reflow,增减显示区域或者栏数
  • Show/hide,显示或者隐藏控件
  • Replace,将控件替换成对等的,但是对屏幕空间需求不一样的控件
  • Re-architect,切换页面架构,比如从单页切换到主辅

UWP controls and patterns

Responsive layouts with XAML

XAML为响应式设计提供了大量的支持。

XAML支持静态设计,以及采用布局部属和面板来达成流动式设计。

布局部属可以用来控制元素的大小和位置,流动式设计的要点之一就是不要指定绝对的大小和位置,而是通过百分比或者填充的方式来动态变化。

比如说元素的Height和Width两个部属可以设置成绝对像素值,也可以设置成【自动】或者【等分】。【自动】意味着从元素的内容来推断元素的大小;【等分】意味着从父元素的尺寸来推断自身的大小。

【等分】用符号'‘表示,可以让一个元素具有更多的等分,比如将其设置为'2',意味着获得两个【等分】。

下面是一些例子:

  • Column_1的尺寸是Auto,表示从子内容推断大小
  • Column_2的尺寸是’*',表示它会获得一个等分的大小
  • Column_3的尺寸是44,意味着它会获得44个逻辑像素
  • Column_4的尺寸是'2*',表示它会获得两等分

在运行的时候,通过ActualHeight和Width可以获取具体获得的像素值。通过MinWidth/MaxWidth,以及MinHeigh/MaxHeight可以对元素的具体大小加以限制。

通过HorizontalAlignment(Left、Center、Right、Stretch)和VerticalAlignment(Top,Center,Bottom、Stretch)可以设置应用的对其。

可以将元素的Visibility设置成Visible 或者Collapsed,来控制其是否显示。这个部属的效果会扩展到子控件。

逐个设置元素的部属会比较麻烦,可以通过样式来同时给多个元素设置属性。参考Styling controls

响应式设计是以面板为基础的。面板是一类UI元素,比如Canvas, Grid, RelativePanel或者StackPanel。面板的作用是把子内容聚合起来,以适配一种显示方式。下面是不同面板类的对比:

  • Canvas,让你可以自行定位其子元素
    • 元素在Canvas里面是采用绝对定位,使用Canvas.Top以及Canvas.Left这两附着部属来指定
    • 元素的层叠可以使用Canvas.ZIndex来指定、
    • HorizontalAlignment/VerticalAlignment如果指定为Stretch,那么就会被忽略
    • 元素的大小如果没有指定,那么则采用他们内容的大小
    • 超出边框的元素不会被裁剪,允许溢出
    • 元素的内容不会被限制在边界内
  • Grid,网格化管理子内容,可以方便重排内容
    • 通过Grid.Row和Grid.Column可以指定子元素所在的网格
    • 元素可以跨多个行或者列,通过Grid.RowSpan和Grid.ColumnSpan指定
    • 支持HorizontalAlignment/VerticalAlignment的Stretch值
    • 超出范围的子元素会被裁剪
    • 元素内容被限定在区域内,超过会显示滚动条
  • RelativePanel
    • 子元素相对于边界,或者相对于彼此排布
    • Stretch对齐会被忽略,除非设置允许Stretch (比如一个元素与左右两个边界对齐)
    • 子内容超出会被裁剪,会显示滚动条
  • StackPanel
    • 元素排成一列
    • 在指定走向的垂直方向上,支持Stretch对齐
    • 元素超出显示区域会被裁剪
    • 元素大小可以超过范围,也就是不显示滚动栏。如果需要显示滚动栏,需要显示设置元素大小。
  • VariableSizedWrapGrid
    • 和Grid一样,不过支持自动换行(列),当达到设定的最大行(列)数的时候
    • 和StackPanel一样,需要设置走向
    • Stretch对齐不可用,尺寸由ItemHeight 和ItemWidth决定。如果没有设置,那么由第一个子元素的大小决定。
    • 裁剪子元素,超出显示滚动条

一个例子:Responsive techniques sample

面板在逻辑上将多个子元素或者控件组织在了一起。合理使用面板可以方便地自动调整UI元素的大小、位置甚至是流向。如果需要更多调整,那么可能需要用到【可视状态】。

在响应式设计里面,当你的UI窗口到达一定大小之后,或许你需要调整布局设置。【可视状态】可以用来帮你定义不同大小状态下的布局设置。

首先,要有一个AdaptiveTrigger来设置窗口大小的变化临界值,然后一个VisualState定义一个跟UI元素跟这个AdaptiveTrigger相对应的布局设置。多个VisualState可以以VisualStateGroup 的方式在一个VisualStateManager 中管理。

调用VisualStateManager.GoToState可以在代码中切换【可视状态】。可以侦听窗口的SizeChanged 事件来决定什么时候切换【可视状态】。在Set visual states in code可以看到一个具体的例子。

Windows没有给应用提供方法来检查当前正常运行的设备是什么设备。

在Win10之前,在【可视状态】修改布局设置,需要通过Storyboard。从Win10开始,可以用简化的Setter语法,并且可以在XAML中使用StateTrigger Set visual states in XAML markup中带有一个例子。

使用StateTrigger的时候,要保证VisualStateManager.VisualStateGroups在页面的第一个子元素上触发。

当设置附着部属的时候,需要在附着的部属名字上加上括号:

<!-- Set an attached property using ObjectAnimationUsingKeyFrames. -->
<ObjectAnimationUsingKeyFrames
    Storyboard.TargetProperty="(RelativePanel.AlignHorizontalCenterWithPanel)"
    Storyboard.TargetName="myTextBox">
    <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>

<!-- Set an attached property using Setter. -->
<Setter Target="myTextBox.(RelativePanel.AlignHorizontalCenterWithPanel)" Value="True"/>

可以从StateTrigger 扩展并创建自定义的触发器。可以定义这样一种触发器,当输入框获得焦点的时候,修改输入框的颜色。自定义触发器的例子:State triggers sample

可以在StateTrigger里面使用样式,参考Visual states and styles

针对不同设备的UI差别可能很大,有时候为每个设备使用单独的UI定义文件更方便。比如针对不同的设备加载不同的Page。

在Visual Studio增加一个XAML VIEW,只需为项目增加新的条目,并选择条目类型为XAML template type中的XAML View。这个XAML View默认不带有背靠代码,并且名字中带有设备名称(参考 ResourceContext.QualifierValues),具体命名的格式为: [pageName].DeviceFamily-[qualifierString].xaml,例子:

  • MainPage.DeviceFamily-Desktop.xaml
  • MainPage.DeviceFamily-Tablet.xaml

也可以用目录来区分:

  • DeviceFamily-Desktop/MainPage.xaml
  • DeviceFamily-Tablet/MainPage.xaml

当设备类型不匹配时,默认d的MainPage.xaml会启用

也可以创建带背靠代码的XAML template type中的Blank page,但是这样你的代码逻辑要选择何时加载何种页面:

if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Tablet")
{
    rootFrame.Navigate(typeof(MainPage_Tablet), e.Arguments);
}
else
{
    rootFrame.Navigate(typeof(MainPage), e.Arguments);
}

Tailored multiple views sample例子采用GetIntegratedDisplaySize来判断加载的Page类型。

show multiple views

略过

Alignment, margin, padding

大部分UI元素都是从FrameworkElement 继承。FrameworkElement 定义了很多跟尺寸、对齐、外边距、内边距等可以影响布局的部属。

尺寸相关:

  • Height和Width指定乐元素的大小,默认值为NaN。你可以设置为绝对的值(以effective pixels衡量),或者设置为自动大小或者按比例分配。
  • ActualHeigh和ActualWidth这两个只读部属提供运行时的真实大小。在SizeChanged事件中,这两个值会随着改变。注意RenderTransform 不会改变这两个值。
  • MinWidth/MaxWidth和MinHeigh/MaxHeigh用来给元素大小增加约束
  • FontSize和其他文字部属用来控制文字元素的显示。文字元素没有具体指定的大小,但是有ActualHeight和ActualWidth。

对齐相关:

  • HorizontalAlignment和VerticalAlignment 指定横向对齐和竖向对齐的
  • 默认的对齐方式时Stretch,这导致元素的上下左右边界和容器的大小对齐。如果Heigh和Width指定了数值,那么默认的Stretch就会被替换成Center。有一些元素,比如按键,其样式会覆写默认的Stretch值。
  • HorizontalContentAlignment和VerticalContentAlignment 指定元素内容的对齐方式
  • 对齐方式会影响裁剪效果。比如左对齐的元素,如果超出了实际容器大小(ActualWidth),那么其右边超出的部分会被裁剪。
  • 文本元素使用TextAlignment来指定对齐。建议不要修改默认的左对齐,更多信息参考Typography

外边距相关

  • 外边距处于元素周围,但是其实不属于元素,并不会影响元素的ActualHeight和ActualWidth。外边距上触发的事件(比如点击)不属于对应元素。
  • 外边距可以统一指定,比如Margin=“20"代表所有方向的外边距都是20;也可以单独指定,Margin=“0,10,5,25“指定了左上右下的外边距
  • 元素间的外边距可以合并,如果两个元素的外边距都是10,那么这两个元素靠在一起的话,间隔是20
  • 外边距可以为负值,但是会引起意想不到的想过,使用的时候要谨慎。
  • 外边距属于优先级较次的约束,其他约束比如容器大小会先使用上。另外,由于外边距的存在,可能会导致元素的尺寸缩为零。

内边距

  • 内边距,用于在元素内部指定内容到边框的距离。和外边距不一样,内边距不是FrameworkElement的部属。不同的类可以定义自己的内边距。
  • Control.Padding,被所有控件继承,如果控件没有内容,那么内边距不会生效。
  • Border.Padding,从边框(BorderThickness/BorderBrush指定)到Child元素的间距
  • ItemsPresenter.Padding,
  • TextBlock.Padding和RichTextBlock.Padding,应用在文本元素上。值得注意的是,文本元素没有背景,所以无法显现内边距。建议使用Block 容器的Margin部属。

一些建议:

(本篇完)