• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

创建您的第一个NativeScript应用程序

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

  1. 设置 NativeScript

  2. 构建应用程序

  3. 运行应用程序

  4. 调试应用程序

我们将专门在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应用程序  第1张

首先执行以下命令来创建一个新的 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 开发工具完成的。有两种方法可以做到这一点:

  1. 如果有一个应用程序已经在运行,您可以打开一个新的终端窗口并执行 tns debug android --start 以将调试器附加到当前运行的应用程序实例。

  2. 如果您还没有运行应用程序,请使用tns debug android --debug-brk附加调试器构建应用程序实例。

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

结论和后续步骤

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

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


文章目录
  • 1.设置 NativeScript
  • 2.创建应用程序
  • 3.入口点文件
  • 4. 添加 UI 标记
  • 5. javaScript 代码
    • 初始化
    • pageLoaded()功能_
    • newNote() 功能_
    • btnLoaded()功能_
    • openCamera()功能_
    • saveNote()功能_
    • deleteNote()功能_
  • 6.添加样式
  • 7.运行和调试应用程序
    • 调试
  • 结论和后续步骤