NativeScript 移动应用开发全面教程

Viewed 0

1. 基础知识

1.1 为什么选择NativeScript

1.1.1 什么是NativeScript

NativeScript 允许开发者使用 JavaScript 来构建 Android 和 iOS 应用。其开发模式与网页开发类似,使用 CSS 处理样式,JavaScript 编写业务逻辑,但页面结构使用 XML 描述,这是因为 NativeScript 封装了自己的原生 UI 库。

1.1.2 移动端开发方式的对比

移动应用程序主要分为四大类:原生应用、混合应用、跨编译应用和即时编译应用。它们运行在设备上的方式各有不同。混合应用本质上是在 Web 浏览器中运行的网页;跨编译应用通过编译器转换为原生应用;而即时编译应用,例如 NativeScript,则运行在一个 JavaScript 虚拟机中。

1.1.3 NativeScript的优点

NativeScript 的主要优点包括:需要处理平台差异的垫片代码更少;一套代码可以同时部署到 Android 和 iOS 平台;以及能够访问完整的原生设备 API。

1.1.4 NativeScript能构建什么样的应用

由于 NativeScript 应用直接在设备上运行,并通过应用内部的 JavaScript 虚拟机解释执行,因此理论上它不受访问原生设备 API 或硬件的限制,可以构建任何类型的应用。但是,由于应用运行在 JavaScript 虚拟机中,在应用与底层硬件之间存在一个额外的抽象层,因此它不适合构建对图形性能要求极高的密集型游戏。

1.1.5 NativeScript是怎么工作的

  • NativeScript Runtime:作为连接 JavaScript 代码与 Android、iOS 原生 API 的桥梁。
  • NativeScript Core Modules:一组用于构建应用的核心库,包括 UI 组件、导航和应用程序逻辑。
  • JavaScript Virtual Machine:负责执行 JavaScript 代码,但需要通过 NativeScript Runtime 和 Core Modules 来学习如何与移动设备 API 交互。
  • NativeScript CLI:命令行工具,抽象了原生开发工具和 SDK 的复杂性,提供跨平台的构建和部署命令。

1.2 第一个NativeScript的应用

  1. 参考官方文档完成环境搭建。
  2. 下载并安装 Android Studio,然后按照教程配置 Android 模拟器。
  3. 在命令行中运行 tns create HelloWorld --template tns-template-hello-world 创建项目。
  4. 进入项目目录:cd [filename]
  5. 运行 tns run android --emulator 在 Android 模拟器上启动应用。

2. 构建应用

2.1 剖析NativeScript应用程序

2.1.1 探索NativeScript应用程序的结构

典型的 NativeScript 项目结构如下:

|- app
    |- App_Resources
        |- Android
        |- iOS
    |- app.css
    |- app.js
    |- bundle-config.js
    |- main-page.js
    |- main-page.xml
    |- main-view-model.js
    |- package.json
    |- references.d.ts
|- node_modules
|- platforms
|- package.json

项目根目录的 package.json 文件示例如下:

{
    "description": "NativeScript Application",
    "license": "SEE LICENSE IN <your-license-filename>",
    "readme": "NativeScript Application",
    "repository": "<fill-your-repository-here>",
    "nativescript": {
        "id": "org.nativescript.myapp"
    },
    "dependencies": {
        "nativescript-theme-core": "~1.0.2",
        "tns-core-modules": "3.1.0"
    }
}

2.2 页面和导航

2.2.1 创建多页面应用

  1. 创建一个 HelloWorld 项目。
  2. app 目录下,删除默认的主页面文件,并创建 views 文件夹来组织页面。每个页面拥有独立的文件夹,例如 homeabout,其中包含对应的 .css.xml.js 文件。
  3. 修改 app.js 启动文件,将起始页面设置为 home 页面。
  4. home.xml 示例:
<Page>
    <StackLayout>
        <Label text="Welcome to the Tekmo App!" />
    </StackLayout>
</Page>
  1. about.xml 示例:
<Page>
    <StackLayout>
        <Label textWrap="true" text="Small company that wants to bring you the best in retro gaming!" />
        <Label text="Come visit us in Louisville, KY" />
    </StackLayout>
</Page>

2.2.2 多页面之间的导航

home.xml 中添加一个按钮:

<Page>
    <StackLayout>
        <Label text="Welcome to the Tekmo App!" />
        <Button text="About" tap="onTap" />
    </StackLayout>
</Page>

home.js 中实现导航逻辑:

var frameModule = require("ui/frame");
function onTap() {
    frameModule.topmost().navigate("views/about/about");
}
exports.onTap = onTap;

2.2.3 在页面导航间应用转换动画

不同平台支持多种过渡动画。在导航时添加动画的示例:

var frames = require("ui/frame");
function onTap() {
    var navigationEntry = {
        moduleName: "views/about/about",
        transition: {
            name: "slideBottom"
        }
    };
    frames.topmost().navigate(navigationEntry);
}
exports.onTap = onTap;

2.3 理解页面布局

关于布局容器的详细信息,请参考官方文档。

2.4 写app的样式

应用样式的编写方法,请参考官方文档。

3. 改善应用

3.1 数据绑定

3.1.1 普通数据的双向绑定

home.xml 中绑定数据:

<Page loaded="onLoaded">
    <StackLayout>
        <Label text="{{ Name }}" />
    </StackLayout>
</Page>

home.js 中设置数据源:

var observableModule = require("data/observable");
var viewModule = require("ui/core/view");
exports.onLoaded = function(args){
    var page = args.object;
    var pet = new observableModule.Observable();
    page.bindingContext = pet;
    pet.set("Name", "Riven");
}

3.1.2 列表数据的双向绑定

home.xml 中绑定列表数据:

<Page loaded="onLoaded">
    <StackLayout>
        <ListView items="{{ pages }}" itemTap="onItemTap">
            <ListView.itemTemplate>
                <StackLayout>
                    <Label text="{{ title, title + ' Scrapbook Page' }}" />
                </StackLayout>
            </ListView.itemTemplate>
        </ListView>
    </StackLayout>
</Page>

home.js 中设置列表数据源:

var observable = require("data/observable");
var observableArray = require("data/observable-array");
exports.onLoaded = function(args) {
    var page = args.object;
    var filledPage = new observable.Observable({
        title: "Riven's Page"
    });
    var home = new observable.Observable({
        pages: new observableArray.ObservableArray(filledPage)
    });
    page.bindingContext = home;
};

可以将数据绑定逻辑封装在单独的 ViewModel 文件中以便复用。

3.2 NativeScript与设备硬件的交互

3.2.1 文件系统模块

使用文件系统模块读写文件的示例:

var fileSystemModule = require("file-system");
exports.onLoaded = function(){
    var fileName = "myFile.json";
    var file = fileSystemModule.knownFolders.documents().getFile(fileName);
    var data = {name: "Brosteins", type: "filesystemexample"};
    var jsonDataToWrite = JSON.stringify(data);
    file.writeText(jsonDataToWrite);
    console.log("Wrote to the file: " + jsonDataToWrite);
    var jsonDataRead = file.readTextSync();
    console.log("Read from the file: " + jsonDataRead);
    file.remove();
};

3.2.2 相机

首先安装相机插件:npm install nativescript-camera --save。使用示例:

var camera = require("nativescript-camera");
var image = require("image-source");
exports.onAddImageTap = function (args) {
    var page = args.object;
    var scrapbookPage = page.bindingContext;
    camera.requestPermissions();
    camera
        .takePicture()
        .then(function (picture) {
            image.fromAsset(picture).then(function (imageSource) {
                scrapbookPage.set("image", imageSource);
            });
        });
}

如需保存图片,需要先将图片数据转换为 Base64 字符串。

3.2.3 GPS定位

安装定位插件:tns plugin add nativescript-geolocation。结合相机使用的示例:

var camera = require("nativescript-camera");
var image = require("image-source");
var geolocation = require("nativescript-geolocation");
exports.onAddImageTap = function (args) {
    var page = args.object;
    var scrapbookPage = page.bindingContext;
    if (!geolocation.isEnabled()) {
        geolocation.enableLocationRequest();
    }
    camera
        .takePicture({ width: 100, height: 100, keepAspectRatio: true })
        .then(function (picture) {
            image.fromAsset(picture).then(function (imageSource) {
                scrapbookPage.set("image", imageSource);
            });
            geolocation.getCurrentLocation().then(function (location) {
                scrapbookPage.set("lat", location.latitude);
                scrapbookPage.set("long", location.longitude);
            });
        });
};

3.3 创建具有主题的专业UI

关于应用主题的详细指南,请参考官方文档。

3.4 改善用户的体验

3.4.1 用modal构建更专业的UI

主页面 xml 示例:

<Page backgroundColor="green" loaded="onLoaded">
    <StackLayout backgroundColor="lightGreen">
        <Button text="BirthDate" tap="onBirthDateTap"/>
    </StackLayout>
</Page>

主页面 js 示例:

var page;
exports.onLoaded = function(args) {
    page = args.object;
    var scrapbookPage = page.navigationContext.model;
    page.bindingContext = scrapbookPage;
};
exports.onBirthDateTap = function(args) {
    var modalPageModule = "views/selectDate-page";
    var context = { birthDate: page.bindingContext.birthDate };
    var fullscreen = true;
    page.showModal(
        modalPageModule,
        context,
        function closeCallback(birthDate) {
            page.bindingContext.set("birthDate", birthDate);
        },
        fullscreen
    );
};

Modal 页面 xml 示例:

<Page shownModally="onShownModally" loaded="onLoaded">
    <StackLayout>
        <DatePicker date="{{ date }}" />
        <Button class="btn btn-primary btn-rounded-sm btn-active" text="Done" tap="onDoneTap" />
    </StackLayout>
</Page>

Modal 页面 js 示例:

var observableModule = require("data/observable");
var model;
var closeCallback;
exports.onLoaded = function(args) {
    var page = args.object;
    model = new observableModule.fromObject({
        date: new Date(Date.now())
    });
    page.bindingContext = model;
};
exports.onShownModally = function(args) {
    closeCallback = args.closeCallback;
};
exports.onDoneTap = function(args) { closeCallback(model.date); };

3.4.2 适配平板电脑

可以通过创建特定文件来为平板设备定制界面,例如 page-name.land.minWH600.xmlpage-name.land.minWH600.jspage-name.land.minWH600.css

3.5 部署前的配置

3.5.1 修改app图标

  • Android:将不同分辨率的图标放入 App_Resources/Android/drawable-* 目录下。图标路径在 App_Resources/Android/AndroidManifest.xml 文件的 android:icon 属性中指定。
  • iOS:将适配各种设备的图标放入 App_Resources/iOS/Assets.xcassets/AppIcon.appiconset 目录中。

3.5.2 修改app名字

  • Android:在 App_Resources/Android/values/strings.xml 中修改 app_nametitle_activity_kimera 字符串。
  • iOS:在 App_Resources/iOS/Info.plist 中修改 CFBundleDisplayName 的值。

3.5.3 修改app版本号

  • Android:在 App_Resources/Android/AndroidManifest.xml 中设置 android:versionCodeandroid:versionName
  • iOS:在 App_Resources/iOS/Info.plist 中设置 CFBundleVersionCFBundleShortVersionString

3.5.4 修改app适配机器

  • Android:在 App_Resources/Android/AndroidManifest.xml 中配置 supports-screens 属性以支持不同屏幕尺寸。
  • iOS:在 App_Resources/iOS/Info.plist 中通过 UISupportedInterfaceOrientationsUISupportedInterfaceOrientations~ipad 键来适配 iPhone 和 iPad 的设备方向。

3.5.5 修改app启动页面

  • Android:启动屏幕在 drawable-nodpi/splash_screen.xml 中配置,指定背景图和中心 Logo。
  • iOS:启动屏幕的背景图和 Logo 分别放置在 App_Resources/iOS/Assets.xcassets/ 下的对应目录中。

4. 结语

本文内容基于《The NativeScript Book》一书整理。更多学习资源,请参考 NativeScript 官方文档和视频教程。

0 Answers