# 使用 NativeScript 构建响应式应用程序

·

NativeScript 出色地完成了工作,允许开发人员从同一代码库为 Android 和 iOS 创建应用程序。但是手机和平板电脑呢?我们还可以针对不同的屏幕尺寸和比例使用相同的代码库吗?如果我们不这样做,那将是愚蠢的。在本文中,我们将探讨一些策略来调整布局以适应所有类型的设备。

如果您来自网络世界,则可能熟悉术语“响应式布局”,即适应浏览器大小的布局。但是,本机应用程序并不完全相同。用户可以根据自己的选择调整浏览器的大小,但是应用程序(几乎)将始终占据整个屏幕的大小。屏幕大小有数百种,但是我们可以将其分为两种基本类型:手机和平板电脑。区别不在于屏幕大小本身(有 6.5 英寸的手机和 7 英寸的平板电脑)本身,而是用户与设备交互的方式。例如,人们通常用一只手以纵向模式握住电话,而平板电脑通常以横向模式站立在桌子上。

并非每个应用都需要响应。您的应用可能仅针对手机。如果您需要使其具有响应性,那么本文适合您!

TIP

本文中的示例使用 NativeScript-Vue,但是这些技术适用于 NativeScript 的任何“风味”。

在 NativeScript 中没有“标准”的方式来制作响应式应用程序。值得庆幸的是,NativeScript 非常灵活且功能强大,我们可以采用多种方式实现目标。让我们深入研究其中一些技巧。

# 使用 CSS

实际上,NativeScript 的 CSS 不提供与媒体查询等效的功能。但是,我们有一些非常接近的东西:plugin nativescript-platform-css。让我们看看它将如何帮助我们。

您需要使用以下命令添加插件:

tns plugin add nativescript-platform-css

然后使用以下命令对其进行初始化app.js

require('nativescript-platform-css');

首先,我们必须了解该插件的功能:它将有关平台和屏幕尺寸的顶级 CSS 类添加到我们的应用程序中。其中一些类是:

  • .androidXXX.iosXXX,其中XXX可以是 1280、1024、800、600、540、480,400、360 或 320。
  • .phone.tablet,具体取决于设备类型。

TIP

此插件具有更多功能和自定义设置。在这里查看它们。

考虑这个假食谱应用程序的登录屏幕:

1 之前登录
这是屏幕的主要模板:

<FlexboxLayout alignItems="stretch" flexDirection="column">
  <Image marginTop="46" width="250" height="183" alignSelf="center" src="res://loginlogo"/>
  <Label flexGrow="1"/>
  <Button class="social-login fb" text="Log in with Facebook"/>
  <Button class="social-login google" text="Log in with Google"/>
</FlexboxLayout>

在手机上看起来不错,但在平板电脑上却不尽如人意,尤其是在横向模式下。这里的问题是按钮太宽。要解决此问题,我们可以添加 css 样式以限制按钮的宽度,并将其范围设置为.tablet仅适用于平板电脑,如下所示:

.tablet button.social-login {
  align-self: center;
  width: 400;
}

我们将按钮对齐到(FlexLayout)[https://docs.nativescript.org/ui/layouts/layout-containers#flexboxlayout (opens new window)]的中心,并将其固定宽度设为 400dpi。现在好多了:

2 次登录后
这是一个简单的示例,但是它显示了很多可能性。如果您习惯 CSS 媒体查询,那么此技术适合您。感谢nativescript-platform-css插件!

# 动态更改网格布局

万能的人<GridLayout>也可以帮助我们提高应用程序的响应速度。这是因为当我们在运行时更改其布局时,此布局响应良好。我们可以利用它来发挥优势,并根据屏幕的宽度重新排列布局。

查看<GridLayout>在我们的伪食谱应用程序中实现的食谱屏幕:

3 网格布局之前
在横向图形输入板上,文本在水平方向上拉伸得太大。通过将配料与制备说明并排放置,我们可以更好地利用可用的筛子宽度。“横向网格”与“纵向网格”的区别在于,由于成分与说明位于同一行,因此我们少一行。让我们看看如何做到这一点。

首先,我们做了<GridLayout>的行具有反应性的,还有rowcolcolSpan用于改变其位置的网格单元。模板最终如下所示:

<GridLayout
  columns="*, *, *, *"
  :rows="gridLayout.rows"
  ref="layout"
  @layoutChanged="updateLayout"
  >
(...)
  <StackLayout
    :row="gridLayout.ingredients.row"
    :colSpan="gridLayout.ingredients.colSpan"
    textWrap="true"
    id="ingredientsText"
    ref="ingredientsText"
  >
    <!-- Ingredients text added here -->
  </StackLayout>
  <StackLayout
    :row="gridLayout.instructions.row"
    :col="gridLayout.instructions.col"
    colSpan="4"
    textWrap="true"
    id="instructionsText"
    ref="instructionsText"
  >
    <!-- Recipe text added here -->
  </StackLayout>
  (...)
</GridLayout>
(...)

我们向gridLayout组件的数据中添加了一个对象,以使这些布局更改井井有条:

data() {
  return {
    gridLayout: {
      rows: "40, auto, auto, 40, auto",
      ingredients: {
        row: 1,
        colSpan: 4
      },
      instructions: {
        row: 2,
        col: 0
      }
    },
(...)

gridLayout默认情况下, 此数据在人像模式下初始化。然后,我们创建了一个方法updateLayout(),如果有足够的空间,可以将该对象的值更改为横向模式。看一看:

updateLayout() {
  const width = utils.layout.toDeviceIndependentPixels(
    this.$refs.layout.nativeView.getMeasuredWidth()
  );
  if (width < 1000) {
    this.gridLayout = {
      rows: "40, auto, auto, 40, auto",
      ingredients: {
        row: 1,
        colSpan: 4
      },
      instructions: {
        row: 2,
        col: 0
      }
    };
  } else {
    this.gridLayout = {
      rows: "40, auto, 40, auto",
      ingredients: {
        row: 1,
        colSpan: 1
      },
      instructions: {
        row: 1,
        col: 1
      }
    };
  }
}

在此函数中,我们首先抓取<GridLayout>with 的宽度getMeasuredWidth()。该值可能以像素为单位,因此我们必须使用函数将其转换为与设备无关的像素(DIP)toDeviceIndependentPixels()

WARNING

警告!该函数utils.layout.toDeviceIndependentPixels()来自 NativeScript utils 包,因此您需要使用以下命令导入它: import * as utils from "utils/utils";

然后,如果屏幕的宽度大于 1000 DPI,我们将通过更改gridLayout数据对象来重新排列网格布局。

另一个重要的细节是我们将方法附加updateLayout()到@layoutChanged 事件上。每当<GridLayout>重新计算布局时都会触发此事件,这是在首次渲染布局时以及屏幕方向更改时发生的。没错,布局会根据屏幕旋转进行调整!

我们选择了 1000 个 DPI 作为断点,因为这是关于景观图块开始的位置。设备的差异很大,但以下是一些近似值,可以帮助您确定断点:

  • 纵向模式下的电话大约为 350 至 450 DPI。
  • 横向模式下的电话大约 600 至 800 DPI。
  • 纵向模式下的平板电脑宽约 700 至 900 DPI。
  • 横向模式下的平板电脑宽约 1000 至 1200 DPI。

这项技术似乎很复杂,但是肯定要重新实现平板电脑的布局。而且,<GridLayout>性能一尘不染:没有可察觉的“跳跃布局”,屏幕闪烁或滞后。你自己看:

hubby_chef

# Rad 列表视图网格布局

<RadListView>是一个功能丰富的组件,它提供了大量的在改进<ListView>。这些功能之一是能够使用网格布局而不是堆叠布局。以及该功能如何帮助我们使应用程序具有响应能力?嗯,根据文档,我们可以使用参数轻松定义列数gridSpanCount

这是响应式网格列表视图在我们的应用程序中的外观:

5 角列表视图

请注意,在电话上,网格有两列,在纵向平板电脑上,网格有四列,在横向平板电脑上,网格有五列。这是组件的代码:

<template>
  <RadListView
    for="recipe in recipes"
    layout="grid"
    itemHeight="200"
    :gridSpanCount="gridColumns"
    ref="layout"
    @layoutChanged="updateLayout">
    <v-template>
      <RecipeCard :recipe="recipe"/>
    </v-template>
  </RadListView>
</template>
<script>
import * as utils from "utils/utils";
import RecipeCard from "../components/RecipeCard";
export default {
  props: ["recipes"],
  components: { RecipeCard },
  data() {
    return {
      gridColumns: 4
    };
  },
  methods: {
    updateLayout() {
      const width = utils.layout.toDeviceIndependentPixels(
        this.$refs.layout.nativeView.getMeasuredWidth()
      );
      this.gridColumns = parseInt(width / 180);
    }
  }
};
</script>

“响应魔术”发生在gridSpanCount属性中。我们使列数对变量具有反应性gridColumns。然后,我们在updateLayout方法中更改了gridColumns变量。我们使用了上一节中的方法来检索可用宽度。这也意味着布局会在屏幕旋转时自动调整。我们将总宽度除以 180,以使卡的宽度在 180 到 250 之间变化,因此它们在每个设备中看起来都是大致平方的。

# 结论

在本文中,我们以三种不同的方式实现了“响应性”。这些技术可让您使用最少的代码使应用程序在宽屏(例如平板电脑)上看起来不错。

第一种技术使用 CSS,因此吸引了经验丰富的 Web 开发人员。使用(和滥用)NativeScript 的反应性系统的其他方法,显示了此框架的可塑性。有了一些创造力,我们可以做任何事情!

可能有上千种方法使应用程序具有响应能力。您是否使用过其他方法?我希望在评论中听到他们的消息!