创建您的第一个NativeScript应用

在上一篇文章中,我向您介绍了NativeScript 。 在这里,您了解了NativeScript的基础知识以及它与其他移动开发框架的不同之处。 这次,您将通过创建第一个NativeScript应用程序来动手。 我将引导您完成使用NativeScript构建应用的整个过程,从设置开发环境到在设备上运行该应用。 这是我将要讨论的内容的概述:

  1. 设置NativeScript
  2. 构建应用
  3. 运行应用
  4. 调试应用

我们将在Android平台上专门运行该应用程序。 但是,如果要部署到iOS,您仍然可以遵循,因为代码几乎相同。 唯一的区别在于设置NativeScript的过程以及运行应用程序时执行的命令。

该应用程序的完整源代码可从GitHub repo教程中获得

1.设置NativeScript

要设置NativeScript,你必须首先安装Node.js的 。 安装Node.js之后,您可以通过在终端上执行npm install -g nativescript来安装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应用

首先执行以下命令来创建新的NativeScript项目:

tns create noter --appid "com.yourname.noter"

noter是项目的名称, com.yourname.noter是唯一的应用程序ID。 将其提交到Play或App Store后,将在以后用于识别您的应用。 默认情况下, tns create命令将为您创建以下文件夹和文件:

  • 应用程式
  • node_modules
  • 平台
  • package.json

通常,您只需要触摸app目录中的文件即可。 但在某些情况下,您可能需要在platforms / android目录中编辑文件。 一种这样的情况是,当您尝试使用的插件没有自动链接所需的依赖项和资产时。

接下来,导航到应用程序目录并删除除App_Resources文件夹之外的所有文件。 然后创建以下文件:

  • app.js
  • app.css
  • notes-page.js
  • notes-page.xml

这些是NativeScript运行时将使用的文件。 就像构建网页时一样, .css文件用于样式设置, .js文件用于功能设置。 但是对于应用程序的标记,我们使用XML而不是HTML。 通常,您将为应用程序的每个屏幕(例如,登录,注册或仪表板)创建一个单独的文件夹,并在每个文件夹中包含XML,CSS和JavaScript文件。 但是由于此应用只有一个屏幕,因此我们在根目录中创建了所有文件。

如果您需要有关NativeScript目录结构的更多信息,请参阅《 NativeScript入门指南》的第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属性设置为所需的页面标题来指定标题。

下面,我们使用了胡子语法来输出notes-page.js文件中定义的app_title的值。 这是您输出绑定到页面的值的方式。

<Page.actionBar>
    <ActionBar title="{{ app_title }}">
		...
   
	</ActionBar>
</Page.actionBar>

要定义按钮,请首先使用<ActionBar.actionItems>作为父项,每个<ActionItem>将是您要定义的按钮。 tap属性指定在tap按钮时要执行的功能,而os.positionandroid.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>

接下来是实际的页面内容。 您可以使用一个或多个布局容器来排列不同的元素。 下面,我们使用了两个可用的布局: StackLayoutGridLayout

StackLayout允许您将所有元素堆叠在其中。 默认情况下,此布局的方向是垂直的,因此每个UI组件都堆叠在最后一个组件的下方。 想想乐高积木向下流动。

另一方面, GridLayout允许您以表结构排列元素。 如果您曾经使用过Bootstrap或其他CSS网格框架,那么这对您来说似乎很自然。 GridLayout使您可以定义可以在其中放置每个UI组件的行和列。 稍后我们将看一下如何实现它。 现在,让我们继续进行代码。

首先,让我们定义用于创建新笔记的表单。 就像在HTML中一样,您可以定义诸如idcssClassclass属性(等同于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。 稍后我们将看看如何定义可观察数组。

还要注意, ListView可以具有itemTap属性,该属性允许您指定在敲击ListView的项目时要执行的功能。 但是在这种情况下,我们并没有真正添加它,因为在轻按一项时我们不需要执行任何操作。

<ListView items="{{ notes }}" id="list" visibility="{{ showForm ? 'collapsed' : 'visible' }}">
    ...
</ListView>

可以使用<ListView.itemTemplate>定义ListView的项目。 在这里,我们使用<GridLayout>创建两行两列。 columns属性用于指定每行中需要多少列。

在这种情况下, *,*表示有两列,每列占用当前行中相等的可用空间。 因此,如果整行的总宽度为300像素,则每列的宽度将为150像素。 因此,基本上每个*代表一列,并且您使用逗号将它们分开。

rows属性的工作方式类似,但是控制单个行使用的空间量。 auto表示只消耗每一行的子级所需的空间量。

GridLayout定义了columnsrows之后,您仍然必须指定其子级属于哪个行和列。 第一行包含项目的标题(第一列)和删除按钮(第二列)。 第二行包含附加到商品的图像(第一列)。 通过使用每个元素的rowcol属性来指定rowcol

还要注意使用horizontalAlignmentverticalAlignment 。 您可以将其视为HTML的text-align属性的NativeScript等效项。 但是,我们正在对齐UI组件,而不是文本。 horizontalAlignment可以具有rightleftcenterstretch的值,而verticalAlignment可以具有topbottomcenterstretch 。 其中大多数都是不言自明的,除了stretch可以拉伸以占据可用的水平或垂直空间。

在这种情况下, horizontalAlignmentverticalAlignment用于将图像在其列内水平和垂直居中。 然后在删除按钮上使用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>

我们还没有为ListView指定itemTap属性。 相反,我们希望附加一个删除动作,只要在点击列表项中的删除按钮时将执行该动作。 每个项目都有一个index属性,我们将其作为自定义属性传递给Delete按钮。 这是用于标识每个项目的唯一密钥,以便我们在需要时可以轻松地引用它们。

还请注意已loaded属性。 正如<Page>具有已loaded属性一样,按钮也可以具有一个。 稍后您将看到如何使用它。

5. JavaScript代码

现在,我们准备看一下使它们全部起作用JavaScript。 在本节中,我们将对notes-page.js文件进行编码。

初始化

首先,我们导入data/observabledata/observable-array模块。 这些是NativeScript中的内置模块,可让我们创建可观察的对象和数组。 可观察变量使我们能够在这些对象和数组更新时自动更新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;

对表单进行动画处理以创建新的笔记。 为此,我们在view使用getViewById函数,并将上下文(当前页面)作为第一个参数,并将id属性传递给要操作的元素。

接下来,调用animate函数。 这将接受包含动画设置的对象。 在这里,我们希望表单在800毫秒的时间内从其原始位置向下滑动160个像素。

view.getViewById(page, 'form').animate({
    translate: { x: 0, y: 160 },    
    duration: 800,
});

newNote()函数

当用户点击标题上的New Item操作项时,将执行newNote()函数。 这将隐藏并显示新项目ListView并根据showForm的当前值上下滑动表单。

如果showFormtrue ,这表示当前正在显示它,我们将在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()函数

笔记-page.xml文件,我们有一个loaded在按钮删除附注属性。 这是在事件发生时执行的功能。

默认情况下,当在ListView项目中定义按钮时,分配给ListView itemTap属性的函数将不会执行。 这是因为NativeScript假定只能从那些按钮触发要为每个列表项执行的操作。

下面的代码是解决该默认行为的方法。 这基本上消除了对删除按钮的关注,因此当用户点击ListView项目时,您仍然可以执行功能。 在这种情况下,由于我们没有为项目分接头分配任何功能,因此我们实际上不需要此代码,但这是使用列表时的一个很好的工具。

exports.btnLoaded = function (args) {
  var btn = args.object;
  btn.android.setFocusable(false);
}

openCamera()函数

接下来是openCamera()函数,当用户点击“ 附加图像”按钮时将执行该函数。 使用相机模块时,当前状态不会保持,因此我们需要先将新便笺的标题保存到应用程序设置中。

之后,我们可以通过调用takePicture()方法在设备中启动默认的相机应用程序。 此方法接受包含图片设置的对象。 用户拍照后,在Android上单击“ 保存”按钮或在iOS上使用“使用图像”按钮,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()函数。 这将获取注释标题文本字段和图像路径的当前值,增加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()函数,当用户点击列表项内的删除按钮时,该函数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 ,然后再执行要部署的平台,在设备上运行该应用程序。 这是android的示例:

tns run android

如果尚未安装Android平台,它将自动为您安装Android平台,然后在安装后在您的设备上运行该应用。 应用程序运行后,您可以在每次对源文件进行更改时执行tns livesync android --watch以自动刷新应用程序。

调试

与其他任何应用程序框架一样,NativeScript允许开发人员调试其应用程序。 这是通过Chrome开发工具完成的。 有两种方法可以做到这一点:

  1. 如果已经有一个应用程序正在运行,则可以打开一个新的终端窗口,然后执行tns debug android --start将调试器附加到该应用程序当前正在运行的实例。
  2. 如果您尚未运行应用程序,请使用tns debug android --debug-brk来构建该应用程序的实例,并附加一个调试器。

无论您选择哪个选项,都将在Google Chrome浏览器中打开一个新标签,使您可以像调试普通JavaScript Web应用程序一样调试该应用程序。 这意味着您实际上可以在源代码中使用console.log来检查正在使用的变量的内容。

结论和后续步骤

在本文中,您学习了如何使用NativeScript构建应用程序。 具体来说,您已经学习了一些概念,例如使用UI组件,布局,样式,动画,使用设备摄像头以及使用应用程序设置维护应用程序状态。

现在,您已经构建了自己的第一个NativeScript应用程序,现在该通过检查NativeScript可以做什么以及使用它来构建更多应用程序来进一步提高您的技能。

翻译自: https://code.tutsplus.com/tutorials/create-your-first-nativescript-app--cms-26957

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值