Web应用程序的灵活布局
PUBLISHED
简介
当创建移动应用程序,您必须决定是否要支持不同的屏幕方向,或只是在一个方向(默认纵向)显示应用程序。 当你横持设备(横向),视口的宽度试一个更长的边。 当你纵持设备(纵向),视口的宽度是一个更短的边。 此外,我们可以以纵向为主要方向,横向为次要方向。 差异如下图所示。
看一下每一个方向里Home按钮被放置在哪里。 当你以纵向次要方向持有设备,有实际上是倒置着设备。
良好的应用程序的设计应采用不同的屏幕方向,并为每个显示进行不同的布局。 当横向模式显示时,一些应用程序可在边缘显示额外的数据,其他程序可以不同的方式显示相同的数据。 让应用程序在两种情况下看起来都很吸引人不是一个容易的任务。 我们会调查这个问题,并就Tizen应用里如何作出正确的布局提供一些线索。
先决条件
首先,您必须在您的Tizen设备(或仿真器)的设置菜单启用方向变化功能。 要打开它,进入设置>显示设置,并启用自动旋转屏幕的选项,如下图所示。
现在,旋转设备时,你会在一些应用程序看到,屏幕方向也在发生变化。 您的应用程序在默认情况下设置为纵向模式。 我们必须在config.xml文件中进行更改。 打开它,并去Tizen标签。 在设定部分应看到几个下拉菜单,其中之一是被标记的屏幕方向。 改变它的值为从纵向到自动旋转。 现在,您的应用程序会自动响应设备方向的变化。
此外,我们将使用硬件返回键,所以我们必须在config.xml文件的同一个设置部分启用hwkey事件选项,。
获取屏幕方向
在Tizen操作系统中有三种方法来获得设备的方向值。 我们可以采用Tizen的API或者使用W3C的API得到它的值。
Tizen API
该设备的方向值是系统信息,所以我们可以通过使用下面的代码得到它。
tizen.systeminfo.getPropertyValue('DEVICE_ORIENTATION', function (device) { console.log(device.status); });
getPropertyValue函数需要3个参数:名称,成功后的回调函数和错误后的回调函数。 成功后的回调函数接收设备方向的信息对象作为第一个参数。 方向信息对象具有两个属性:
- 状态 - 存储当前设备的方向。 它可以是下列其中一个值: PORTRAIT_PRIMARY, PORTRAIT_SECONDARY, LANDSCAPE_PRIMARY, LANDSCAPE_SECONDARY.
- isAutoRotate - 储存自动旋转屏幕选项的信息,表示设备旋转屏幕时,Tizen是否应该旋转屏幕的方向。
W3C API
最简单的使用W3C的方法API是用下面的代码来获得设备的方向。
console.log(screen.orientation); // or console.log(window.orientation);
screen.orientation和window.orientation之间的区别在于,第一个存储的屏幕取向的字符串表示。 它可以是下列之一:纵向主要,纵向次要,横向主要,横向次要。
window.orientation属性存储该装置转动的一个角度。 它可以是下列之一:
- 0 - 对应于纵向为主,
- 90 - 对应于横向主要,
- 180 - 对应于纵向次要,
- -90 - 对应于横向次要。
锁定屏幕方向
任何时候,你可以决定以编程方式锁定屏幕方向的变化。
screen.lockOrientation('portrait-primary');
现在,即使在设备旋转时也不会影响屏幕的方向。 要解锁屏幕方向的变化只需要输入。
screen.unlockOrientation();
检测屏幕方向变化
为了检测屏幕方向的变化,我们可以使用Tizen的API或W3C的API。 他们有一点区别,所以要看一下它们两个。
Tizen API
该tizen.systeminfo对象有addPropertyValueChangeListener方法,这个方法监听一些系统属性的变化,当这种变化发生时执行回调函数。
tizen.systeminfo.addPropertyValueChangeListener('DEVICE_ORIENTATION', function (device) { console.log(device.status); });
传递给addPropertyValueChangeListener方法的第一个参数与getPropertyValue方法是一样的对象类型,所以我们有status和isAutoRotate。 状态属性是设备方向值的字符串表示形式。 它是下列值之一:PORTRAIT_PRIMARY,PORTRAIT_SECONDARY,LANDSCAPE_PRIMARY,LANDSCAPE_SECONDARY。
W3C API
W3C引入OrientationChange事件处理和探测设备方向的变化。 我们可以以以下方式监听这个事件。
window.onorientationchange = function (e) {}; // or window.addEventListener('orientationchange', function (e) {});
传递到侦听器函数的事件对象(e)不包含设备的方向信息。 我们需要手动获取这些信息。 我们可以使用其中一个功能,从这篇文章中的“获取屏幕方向”一节。 参见下面的例子。
window.addEventListener('orientationchange', function () { console.log(screen.orientation); // WARNING! Wrong value! console.log(window.orientation); tizen.systeminfo.getPropertyValue('DEVICE_ORIENTATION', function (device) { console.log(device.status); }); });
你必须注意,当在onOrientationChange事件监听器打印值时,screen.orientation属性存储的是错误的值,实际上它存储的是上一次的状态值。 要使它发挥作用,我们可以将这行与延时设置为0的setTimeout函数进行封装。
window.addEventListener('orientationchange', function () { console.log(screen.orientation); // WARNING! Wrong value! setTimeout(function () { console.log(screen.orientation); // Proper value. }, 0); });
有条件的风格
感谢CSS3,我们可以为应用程序的布局和依赖于屏幕方向(屏幕宽度)改变其外观准备一个HTML的基干。 我们将使用@media规则来实现这一目标。 灵活布局最流行的@media规则是最小宽度和最大宽度。
@media all and (max-width: 480px) { /* This code will take effect only when viewport width is under 480px */ div { background-color: red; } }
正如你在上面的代码中看到的,我们声明了视口不超过480像素宽的媒体规则。 实际上,div元素都会有红色背景,只有当媒体的规则得到满足。 在移动设备(尤其是Tizen设备),我们有更多的媒体规则,所谓的定向,我们声明方式如下。
@media all and (orientation: portrait) { /* This code will take effect only when device is in portrait orientation */ div { background-color: red; } }
当设备处于纵向时,上面的代码只会影响div元素。 用这个简单的方法,依赖于视口宽度和设备方向我们就可以控制布局如何显示。 如果你想支持多个平台,你应该同时指定(最小/最大) - 宽度和方向的规则。 例如媒体认为,应该适用于大多数智能手机的规则看起来是这样的。
@media all and (max-width: 480px) and (orientation: portrait) { /* portrait */ } @media all and (min-width: 480px) and (max-width: 768px) { /* landscape */ }
现在我们有所有必要的信息,使一个简单的应用程序根据屏幕方向显示不同的布局。
示例应用程序
本文所附的示例应用程序的标题是WebFlexibleLayout,并提出有关太阳系行星的信息。 该应用程序包括两个布局元素,太阳系行星列表的菜单和单击菜单上的指定行星名字后显示的的相关信息的元素。 设备旋转时布局的变化。 看看下面的截图,看看每个布局的模样。
正如你所看到的,在纵向模式下,应用程序需要使用所有的屏幕空间来显示特定星球的信息,屏幕上方只有一个窄栏是专门用于菜单信息。 默认情况下菜单是折叠的,我们可以通过点击栏或在其上向下滑动来展开。 下面的图片显示了菜单展开时的外观。
我们可以通过向上互动或者单击菜单底部的图标再次折叠菜单。 让我们来看看应用程序的最重要的部分。
HTML
首先,我们必须准备应用程序的HTML框架,这是呈现在下面的HTML基干。
<body> <section class="menu-section folded"> <ul class="menu"> <li class="menu-item selected"><img src="images/mercury-m.png" /><span>Mercury</span></li> <li class="menu-item"><img src="images/venus-m.png" /><span>Venus</span></li> <li class="menu-item"><img src="images/earth-m.png" /><span>Earth</span></li> <li class="menu-item"><img src="images/mars-m.png" /><span>Mars</span></li> <li class="menu-item"><img src="images/jupiter-m.png" /><span>Jupiter</span></li> <li class="menu-item"><img src="images/saturn-m.png" /><span>Saturn</span></li> <li class="menu-item"><img src="images/uranus-m.png" /><span>Uranus</span></li> <li class="menu-item"><img src="images/neptune-m.png" /><span>Neptune</span></li> <li class="toggle"><img src="images/menu.png" /></li> </ul> </section><section class="desc-section"> <div class="description" id="mercury"> <h2>Mercury</h2> <p><img src="images/mercury.png" /><!-- Planet description --></p> </div> <div class="description" id="venus" style="display: none"> <h2>Venus</h2> <p><img src="images/venus.png" /><!-- Planet description --></p> </div> <div class="description" id="earth" style="display: none"> <h2>Earth</h2> <p><img src="images/earth.png" /><!-- Planet description --></p> </div> <div class="description" id="mars" style="display: none"> <h2>Mars</h2> <p><img src="images/mars.png" /><!-- Planet description --></p> </div> <div class="description" id="jupiter" style="display: none"> <h2>Jupiter</h2> <p><img src="images/jupiter.png" /><!-- Planet description --></p> </div> <div class="description" id="saturn" style="display: none"> <h2>Saturn</h2> <p><img src="images/saturn.png" /><!-- Planet description --></p> </div> <div class="description" id="uranus" style="display: none"> <h2>Uranus</h2> <p><img src="images/uranus.png" /><!-- Planet description --></p> </div> <div class="description" id="neptune" style="display: none"> <h2>Neptune</h2> <p><img src="images/neptune.png" /><!-- Planet description --></p> </div> </section> </body>
在本体DOM元素,我们有两个区段元素保存菜单和星球的描述。 每个行星描述以适当的id值被放置在单独分开的div元素。
菜单包括UI元素和式样属性。 一个名为类折叠可以分配给菜单部分 div 元素的样式菜单中的方式,让它紧贴 (仅在纵向方向) 屏幕的上部边缘。
样式表
在旋转屏幕后变化最大的元素是菜单。 来看看我们是如何根据屏幕方向设计菜单和说明部分。
@media all and (max-width: 480px) and (orientation: portrait) { section { display: block; width: 100%; } .desc-section { top: 45px; width: 100%; } .menu-section { width: 100%; background-color: rgba(255, 255, 255, 0.95); /*box-shadow*/ -webkit-box-shadow: 2px 0px 4px rgba(0, 0, 0, 0.5); -moz-box-shadow: 2px 0px 4px rgba(0, 0, 0, 0.5); box-shadow: 2px 0px 4px rgba(0, 0, 0, 0.5); /*transition*/ -webkit-transition: top 1s; -moz-transition: top 1s; -o-transition: top 1s; transition: top 1s; } .menu-section.folded { top: -360px; } } @media all and (min-width: 480px) and (max-width: 768px) { section { display: inline-block; } .desc-section { left: 43.75%; width: 56.25%; } .menu-section { height: 100%; width: 43.75%; background-color: rgb(255, 255, 255); /*box-shadow*/ -webkit-box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5); -moz-box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5); box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5); } .toggle { display: none; } }
在横向模式下,我们设置了部分标签的显示属性为inline-block的价值,把它们并排,并默认阻止纵向方向。 当并排显示它们时,我们防止他们并且设置他们的宽度以填充整个屏幕。
菜单部分具有位置属性,它固定设置总是显示在屏幕的同一位置。 在纵向模式中,我们增加额外的规则来展开和折叠菜单的动画。
-webkit-transition: top 1s;
当菜单折叠,我们设定它的最高属性为-360px将其隐藏在屏幕上方的顶部边缘。
.menu-section.folded { top: -360px; }
一个更重要的是元素的z-index属性。 我们必须菜单部分放在描述部分之上,所以我们相应地设定z-index的值为2和1。
.desc-section { /* ... */ z-index: 1; } .menu-section { /* ... */ z-index: 2; }
JavaScript
JavaScript代码是非常简单的。 我们首先要做的就是获得屏幕方向。 我们在程序启动的时候获取。。。
orientation = screen.orientation.substr(0, screen.orientation.indexOf('-'));
......以及设备方向变化时。
tizen.systeminfo.addPropertyValueChangeListener('DEVICE_ORIENTATION', function (e) { orientation = e.status.toLowerCase().substr(0, e.status.indexOf('_')); });
我们采取的屏幕方向的信息只有第一部分(即 PORTRAIT和LANDSCAPE字符串 - 所以使用substr法)从一个字符串,它的格式如下: [ORIENTATION]_[PRIMARY/SECONDARY]. 然后我们使它成为小写。
当用户单击设备的返回硬件按钮时,我们使用关于屏幕方向的信息。 当设备处于纵向模式并且菜单是展开的,用户点击后退按钮,我们做的第一件事就是折叠菜单,再次点击返回按钮将关闭该应用程序。
document.addEventListener('tizenhwkey', function (e) { if (e.keyName === 'back') { if (!$menu.hasClass('folded') && orientation === 'portrait') { $menu.addClass('folded'); } else { tizen.application.getCurrentApplication().exit(); } } });
我们对菜单做的另一件事是切换它的可见性。 我采用两种方式。 第一种是通过点击菜单的按钮栏的图标,它刚刚切换了菜单部分的折叠类。
$menu.on('click', '.toggle', function () { $menu.toggleClass('folded'); });
另一种方式来展开或折叠菜单是向下或向上滑动它。 要做到这一点,我们必须使用触摸事件(touchstart,touchmove,touchend)。 我们以只有一个手指的Y轴(touches.length == 1)测量距离(dist变量)。 当触摸开始,我们在start变量中存储Y位置。 当手指移动,我们测量手指的当前Y位置,并计算距离。 当该距离为正则意味着我们想要展开菜单,而负值意味着我们折叠菜单。 一个更重要的是使用preventDefault方法的TouchMove事件,以防止在该菜单部分的区域移动手指时滚动行星描述。
var start = null; $menu.on('touchstart touchmove touchend', function (e) { var touches = e.originalEvent.changedTouches, dist = 0; if (touches.length === 1) { if (e.type === 'touchstart') { start = touches[0].pageY; } else if (e.type === 'touchmove') { e.preventDefault(); dist = touches[0].pageY - start; if (dist > 20) { $menu.removeClass('folded'); } else if (dist < -20) { $menu.addClass('folded'); } } else if (e.type === 'touchend') { prev = null; } } });
当用户点击菜单中的任何选项,合适的描述会被显示,其他的被隐藏,我们滚动描述到最顶端。
$menu.on('click', '.menu-item', function () { var $menuItem = $(this); var planetName = $menuItem.text().toLowerCase(); $descriptions.hide(); $descriptions.filter('#' + planetName).show(); $menuItems.removeClass('selected'); $menuItem.addClass('selected'); window.scrollTo(0, 0); });
总结
做灵活的布局时,工作的量取决于你想要它有多复杂。 我希望我在本文所展示的是制作简单灵活的布局不是困难的事。 读完这篇文章后,您应该能够创建自己的布局。 如果您想了解更多详细信息,请研究本文附带的应用程序的代码。