六、创建自适应布局

在第三十章,我向你展示了如何使用 Metro 对单页内容模型的支持来为你的应用创建布局。在这一章中,我将向您展示如何使布局适应不同的视图和方向。大多数 Windows 8 设备上都有视图,允许用户选择不同的方式与应用交互,包括并排运行两个 Metro 应用。方位出现在可以保持在不同位置或容易旋转的设备上,并且这些设备配备有传感器来报告它们的状态。你需要仔细考虑如何在应用中容纳不同的视角和方向,以创造一流的地铁体验。

我还将向您展示如何处理高像素密度显示器。这些显示器在平板电脑和手机平台上越来越常见,为用户提供了比传统硬件更清晰的显示器。在大多数情况下,Windows 8 会为您处理像素密度,但有一个关键的例外需要注意:位图图像。我解释了 Windows 8 如何接近像素密度,并向您展示 Metro 功能,这些功能可以帮助您为正在使用的硬件呈现正确的分辨率位图。表 6-1 对本章进行了总结。

images

images

创建示例项目

我已经创建了一个名为AppViews的示例项目,这样我可以在本章中演示不同的特性。我再次使用了 Visual Studio Blank App项目模板。你可以在清单 6-1 的中看到我对default.html所做的添加,我将把它用作我的 HTML 母版页。

清单 6-1 。AppViews 项目中 default.html 文件的内容

`<!DOCTYPE html>

         AppViews

              

              

    
        
Top Left
        
Top Right
        
Bottom Left
        
Bottom Right
    

`

我使用 CSS grid特性创建了一个 2 乘 2 网格的主布局。网格将包含在 id 为gridContainerdiv元素中,每个子元素包含一个标签来指示其位置。你可以在清单 6-2 中看到我用来创建布局的 CSS 属性,它显示了css/default.css文件。还有第二个链接到default.html的 CSS 文件——这个文件叫做views.css。它目前是空的,我将在本章后面回到它。

清单 6-2 。default.css 文件

`#gridContainer {     display: -ms-grid;     -ms-grid-rows: 1fr 1fr;     -ms-grid-columns: 1fr 1fr;     height: 100%;     font-size: 40pt; }

gridContainer > div {

border: medium solid white;     padding: 10px; margin: 1px; }

topLeft {

-ms-grid-row: 1; -ms-grid-column: 1;     background-color: #317f42; }

topRight {

-ms-grid-row: 1; -ms-grid-column: 2;     background-color: #5A8463; }

bottomLeft {

-ms-grid-row: 2; -ms-grid-column: 1;     background-color: #4ecc69;     }

bottomRight {

-ms-grid-row: 2; -ms-grid-column: 2;     background-color: #46B75E; }

span, button, img {     font-size: 25pt;     margin: 5px;     display: block; }

testImg {

width: 100px;     height: 100px; }`

images 提示我保留了 Visual Studio 创建的js/default.js文件。我将回到这个文件,并在本章的后面向您展示它的内容。

这些文件产生了一个简单的应用,它有四个彩色象限,如图 6-1 所示。在本章的剩余部分,我将使用这个应用来解释应用可以显示的不同视图,以及如何适应它们。

images

图 6-1。默认视图中显示的示例应用

了解地铁景观

Metro 应用可以以四种视图之一显示。图 6-1 显示了全屏横向视图中的应用,其中整个显示屏专用于示例应用,屏幕的最长边位于设备的顶部和底部。还有另外三个视图需要你处理:全屏人像抓拍填充

全屏纵向视图中,你的应用占据了整个屏幕,但是最长的边在设备的左右两边。在截图中,应用以 320 像素宽的条状显示在屏幕的左边缘或右边缘。在填充视图中,除了被抓拍的应用占据的 320 像素条之外,应用显示在整个屏幕上。仅当显示器的水平分辨率为 1366 像素或更高时,才支持对齐和填充视图。对于当今的大多数设备来说,这意味着只有当设备处于横向方向时,填充和对齐视图才可用,但这不是必需的,并且在屏幕足够大的情况下,设备将能够在横向和纵向方向上对齐和填充。你可以在图 6-2 中的截图和填充视图中看到示例应用。

images 提示在填充视图和捕捉视图之间切换的最简单方法是按下Win + .(句点键)。

images

图 6-2。截图和填充视图中的示例应用

如您所见,Metro 应用的默认行为只是适应任何可用的空间。在我的示例应用中,这意味着可用空间在我的网格中的列间平均分配。当你的应用在填充视图中时,这通常不是那么糟糕,因为 320 像素并不是屏幕空间的巨大损失。当你的应用在快照视图中时,它会有更大的影响,因为 320 像素根本不是很大的空间。如图所示,我的示例被压缩到可用空间中,没有完全显示文本。

images 注意图中另一个 app 报告其当前视图。我在这本书的源代码下载中包含了这个应用,以防你会觉得它有用——这个应用叫做PlaceHolder,在本章的文件夹中。它使用的特性和功能与我在本章中描述的相同,这也是我没有列出代码的原因。

用户决定他想要哪个视图以及何时想要。你不能创建一个只在特定视图下工作的应用,所以你需要花时间让你的应用布局以一种有意义的方式适应每个视图。有不同的方法来适应这些视图,我将在接下来的小节中带您了解它们。

images 注意理解这一章的最好方法是跟随并像我一样构建应用。这将让你看到应用响应视图变化的方式,这是静态截图无法正确捕捉的。

使用 CSS 适应视图

适应不同视图的第一种方法是使用 CSS。微软已经定义了一些特定于 Metro 的 CSS media规则,当应用从一个视图移动到另一个视图时会应用这些规则。你可以在清单 6-3 中看到这四个规则,它显示了我前面提到的css/views.css文件。

清单 6-3 。响应 views.css 文件中的视图更改

`@media screen and (-ms-view-state: fullscreen-landscape) { }

@media screen and (-ms-view-state: fullscreen-portrait) {}

@media screen and (-ms-view-state: filled) {     #topLeft {         -ms-grid-column-span: 2;     }

#topRight {         -ms-grid-row:  2;     }

#bottomRight {         display: none;     } }

@media screen and (-ms-view-state: snapped) {     #gridContainer {         -ms-grid-columns: 1fr;     } }`

至少在我看来,这是 Metro 和支撑它的标准 web 技术之间最好的接触点之一。CSS media规则简单而优雅,通过定义少量特定于 Metro 的属性,微软使得响应不同的视图变得非常容易。

我经常与微软斗争,我认为它倾向于忽视或扭曲公认的标准,但我不得不称赞该公司对 Metro 采取了更温和的态度。我已经为两个media规则定义了属性,我将在下面的章节中解释。

当 Visual Studio 创建一个 CSS 文件作为新项目的一部分时,它会添加四个对应于四个视图的media规则。这通常在default.css文件中,但是对于这个项目来说,将它们移到views.css更适合我。仅当您的应用显示在相应视图中时,您在每个规则中定义的样式才有效。通常的 CSS 优先规则适用,这意味着规则通常被定义为项目的 CSS 文件中的最后一项。如果您使用一个单独的文件来定义规则,就像我对示例项目所做的那样,那么您需要确保导入 CSS 的link元素出现在最后,就像我在default.html文件中所做的那样:

`...

...`

适应填充视图

大多数应用可以容忍屏幕丢失 320 像素,没有太多问题。如果你创建了一个布局不能自动适应的应用,你可以在–ms-view-state属性值为filled时应用的media规则中定义样式。为了演示如何适应填充视图,我重新定义了一些 CSS 网格属性,这些属性应用于填充默认横向视图中每个象限的div元素:

`... @media screen and (-ms-view-state: filled) {     #topLeft {         -ms-grid-column-span: 2;     }

#topRight {         -ms-grid-row:  2;     }

#bottomRight {         display: none;     } } ...`

CSS 网格布局和媒体规则的结合使您在适应特定视图时可以轻松地应用全面的更改。对于这个视图,我改变了布局,使四个div元素中的三个可见,扩展了一个div元素,使其跨越两列,并将第三个元素重新定位到网格中的不同位置。你可以在图 6-3 中看到效果。

images

图 6-3。使用 CSS 网格调整布局以适应填充的视图

适应快照视图

快照视图通常需要更多的思考。你需要在那个 320 像素的长条中放一些有用的东西,但是整个应用的布局通常放不下。我的首选方法是在应用处于快照视图时切换到仅显示信息的视图,并在用户与我的应用交互后立即脱离该视图。在本章的后面,我将向你展示如何改变你的应用的视图。

不管你用什么方法,你都必须面对这样一个事实:与整个屏幕相比,你的空间相对较小。在我的示例应用中,我通过改变我的 CSS 网格来做出响应,这样它只有一列——这具有在网格的其余部分隐藏内容的效果,使用了清单 6-4 中所示的属性。

清单 6-4 。适应快照视图

... @media screen and (-ms-view-state: snapped) { **    #gridContainer {** **        -ms-grid-columns: 1fr;** **    }** } ...

你可以在图 6-4 中看到结果。

images

图 6-4。使用 CSS 网格来适应抓取的视图

使用 JavaScript 适应视图

我喜欢 CSS 适应视图的方法,但是它只能让我到此为止——例如,我不能用它来改变元素的内容。对于更广泛的变化,您可以用一些 JavaScript 代码来补充您的 CSS media规则。视图相关的功能包含在Windows.UI.ViewManagement名称空间中。这是我在本书中第一次使用 Windows API 的功能,而不是 WinJS API。Windows API 在 HTML/JavaScript Metro 应用和用 Microsoft 编写的应用之间共享。NET 技术,如 XAML/C#。因此,一些方法和事件的命名可能会有点笨拙。在接下来的小节中,我将向您展示如何检测当前视图并在视图改变时接收通知。

检测当前视图

您可以通过读取Windows.UI.ViewManagement的值来找出应用当前显示在哪个视图中。ApplicationView.value属性(正如我说过的,Windows API 中的一些命名有点奇怪)。该属性返回一个与Windows.UI.ViewManagement中的值相对应的整数。ApplicationViewState枚举,如表 6-2 所示。

images 提示 Metro 枚举有点像名称空间和接口。它们在 JavaScript 中实际上没有多大意义,但是它们使得像 C#等其他 Metro 语言一样使用 Windows API 中的对象成为可能。在 JavaScript 中,它们被表示为对象,这些对象的属性定义了一组预期或支持的值。

images

我已经在default.js文件中添加了一些代码,这样其中一个网格元素就会显示当前的方向,如清单 6-5 所示。

images 提示我不想在我的代码中一直输入Windows.UI.ViewManagement,所以我创建了一个名为 view 的变量作为名称空间的别名——您可以在清单中看到强调这一点的语句。

清单 6-5 。在 JavaScript 中获取并显示当前方向

`(function () {     "use strict";

var app = WinJS.Application;     var view = Windows.UI.ViewManagement;

app.onactivated = function (eventObject) {         topRight.innerText = getMessageFromView(view.ApplicationView.value);     };

    function getMessageFromView(currentView) {         var displayMsg;         switch (currentView) {             case view.ApplicationViewState.filled:                displayMsg = "Filled View";                 break;             case view.ApplicationViewState.snapped:                 displayMsg = "Snapped View";                 break;             case view.ApplicationViewState.fullScreenLandscape:                 displayMsg = "Full - Landscape";                 break;             case view.ApplicationViewState.fullScreenPortrait:                 displayMsg = "Full - Portrait";                 break;         }         return displayMsg;     }

app.start(); })();`

在这个清单中,我获取当前视图,并使用ApplicationViewState枚举从数字字符串映射到可以显示给用户的消息。然后,我使用这个消息来设置表示 DOM 中的topRight元素的对象的innerText属性。

接收视图变化事件

前面清单中的代码在应用启动时获取视图,但是当用户切换到不同的视图时,它不会保持 UI 最新。为了创建一个适应不同视图的应用,我需要监听视图变化事件,这是通过 DOM window 对象的resize事件发出的信号。您可以在清单 6-6 中的default.js文件中看到我是如何处理这些事件的。

清单 6-6 。处理视图变化事件

`(function () {     "use strict";

var app = WinJS.Application;     var view = Windows.UI.ViewManagement;

app.onactivated = function (eventObject) {

topRight.innerText = getMessageFromView(view.ApplicationView.value);         window.addEventListener("resize", function () {             topRight.innerText = getMessageFromView(view.ApplicationView.value);         });     }

function getMessageFromView(currentView) {         var displayMsg;         switch (currentView) {             case view.ApplicationViewState.filled:                 displayMsg = "Filled View";                 break;            case view.ApplicationViewState.snapped:                 displayMsg = "Snapped View";                 break;             case view.ApplicationViewState.fullScreenLandscape:                 displayMsg = "Full - Landscape";                 break;             case view.ApplicationViewState.fullScreenPortrait:                 displayMsg = "Full - Portrait";                 break;         }         return displayMsg;     }

app.start(); })();`

resize事件表示视图发生了变化,但是为了弄清楚用户选择了哪个视图,我必须再次读取ApplicationView.value属性。然后,我将该值传递给getMessageFromView函数,以创建一个可以显示在应用布局右上面板中的字符串。

你可以在图 6-5 的中看到我添加示例应用的结果。示例应用响应视图变化,使用 CSS media规则控制布局,使用 JavaScript 修改内容(尽管在两种情况下都做了简单的修改)。当然,您可以用 JavaScript 做任何事情,但是我发现这种方法变得非常笨拙,很难测试。CSS 布局和 JavaScript 内容的良好结合对我来说是最好的。

images

图 6-5。通过使用 JavaScript 改变元素内容来适应视图

适应导入内容的视图变化

没有特殊的机制将视图信息传播到您导入到布局中的内容,但是您可以使用 CSS media规则并响应resize事件,就像处理母版页一样。清单 6-7 显示了我添加到项目content.html中的一体化页面的简单例子。(一体化页面将脚本和样式包含在与标记相同的文件中——我在整本书中使用它们来为示例应用添加自包含的演示。)

清单 6-7 。响应导入内容的视图更改

<!DOCTYPE html> <html>     <head>         <title></title>         <script> `            var view = Windows.UI.ViewManagement;

function setButtonContent(viewState) {                 if (viewState == view.ApplicationViewState.snapped) {                     button1.innerText = "Change View";                 } else {                     button1.innerText = "Button One";                 }             }

WinJS.UI.Pages.define("/content.html", {                 ready: function () {                     setButtonContent(view.ApplicationView.value);

                    window.addEventListener("resize", function () {                         setButtonContent(view.ApplicationView.value);                     });                 }             });                               button {                 font-size: 30px;             }             @media screen and (-ms-view-state: snapped) {                 #button1 {                     font-size: 40px;                     font-family: serif;                 }                 #button2 {                     display: none;                 }             }                   

        
            Button One             Button Two