在上一篇文章中,我 向您介绍了 nativescript。在那里,您了解了 NativeScript 的基础知识以及它与其他移动开发框架的不同之处。这一次,您将通过创建您的第一个 NativeScript 应用程序来亲自动手。我将引导您完成使用 NativeScript 构建应用程序的整个过程,从设置开发环境到在设备上运行应用程序。以下是我将要讨论的内容的概要:
设置 NativeScript
构建应用程序
运行应用程序
调试应用程序
我们将专门在android平台上运行该应用程序。但是,如果您想部署到ios ,您仍然可以跟随,因为代码几乎相同。唯一的区别在于设置 NativeScript 的过程和您在运行应用程序时执行的命令。
此应用程序的完整源代码可从教程 GitHub 存储库中获得。
1.设置 NativeScript
要设置 NativeScript,您首先必须安装node.js。安装nodenpm install -g nativescript .js 后,您可以通过 在终端上 执行来安装NativeScript命令行工具。
最后一步是为要部署到的每个平台安装开发工具。对于 Android,这是 Android SDK。对于 iOS,它是 XCode。您可以按照 NativeScript 网站上的安装指南 获取有关如何为您的开发环境设置必要软件的更详细说明。
设置好环境后,执行tns doctor 以确保您的环境已准备好进行 NativeScript 开发。如果您在 Linux 或 Windows 上,如果您的环境已准备好,您将看到如下内容:
NOTE: You can develop for iOS only on mac OS X systems. To be able to work with iOS devices and projects, you need Mac OS X Mavericks or later. Your components are up-to-date. No issues were detected.
那里有一条说明,您只能在 Mac OS X 系统上为 iOS 开发。这意味着如果您在 PC 上,您将只能部署到 Android 设备。但是,如果您使用的是 Mac,则可以在 iOS 和 Android 平台上进行部署。
如果您在设置过程中遇到任何问题,您可以获得加入NativeScript Slack 社区的邀请 ,一旦您加入,请转到入门频道 并在那里提出您的问题。
2.创建应用程序
我们要构建的应用程序是一个笔记应用程序。它将允许用户创建笔记,每个笔记都带有一个可选的图像附件,该附件将由设备相机捕获。笔记使用NativeScript 应用程序设置进行持久化,并且可以单独删除。
这是应用程序的外观:
首先执行以下命令来创建一个新的 NativeScript 项目:
tns create note --appid "com.yourname.noter"
noter 是项目的名称, com.yourname.noter 是唯一的应用 ID。稍后将在您将应用提交到 Play 或 App Store 后使用它来识别您的应用。默认情况下,该 tns create 命令将为您创建以下文件夹和文件:
应用程序
节点模块
平台
包.json
您通常只需触摸 app 目录中的文件。但在某些情况下,您可能需要编辑platforms/ android 目录中的文件。一种这样的情况是,当您尝试使用的插件没有自动链接它需要的依赖项和资产时。
接下来,导航到应用程序 目录并删除除App_Resources文件夹之外的所有文件。然后创建以下文件:
应用程序.js
应用程序。css
notes-page.js
笔记-page.xml
这些是 NativeScript 运行时将使用的文件。就像在构建网页时一样, .css 文件用于样式,.js 文件用于功能。但是对于应用程序的标记,我们使用 XML 而不是html。通常,您会为应用程序的每个屏幕(例如登录、注册或仪表板)创建一个单独的文件夹,并在每个文件夹中包含 XML、CSS 和javascript文件。但是由于这个应用程序只有一个屏幕,我们在根目录中创建了所有文件。
如果您需要有关 NativeScript 目录结构的更多信息,请查看NativeScript Getting Started Guide 的第 2 章。
3.入口点文件
打开app.js 文件并添加以下代码:
var application = require("application"); application.start({ moduleName: "notes-page" });
这是 NativeScript 应用程序的入口点。它使用应用程序模块 及其start方法来指定用于应用程序初始页面的模块。在这种情况下,我们指定notes-page了 ,这意味着模块是notes-page.js,标记是notes-page.xml,页面的样式是notes-page.css。这是 NativeScript 中使用的约定,即特定页面的所有文件必须具有相同的名称。
4. 添加 UI 标记
打开notes-page.xml文件并添加以下代码:
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded"> <Page.actionBar> <ActionBar title="{{ app_title }}"> <ActionBar.actionItems> <ActionItem tap="newNote" ios.position="right" android.position="actionBar"> <ActionItem.actionView> <StackLayout orientation="horizontal"> <Label text="New Item" color="#fff" cssClass="header-item" /> </StackLayout> </ActionItem.actionView> </ActionItem> </ActionBar.actionItems> </ActionBar> </Page.actionBar> <StackLayout> <StackLayout id="form" cssClass="form-container"> <TextView text="{{ item_title }}" hint="Title" /> <Button text="Attach Image" cssClass="link label" tap="openCamera" /> <Image src="{{ attachment_img }}" id="attachment_img" cssClass="image" visibility="{{ attachment_img ? 'visible' : 'collapsed' }}" /> <Button text="Save Note" tap="saveNote" cssClass="primary-button" /> </StackLayout> <ListView items="{{ notes }}" id="list" visibility="{{ showForm ? 'collapsed' : 'visible' }}"> <ListView.itemTemplate> <GridLayout columns="*,*" rows="auto,auto" cssClass="item"> <Label text="{{ title }}" textWrap="true" row="0" col="0" /> <Image src="{{ photo }}" horizontalAlignment="center" verticalAlignment="center" cssClass="image" row="1" col="0" visibility="{{ photo ? 'visible' : 'collapsed' }}" /> <Button text="delete" index="{{ index }}" cssClass="delete-button" tap="deleteNote" row="0" col="1" horizontalAlignment="right" loaded="btnLoaded" /> </GridLayout> </ListView.itemTemplate> </ListView> </StackLayout> </Page>
在 NativeScript 中创建应用页面时,您应该始终从<Page> 标签开始。这就是 NativeScript 知道您正在尝试创建新页面的方式。该xmlns 属性指定用于 XML 文件的架构的 URL。
如果您访问指定的架构 URL,您可以看到可以在 NativeScript 中使用的所有 XML 标记的定义。该 loaded 属性指定加载页面后要执行的函数。稍后我们将在notes-page.js 文件中查看此函数定义。
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded"> ... </Page>
默认情况下,应用标题仅包含应用的标题。如果要添加其他 UI 组件,则需要使用<Page.actionBar>. 然后在里面定义你想在标题中看到的东西。通过使用<ActionBar> 并将其title 属性设置为所需的页面标题来指定标题。
下面我们使用 mustache 语法输出notes-page.js文件中app_title 定义的值。这就是您输出绑定到页面的值的方式。
<Page.actionBar> <ActionBar title="{{ app_title }}"> ... </ActionBar> </Page.actionBar>
要定义按钮,首先<ActionBar.actionItems> 用作父按钮,每个 <ActionItem> 按钮都是您要定义的按钮。该tap属性指定点击按钮时要执行的功能,而 os.position 和 android.position 是按钮在 iOS 和 Android 中的位置。
要指定按钮文本,您可以使用<ActionItem>' text 属性。但是,NativeScript 目前不允许通过 CSS 更改按钮的文本颜色。这就是为什么我们习惯于 <ActionItem.actionView> 定义按钮的内容并设置其文本颜色的原因。
<ActionBar.actionItems> <ActionItem tap="newNote" ios.position="right" android.position="actionBar"> <ActionItem.actionView> <StackLayout orientation="horizontal"> <Label text="New Item" color="#fff" cssClass="header-item" /> </StackLayout> </ActionItem.actionView> </ActionItem> </ActionBar.actionItems>
接下来是实际的页面内容。您可以使用一个或多个布局容器来排列不同的元素。下面我们使用了两种可用的布局: StackLayout 和GridLayout.
StackLayout 允许您堆叠其中的所有元素。默认情况下,此布局的方向是垂直的,因此每个 UI 组件都堆叠在最后一个组件的下方。想想向下流动的乐高积木。
另一方面,GridLayout 允许您在表结构中排列元素。如果您曾经使用过Bootstrap 或其他 CSS 网格框架,那么这对您来说应该很自然。GridLayout允许您定义可以放置每个 UI 组件的行和列。稍后我们将看看这是如何实现的。现在,让我们继续看代码。
首先,让我们定义用于创建新笔记的表单。就像在 HTML 中一样,您可以定义诸如id和cssClass(相当于 HTML 的 class属性)之类的属性。id 如果您想从代码中操作该属性,则该属性会附加到元素上。在我们的例子中,我们希望稍后为表单设置动画。cssClass 用于指定将用于设置元素样式的 CSS 类。
表单内部有一个用于输入注释标题的文本字段、一个用于附加图像的按钮、一个选定的图像以及一个用于保存注释的按钮。attachment_img 仅当具有真值时,图像元素才可见。如果先前附加了图像,情况就是如此。
<StackLayout id="form" cssClass="form-container"> <TextView text="{{ item_title }}" hint="Title" /> <Button text="Attach Image" cssClass="link label" tap="openCamera" /> <Image src="{{ attachment_img }}" id="attachment_img" cssClass="image" visibility="{{ attachment_img ? 'visible' : 'collapsed' }}" /> <Button text="Save Note" tap="saveNote" cssClass="primary-button" /> </StackLayout>
接下来是显示用户已添加的注释的列表。列表是使用ListView 组件创建的。这接受items 为必需属性。该值可以是普通数组或可观察数组。
如果您不需要对数组中的每个项目执行任何形式的更新(例如删除或更新字段),则可以使用纯 JavaScript 数组。否则,请使用可观察数组,它允许您对数组执行更新并将其自动反映到 UI。稍后我们将看看如何定义可观察数组。
还要注意 aListView可以有一个属性,它允许您指定当 轻按itemTap a 中的项目时要执行的功能。ListView但在这种情况下,我们并没有真正添加它,因为当一个项目被点击时我们不需要执行任何操作。
<ListView items="{{ notes }}" id="list" visibility="{{ showForm ? 'collapsed' : 'visible' }}"> ... </ListView>
中的项目ListView 可以使用 来定义<ListView.itemTemplate>。在这里,我们使用 a<GridLayout> 创建两行和两列。该columns 属性用于指定每行中需要多少列。
在这种情况下,*,* 意味着有两列,每列占用当前行中等量的可用空间。因此,如果整行的总宽度为 300 像素,则每列将是 150 像素宽。所以基本上每一* 列代表一列,你用逗号分隔每一列。
该rows 属性的工作方式类似,但控制单行使用的空间量。auto 意味着它只会消耗每行的孩子所需的空间量。
在 中定义columns and rows 之后GridLayout,您仍然需要指定它的哪个子代属于哪一行和哪一列。第一行包含项目的标题(第一列)和删除按钮(第二列)。第二行包含附加到项目的图像(第一列)。通过使用 每个元素 的rowand 属性来指定行和列。col
还要注意 和 的horizontalAlignment 使用 verticalAlignment。text-align 您可以将其视为 HTML属性的 NativeScript 等价物 。但我们不是对齐文本,而是对齐 UI 组件。 horizontalAlignment 可以有right, left, center, 或的值stretch,而 verticalAlignment 可以有top, bottom, center, 或的值stretch。其中大部分是不言自明的,除了stretch,它会延伸以占用可用的水平或垂直空间。
在这种情况下, horizontalAlignment 和 verticalAlignment 用于在其列内水平和垂直居中图像。并horizontalAlignment 在删除按钮上使用以将其与第二列的最右侧部分对齐。
<ListView.itemTemplate> <GridLayout columns="*,*" rows="auto,auto" cssClass="item"> <Label text="{{ title }}" textWrap="true" row="0" col="0" /> <Image src="{{ photo }}" horizontalAlignment="center" verticalAlignment="center" cssClass="image" row="1" col="0" visibility="{{ photo ? 'visible' : 'collapsed' }}" /> <Button text="delete" index="{{ index }}" cssClass="delete-button" tap="deleteNote" row="0" col="1" horizontalAlignment="right" loaded="btnLoaded" /> </GridLayout> </ListView.itemTemplate>
我们没有itemTap 为ListView. 相反,我们想要附加一个删除操作,只要点击列表项中的删除按钮,就会执行该操作。每个项目都有一个index 属性,我们将其作为删除按钮的自定义属性传递。这是用于识别每个项目的唯一密钥,以便我们在需要时可以轻松引用它们。
还要注意loaded 属性。就像 <Page>有一个loaded 属性一样,按钮也可以有一个。稍后您将看到如何使用它。
5. javaScript 代码
现在我们已经准备好查看使这一切正常工作的 JavaScript。在本节中,我们将对notes-page.js 文件进行编码。
初始化
首先我们导入data/observable 和 data/observable-array 模块。这些是 NativeScript 中的内置模块,允许我们创建可观察的对象和数组。Observables 允许我们在这些对象和数组更新时自动更新 UI。
在我们的应用程序中, pageArray 用于存储笔记数组,并pageData 用于将其绑定到页面。pageData还用作我们要在 UI 中显示的所有数据的通用容器。
var Observable = require("data/observable"); var ObservableArray = require("data/observable-array"); var pageArray = new ObservableArray.ObservableArray(); var pageData = new Observable.Observable({ notes: pageArray });
接下来,导入我们将在此页面中使用的所有其他模块:
camera:用于使用设备相机。
view:用于引用页面中的特定元素。将其视为 NativeScript 中的等价物 document.getElementById 。
ui/enums:与 UI 相关的任何内容的常量值的全局字典。
ui/animation: 用于动画元素。
application-settings: 用于持久化本地数据。
file-system: 用于处理文件系统。
var cameramodule = require("camera"); var view = require("ui/core/view"); var uiEnums = require("ui/enums"); var animation = require("ui/animation"); var appSettings = require("application-settings"); var fs = require("file-system");
接下来,初始化将在整个文件中使用的变量的值。 page 用于存储对当前页面的引用,notesArr 是页面中当前笔记的纯数组副本,current_index 是索引的初始值,用作每个笔记的唯一ID。
var page; var notesArr = []; var current_index = -1;
pageLoaded()功能_
通过使用 ,函数在页面上下文中变得可用exports。在之前的notes-page.xml 文件中,您看到 pageLoaded() 函数在页面加载时执行。
exports.pageLoaded = function(args) { ... }
在pageLoaded()函数内部,我们将从获取对页面的引用开始。然后我们展示创建新便笺的表单,并从应用程序设置中获取新便笺标题和便笺的当前存储值。
page = args.object; pageData.set('showForm', true); var new_note_title = appSettings.getString('new_note_title'); var notes = appSettings.getString('notes');
接下来,仍然在pageLoaded()函数内,检查是否有本地存储的笔记。如果没有,我们创建一个笔记数组。该数组将作为应用程序新用户的默认内容。但是,如果本地已经存储了一些笔记,我们将它们转换为一个数组,然后将该数据推送到可观察数组。
请注意,在我们将项目推入可观察数组之前,我们首先检查它是否为空。我们必须这样做,因为使用相机模块会在选择图像后再次执行页面上的loaded 事件。这意味着如果我们不小心,每次用户使用相机时,我们最终都会将重复项推入数组中。
if(!notes){ notes = [ { index: 0, title: '100 push ups' }, { index: 1, title: '100 sit ups' }, { index: 2, title: '100 squats' }, { index: 3, title: '10km running' } ]; }else{ notes = JSON.parse(notes); } notesArr = notes; if(!pageArray.length){ for(var x = 0; x < notes.length; x++){ current_index += 1; pageArray.push(notes[x]); } }
现在我们已经设置了笔记数据,我们可以通过将其 item_title 属性设置为我们之前从应用程序设置中获得的值来更新页面标题。然后绑定pageData 到页面,以便每当对我们设置的项目进行更改时,UI 都会自动更新。
pageData.set('item_title', new_note_title); args.object.bindingContext = pageData;
为创建新笔记的表单设置动画。我们通过使用中的getViewById 函数view 并传入上下文(当前页面)作为第一个参数和id分配给您要操作的元素的属性来做到这一点。
接下来,调用该animate 函数。这接受一个包含动画设置的对象。在这里,我们希望表单在 800 毫秒的时间内从其原始位置向下滑动 160 像素。
view.getViewById(page, 'form').animate({ translate: { x: 0, y: 160 }, duration: 800, });
newNote() 功能_
newNote() 当用户点击 标题上的New Item操作项时,将执行该功能。这将隐藏和显示新项目 ListView ,并根据 的当前值向上或向下滑动表单showForm。
如果showForm 是true,这意味着它当前正在显示,我们在 400 毫秒的过程中将不透明度更改ListView 为1 ,然后向上滑动表单以隐藏它。否则,我们隐藏ListView 并向下滑动表单。
exports.newNote = function() { var showForm = pageData.get('showForm'); var top_position = (showForm) ? -160 : 160; var list_visibility = (showForm) ? 1 : 0; view.getViewById(page, 'list').animate({ opacity: list_visibility, duration: 400 }); view.getViewById(page, 'form').animate({ translate: { x: 0, y: top_position }, duration: 800, }); pageData.set('showForm', !showForm); }
btnLoaded()功能_
在notes-page.xml 文件中,我们loaded 在按钮中有一个用于删除便笺的属性。这是在该事件发生时执行的函数。
默认情况下,当在项目中定义按钮时,分配给itemTap 属性的函数ListView 不会被执行ListView 。这是因为 NativeScript 假定为每个列表项执行的操作只能从这些按钮触发。
下面的代码是该默认行为的解决方法。这基本上消除了删除按钮上的焦点,以便在用户点击 ListView 项目时仍然可以执行功能。在这种情况下,我们实际上并不需要此代码,因为我们没有为项目点击分配任何功能,但是在处理列表时它是一个很好的工具。
exports.btnLoaded = function (args) { var btn = args.object; btn.android.setFocusable(false); }
openCamera()功能_
接下来是openCamera() 函数,当用户点击Attach Image 按钮时会执行该函数。使用摄像头模块时不会保持当前状态,所以我们需要先将新笔记的标题保存到应用程序设置中。
之后我们可以通过调用该takePicture() 方法在设备中启动默认的相机应用程序。此方法接受包含图片设置的对象。一旦用户拍照并点击 Android中的Save按钮或 iOS 上的use image按钮, promise就会解析并且传递给的 回调函数then() 会被执行。
实际图像作为参数传递给函数,我们使用它来将文件保存到文档路径。完成后,我们将完整文件路径保存到应用程序设置和当前应用程序状态,以便稍后在保存注释之前获取值。
exports.openCamera = function() { appSettings.setString('new_note_title', pageData.get('item_title')); cameraModule.takePicture({width: 300, height: 300, keepAspectRatio: true}).then(function(img) { var filepath = fs.path.join(fs.knownFolders.documents().path, "img_" + (new Date().getTime() / 1000) + ".jpg"); img.saveToFile(filepath, uiEnums.ImageFormat.jpeg); appSettings.setString('new_note_photo', filepath); pageData.set('attachment_img', filepath); }); }
saveNote()功能_
该saveNote() 功能在用户点击Save Note 按钮时执行。这将获取注释标题文本字段和图像路径的当前值,递增 current_index,并将新项目推送到普通注释数组和可观察注释数组中。然后它将当前笔记和 保存current_index 到应用程序设置中,从应用程序设置中删除新笔记的值,更新 UI 以使表单显示其空状态,并在隐藏新笔记表单时显示列表。
exports.saveNote = function() { var new_note_title = pageData.get('item_title'); var new_note_photo = pageData.get('attachment_img'); current_index += 1; var new_index = current_index; var new_item = { index: new_index, title: new_note_title, photo: new_note_photo, show_photo: false }; notesArr.push(new_item); pageArray.push(new_item); appSettings.setString('notes', JSON.stringify(notesArr)); appSettings.setNumber('current_index', new_index); appSettings.remove('new_note_title'); appSettings.remove('new_note_photo'); pageData.set('showForm', false); pageData.set('item_title', ''); pageData.set('attachment_img', null); view.getViewById(page, 'list').animate({ opacity: 1, duration: 400 }); view.getViewById(page, 'form').animate({ translate: { x: 0, y: -160 }, duration: 800, }); }
deleteNote()功能_
deleteNote() 最后,当用户点击列表项中的删除按钮时,我们会执行该函数。正如您在之前的函数中已经看到的那样,一个对象作为参数传递给作为特定组件的事件处理程序附加的函数。该对象具有object 引用组件本身的属性。
从那里,您可以获取传递给它的属性的值。在这种情况下,我们获取index 属性的值,并使用它来获取要删除的项目的实际索引。
exports.deleteNote = function(args){ var target = args.object; var index_to_delete = notesArr.map(function(e) { return e.index; }).indexOf(target.index); notesArr.map(function(item, index){ if(index == index_to_delete){ notesArr.splice(index_to_delete, 1); pageArray.splice(index_to_delete, 1); return false; } }); appSettings.setString('notes', JSON.stringify(notesArr)); }
6.添加样式
打开app.css 文件并添加以下全局样式:
ActionBar { background-color: #b898ff; color: #fff; } .header-item { text-transform: uppercase; } .item { padding: 20; font-size: 20px; } .form-container { background-color: #fff; margin-top: -160px; padding: 20px; z-index: 10; } .label { font-size: 18px; } .link { text-align: left; background-color: transparent; color: #0275d8; padding: 5px; margin: 10px 0; text-transform: uppercase; font-size: 15px; } .image { width: 300; margin: 20 0; } .primary-button { padding: 5px; color: #fff; background-color: #0723bb; text-transform: uppercase; } .delete-button { font-size: 15px; background-color: #f50029; color: #fff; }
如果您想应用特定于页面的样式,您还可以创建一个notes-page.css 文件并在其中定义您的样式。
7.运行和调试应用程序
您可以在您的设备上运行该应用程序,方法是执行tns run ,然后是您要部署的平台。这是一个安卓的例子:
tns run android
如果尚未安装,这会自动为您安装 Android 平台,然后在安装后在您的设备上运行该应用程序。应用程序运行后,您可以执行tns livesync android --watch 以在每次更改源文件时自动刷新应用程序。
调试
就像任何其他应用程序框架一样,NativeScript 允许开发人员调试他们的应用程序。这是通过 chrome 开发工具完成的。有两种方法可以做到这一点:
如果有一个应用程序已经在运行,您可以打开一个新的终端窗口并执行 tns debug android --start 以将调试器附加到当前运行的应用程序实例。
如果您还没有运行应用程序,请使用tns debug android --debug-brk附加调试器构建应用程序实例。
无论您选择哪个选项,这都会在 Google Chrome 浏览器中打开一个新选项卡,让您可以像调试普通 JavaScript Web 应用程序一样调试应用程序。这意味着您实际上可以console.log 在源代码中使用来检查您正在使用的变量的内容。
结论和后续步骤
在本文中,您学习了如何使用 NativeScript 构建应用程序。具体来说,您已经学习了诸如使用 UI 组件、布局、样式、动画、使用设备相机以及使用应用程序设置维护应用程序状态等概念。
现在您已经构建了您的第一个 NativeScript 应用程序,现在是时候通过检查您可以使用 NativeScript 做什么并使用它构建更多应用程序来进一步提高您的技能了。
- 初始化
- pageLoaded()功能_
- newNote() 功能_
- btnLoaded()功能_
- openCamera()功能_
- saveNote()功能_
- deleteNote()功能_
- 调试