返回博客首页
← 所有文章

深入浅出 NativeScript 布局

2015年7月14日 — 作者:Jen Looper

Telerik 的开源 NativeScript 框架提供了一种构建移动应用程序的新方法,它允许您编写 JavaScript 或 TypeScript、XML 标记和 CSS,为您的应用程序用户创建真正原生的跨平台体验。如果您已准备好深入研究并开始构建应用程序,第一步是了解各个部分如何组合在一起,可以通过观看 入门 视频或仔细阅读 文档 来了解。您还可以阅读 NativeScript 的工作原理。接下来,您应该设置并准备好您的 开发环境。一旦您设置好并绘制了线框草图……就可以开始构建了!

如果您来自混合移动开发的背景,您可能习惯于以类似于设计 Web 应用程序的方式构建移动应用程序。如果您的应用程序包含的不仅仅是简单的列表和小部件,则需要在屏幕上布局元素,以便所有内容都能在用户可能查看应用程序的各种尺寸的屏幕上良好地适应和缩放。

为此,您可能会转向像 pure.css 或非常轻量级的 skeleton 这样的网格,以便您可以开始将内容放置在屏幕上。但是,这些网格和布局辅助工具仅适用于依赖 DOM 在屏幕上放置元素的混合移动应用程序。然而,在使用 NativeScript 构建的应用程序中,您无法访问 Cordova 支持的混合移动应用程序的 DOM 和屏幕流程。但是,通过了解 NativeScript 中布局的工作原理,您仍然能够快速为您的移动应用程序制作有效的 UI。所以让我们开始吧!

构建布局的基础

NativeScript 中的布局可以通过编写 XML 标记来创建 UI,或者使用 JavaScript 在后台向动态创建的 UI 添加元素来创建。例如,以下是通过 XML 创建的两列网格布局
<GridLayout columns="auto,auto" rows="auto">
    <Label col="0" text="hello"/>
    <Button col="1" text="ohai"/>
</GridLayout>
相同的布局可以在代码隐藏文件中创建
var layout = require("ui/layouts/grid-layout");
var buttonModule = require("ui/button");
var labelModule = require("ui/label");
 
exports.loaded = function(args) {
 
    var page = args.object;
    var gridLayout = new layout.GridLayout();
    var button1 = new buttonModule.Button();
    var label1 = new labelModule.Label();
         
    button1.text = "hello";
    label1.text = "ohai";
         
    layout.GridLayout.setColumn(button1, 0);
    layout.GridLayout.setColumn(label1, 1);
    gridLayout.addChild(button1);
    gridLayout.addChild(label1);
         
    var firstColumn = new layout.ItemSpec(1, layout.GridUnitType.auto);
    var secondColumn = new layout.ItemSpec(1, layout.GridUnitType.auto);
    var firstRow = new layout.ItemSpec(1, layout.GridUnitType.auto);
         
    gridLayout.addColumn(firstColumn);
    gridLayout.addColumn(secondColumn);
    gridLayout.addRow(firstRow);
 
    page.content = gridLayout;
         
};

如您所见,元素作为子元素添加到布局中,并逐步获得属性。ItemSpec 是一个类,用于指定行高和列宽的值和类型。您可以选择如何编写布局代码 - 使用 XML 可能稍微简短一些且更容易,但两种方式都可以。

布局类型

NativeScript 布局有五种“风格”,此处有描述。NativeScript 技术负责人 Alexander Vakrilov 这样解释 NativeScript 布局
“基本概念是我们有不同的布局视图(或布局容器),它们可以有一个或多个子视图。每个布局容器都有自己的逻辑,用于在其获得的空间内排列其子元素。”
以下是布局类型及其流程的列表

  • **绝对布局**:通过子元素的 x 和 y 坐标定位元素。您可以使用绝对布局在应用程序的左上角显示活动指示器小部件。
  • **停靠布局**:如果需要在应用程序的非常具体的区域放置元素,则停靠布局非常有用。例如,停靠在屏幕底部的容器将是广告的良好容器。
  • **网格布局**:将网格布局视为 NativeScript 的<tr><td> 标签。创建网格并使用rowSpanscolSpans 向其中添加子元素,类似于 HTML 中表格的标记。
  • **堆栈布局**:垂直或水平堆叠此布局的子元素。
  • **换行布局**:当空间被填满时,此布局的子元素将从一行或一列流到下一行或下一列。
在简单的应用程序中,您可以使用基本的StackLayout 将一个项目垂直地叠加在另一个项目上。标记将很简单
<StackLayout orientation="vertical">
   <Button text="one"/>
   <Button text="two"/>
   <Button text="three"/>
   <Button text="four"/>
   <Button text="five"/>
 </StackLayout>
我们得到了一堆简单的内容元素

  Screenshot 2015-06-04 17.14.17

使用orientation="horizontal" 水平堆叠,相同的标记将简单地水平对齐

  Screenshot 2015-06-04 17.14.39

向布局添加 horizontalAlignment 将对齐整个内容块
<StackLayout orientation="horizontal" horizontalAlignment="center">
Screenshot 2015-06-04 17.16.09

结合使用 horizontalAlignment 和 verticalAlignment 可以约束内容的比例,可以通过设置 minWidth 或 minHeight 来进一步控制此效果
<StackLayout orientation="horizontal" horizontalAlignment="center" verticalAlignment="center" minHeight="100">
Screenshot 2015-06-04 17.16.44

通过在布局的子元素中使用 horizontalAlignment、verticalAlignment 和 minHeight 或 minWidth,您可以更好地控制子内容的处理方式(请注意,默认情况下内容会被拉伸,horizontalAlignment 和 verticalAlignment 的默认值为“stretch”)。
<StackLayout orientation="vertical">
   <Button text="one" horizontalAlignment="left" minWidth="100"/>
   <Button text="two" horizontalAlignment="center" minWidth="30"/>
   <Button text="three" horizontalAlignment="right" minWidth="70"/>
   <Button text="four"/>
</StackLayout>

Screenshot 2015-06-04 17.24.19

当您开始使用布局时,您会发现它们的行为很大程度上取决于其子元素的属性。了解布局如何处理其子元素的渲染以及子元素可用于处理其在布局中外观的属性非常重要。例如,在使用 ListView 等小部件时,请确保将父网格的高度设置为“star”,否则它会被截断。另一方面,在使用图像时,您可以通过设置其 stretch 属性(如下所示)来设置图像本身以适应其可用空间。

拉伸内容和网格布局

那么当您需要将内容排列成网格时该怎么办?使用 GridLayout,您可以控制网格单元格的宽度和高度以及单元格中内容的呈现方式。设置网格时,您可以指定每一行和每一列如何处理其子内容

  • auto – 行或列采用其子元素的尺寸。
  • pixel – 数值指定单元格的大小(以设备独立像素为单位)。
  • star – 值指定相对于其他星号的相对值。
注意:默认情况下,GridLayout 的行为类似于具有“star”高度和宽度的单行单列。您需要相应地规划此类布局与小部件控件子元素的使用。
<GridLayout rows="auto,auto" columns="auto,auto">
   <Label text="one" row="0" col="0"/>
   <Label text="two" row="0" col="1"/>
   <Label text="three" row="1" col="0"/>
   <Label text="four" row="1" col="1"/>
</GridLayout>
在没有太多样式或对齐的情况下,这组标签看起来像这样

5 将相同的网格设置为具有“star”行和列会将子元素拉伸到网格单元格的高度和内容的宽度

6

更改 horizontalAlignment 属性可以使网格看起来更好一些
<GridLayout rows="auto,auto" columns="auto,auto" horizontalAlignment="center">
   <Label text="one" row="0" col="0" horizontalAlignment="left"/>
   <Label text="two" row="0" col="1"  horizontalAlignment="right"/>
   <Label text="three" row="1" col="0"  horizontalAlignment="left"/>
   <Label text="four" row="1" col="1"  horizontalAlignment="right"/>
</GridLayout>

7
并且向网格单元格添加固定值以及 colSpan 以跨越网格底部,可以让您更精细地控制内容
<GridLayout rows="60,60,auto" columns="60,60" horizontalAlignment="center" verticalAlignment="center">
   <Button text="red" row="0" col="0" cssClass="red"/>
   <Button text="blue" row="0" col="1" cssClass="blue"/>
   <Button text="yellow" row="1" col="0" cssClass="yellow"/>
   <Button text="green" row="1" col="1" cssClass="green"/>
   <Label text="pick a color!" row="2" colSpan="2" horizontalAlignment="center"/>
</GridLayout>
这会产生

8

包含图像的布局

使图像在 NativeScript 布局中看起来很棒与框架如何处理内容缩放有关。NativeScript 在缩放图像内容方面做得非常出色。与原生应用程序一样,开发人员需要创建每个图像的多个版本,并使用区分其大小的命名约定进行命名。对于 iOS,请创建文件版本 1x、2x 和 3x,并根据其大小命名,并将它们放在 App_Resources/iOS 文件夹中。对于 Android,请创建文件的三个版本,无需任何特殊命名约定,并将它们排序到 App_Resources/Android 中的正确文件夹中。最小的文件将进入 drawable_ldpi,最大的文件将进入 drawable_hdpi(您可以创建更多高分辨率图像,只需将它们存储在正确命名的 Android 文件夹中)。

因此在 iOS 上,文件将如下所示

bike.png 

9

[email protected](bike.png 的两倍大小)

10

[email protected](bike.png 的三倍大小,以支持视网膜显示屏)

11

如果您将这些添加到堆叠布局中而不设置任何图像属性,您会注意到内容会被拉伸以适应,根据屏幕上可用的空间进行缩放,并且图像会变得像素化

12

为了停止这种像素化并控制设备上图像的外观,您可以设置它们的 stretch 属性


<StackLayout orientation="vertical" horizontalAlignment="center">
   <Image src="res://bike" stretch="none"/>
   <Image src="res://bike" stretch="none"/>
   <Image src="res://bike" stretch="none"/>
   <Image src="res://bike" stretch="none"/>
   <Image src="res://bike" stretch="none"/>
</StackLayout>
最终得到一组保留其纵横比的图像:13图像的stretch属性可以是四个值之一
  • **None** - 图像保留其原始大小。
  • **AspectFill** - 图像被调整大小以填充目标尺寸,同时保留其原生纵横比。
  • **AspectFit** - 图像被调整大小以适合目标尺寸,同时保留其原生纵横比。如果目标矩形的纵横比与图像不同,则图像会被裁剪以适合目标。
  • **Fill** - 图像被调整大小以填充目标尺寸,并且不保留纵横比。

复杂布局

也许您需要一个支持图像内容缩放 *以及* 能够拉伸色块以填充任意大小屏幕的布局。例如,我正在构建的天气应用程序需要这样的复杂布局

14

在这种情况下,我们可能会尝试创建一个复杂的嵌套布局,因为

  1.  我们有一个顶部导航栏。
  2. 如果从 forecast.io(天气服务 API)获取数据时出现错误或问题,则会出现一个隐藏的消息区域。
  3. 我们在顶部有两个彩色框,需要缩放以适应 iPad(因为这是一个儿童应用程序),并且下面的内容需要跨越顶部的列。
  4. 在顶部两个框内和底部区域内,必须缩放图像,但不能拉伸
让我们看看此标记的外观!

<GridLayout columns="*,*,*,*" rows="auto, *, *">
 
<!--此区域是顶部标题栏-->
    <GridLayout class="header-container" colSpan="5" columns="*,*,*,*,*">
      <Label text="&#xf021;" horizontalAlignment="left" verticalAlignment="center" tap="refresh" class="top-icon weather-icon small-icon"/>
       <Label text="My Weather" colSpan="5" horizontalAlignment="center" verticalAlignment="center" class="large-text"/>            
        <ActivityIndicator col="3" busy="{{ isLoading }}" horizontalAlignment="right" />
         <Label text="&#xf129;" col="4" horizontalAlignment="right" verticalAlignment="center" class="top-icon weather-icon small-icon" tap="openInfo"/>
     </GridLayout>
 
<!--天气信息容器,两列基于网格的列,每列中都包含一个堆叠布局-->
               
 <StackLayout class="blue1-container" colSpan="2" row="1">
      <Label text="Now" row="2" colSpan="2" horizontalAlignment="center" verticalAlignment="center" class="large-text top"/>
       <Label row="3" horizontalAlignment="center" colSpan="2" text="&#xe627;" class="weather-icon large-icon"/>
       <Label text="77°" row="4" colSpan="2" horizontalAlignment="center" class="large-text" />               
   </StackLayout>
               
   <StackLayout class="blue3-container" colSpan="2" row="1" col="2">
       <Image stretch="none" row="2" col="2" colSpan="2" src="res://bike" horizontalAlignment="center" class="top" verticalAlignment="center" />
        <Label row="3" horizontalAlignment="center" colSpan="2" text="&#xe627;" class="weather-icon large-icon"/>
        <Label text="77°" row="4" col="2" colSpan="2" horizontalAlignment="center" class="large-text" />
    </StackLayout>
 
<!--我们根据学生选择的出发时间显示穿衣建议的底部区域-->             
  <Image stretch="aspectFit" row="2" colSpan="4" src="res://warmbg" verticalAlignment="bottom" />        
</GridLayout>


在选项卡界面中,这种布局会生成以下屏幕

15

了解 UI 小部件的各种属性可以帮助您理解它们在 GridLayout 中的行为方式。例如,在此应用程序中:

  • 创建了一个包含四列三行的 GridLayout。第一行设置为自动缩放,但其余行设置为具有相对(' * ')缩放。这样做的目的是使第一行(标题)具有标准高度,而其他列则根据其子元素进行缩放。

    <GridLayout class="header-container" colSpan="5" columns="*,*,*,*,*">
          <Label text="&#xf021;" horizontalAlignment="left" verticalAlignment="center" tap="refresh" class="top-icon weather-icon small-icon"/>
           <Label text="My Weather" colSpan="5" horizontalAlignment="center" verticalAlignment="center" class="large-text"/>            
            <ActivityIndicator col="3" busy="{{ isLoading }}" horizontalAlignment="right" />
            <Label text="&#xf129;" col="4" horizontalAlignment="right" verticalAlignment="center" class="top-icon weather-icon small-icon" tap="openInfo"/>
    </GridLayout>

    在跨越它们的标题下方放置了两个 StackLayout。

    <StackLayout class="blue1-container" colSpan="2" row="1">
         <Label text="Now" row="2" colSpan="2" horizontalAlignment="center" verticalAlignment="center" class="large-text top"/>
          <Label row="3" horizontalAlignment="center" colSpan="2" text="&#xe627;" class="weather-icon large-icon"/>
         <Label text="77°" row="4" colSpan="2" horizontalAlignment="center" class="large-text" />               
    </StackLayout>
                   
    <StackLayout class="blue3-container" colSpan="2" row="1" col="2">
        <Image stretch="none" row="2" col="2" colSpan="2" src="res://bike" horizontalAlignment="center" class="top" verticalAlignment="center" />
         <Label row="3" horizontalAlignment="center" colSpan="2" text="&#xe627;" class="weather-icon large-icon"/>
          <Label text="77°" row="4" col="2" colSpan="2" horizontalAlignment="center" class="large-text" />
    </StackLayout>

    一个图像横跨底部,跨越四列并对齐到底部。此图像设置为 aspectFit,以便不会出现像素化,但保留其纵横比。

    <Image stretch="aspectFit" row="2" colSpan="4" src="res://warmbg" verticalAlignment="bottom" />

看起来很复杂?确实有点,但是一旦您了解每种布局类型在处理其子元素和缩放其子元素内容方面的工作方式,您就可以创建外观漂亮的应用程序。例如,此应用程序在 iPad、iPhone 和 Android 设备上看起来都很好。

16
17 18
注意选项卡布局在各种设备上的工作方式。这是一种很好的方法,可以观察 NativeScript 框架如何通过向用户提供在其所选平台上最熟悉的 UX 来创建真正的跨平台原生用户体验。

结论


我鼓励您查看各种 NativeScript 示例,以了解布局如何从简单到非常复杂。一旦您了解了它们的工作原理,您就可以为您的客户创造真正出色的用户体验!感谢 Hristo Hristov 和 TJ VanToll 为本文提供的帮助