NativeScript 在允许开发者从同一个代码库创建 Android 和 iOS 应用方面做得非常出色。但手机和平板电脑呢?我们是否也可以使用相同的代码库来适应不同的屏幕尺寸和比例?如果不这样做,那真是太傻了。在本文中,我们将探讨一些调整布局以适应所有类型设备的策略。
如果您来自 Web 开发领域,那么您可能熟悉“响应式布局”这个术语,即能够适应浏览器尺寸的布局。不过,原生应用的情况并不完全相同。用户可以根据自己的意愿调整浏览器大小,但应用(几乎)总是占据屏幕的全部尺寸。屏幕尺寸有数百种,但我们可以将其大致分为两种类型:**手机**和平板电脑。区别不在于屏幕尺寸本身(有 6.5 英寸的手机和 7 英寸的平板电脑),而在于用户与设备的交互方式。例如,人们通常用一只手以纵向模式握住手机,而平板电脑则通常以横向模式放在桌子上。
并非每个应用都需要响应式。您的应用可能只针对手机。但是,如果您需要使其具有响应性,那么本文就是为您准备的!
本文中的示例使用 NativeScript-Vue,但这些技术适用于任何“风格”的 NativeScript。
在 NativeScript 中,没有构建响应式应用的“标准”方法。值得庆幸的是,NativeScript 非常灵活且强大,我们可以通过多种方式来实现我们的目标。让我们深入了解其中一些技术。
事实上,NativeScript 的 CSS 没有提供等同于媒体查询的功能。但是,我们拥有非常接近的东西:插件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
,根据设备类型。此插件具有更多功能和自定义选项。请查看此处。
考虑一下这个假想食谱应用的登录屏幕
这是屏幕的主要模板
<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.cn/ui/layouts/layout-containers#flexboxlayout] 的中心对齐,并为其指定了 400dpi 的固定宽度。现在好多了
这是一个简单的示例,但它展示了许多可能性。如果您习惯使用 CSS 媒体查询,那么此技术非常适合您。这都要感谢nativescript-platform-css
插件!
强大的<GridLayout>
也可以帮助我们使应用具有响应性。这是因为当我们在运行时更改其结构时,此布局会做出很好的响应。我们可以利用这一点,根据屏幕宽度重新排列布局。
查看我们在假想食谱应用中使用<GridLayout>
实现的食谱屏幕
在横向平板电脑上,文本在水平方向上拉伸得太多。我们可以通过将配料与制作说明并排放置,更好地利用可用的屏幕宽度。横向网格与纵向网格的区别在于,我们少了一行,因为配料与说明位于同一行。让我们看看如何做到这一点。
首先,我们将<GridLayout>
的行设置为响应式,以及网格单元格的row
、col
和colSpan
,这些单元格会更改其位置。模板最终变成了这样
<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
}
};
}
}
在此函数中,我们首先使用getMeasuredWidth()
获取<GridLayout>
的宽度。此值可能以像素为单位,因此我们必须使用函数toDeviceIndependentPixels()
将其转换为设备无关像素 (DIP)。
警告!函数
utils.layout.toDeviceIndependentPixels()
来自 NativeScript utils 包,因此您需要使用以下代码导入它:import * as utils from "utils/utils";
然后,如果屏幕宽度大于 1000 DPI,我们将通过更改gridLayout
数据对象来重新排列网格布局。
另一个重要的细节是我们将方法updateLayout()
附加到@layoutChanged
事件。每当重新计算<GridLayout>
布局时,此事件就会触发,这发生在第一次渲染布局时,以及屏幕方向发生变化时。没错,布局会自动调整以适应屏幕旋转!
我们选择 1000 DPI 作为断点,因为横向平板电脑大约从这里开始。设备差异很大,但以下近似值可能有助于您确定断点
此技术看起来很复杂,但肯定比重新实现平板电脑的布局要好。此外,<GridLayout>
的性能完美无瑕:没有明显的“布局跳动”、屏幕闪烁或延迟。亲眼看看
<RadListView>
是一个功能丰富的组件,它提供了许多比<ListView>
更好的改进。其中一项功能是能够使用网格布局而不是堆叠布局。那么,此功能将如何帮助我们使应用具有响应性?好吧,根据文档,我们可以使用参数gridSpanCount
轻松定义列数。
这就是响应式网格列表视图在我们的应用中的样子
请注意,在手机上,网格有两列,在纵向平板电脑上,网格有四列,在横向平板电脑上,网格有五列。这是组件的代码
<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 的响应式系统,展示了此框架的延展性。只要有创意,我们就可以做到任何事情!
可能还有上千种不同的方法可以使应用具有响应性。您是否使用过其他方法?我非常乐意在评论中听到您的想法!