1. 基礎知識
1.1 為什麼選擇NativeScript
1.1.1 什麼是NativeScript
NativeScript可以用javascript來寫Android和iOS的應用,如下圖所示NativeScript的程式碼與網頁開發的程式碼很相似,都是用CSS寫樣式,javascript寫業務邏輯,不同的是NativeScript使用XML來描述頁面結構(NativeScript封裝了自己的UI庫)。
1.1.2 移動端開發方式的對比
如下圖所示, 移動應用程式可分為四大類:native, hybrid, cross compiled, and just-in-time (JIT) compiled
讓我們根據下圖一起看一看不同型別的移動應用程式之間的差異以及它們如何在裝置上執行 hybrid app本質上是在web瀏覽器中執行的網頁。cross compiled app通過編譯器被轉換成native app。而JIT compiled app(例如NativeScript)執行在一個 JavaScript的虛擬機器裡。1.1.3 NativeScript的優點
更少的shim程式碼(處理Android和iOS的不同)、一次寫入(Android和iOS共用一套程式碼)、隨處部署等等。
1.1.4 NativeScript能構建什麼樣的應用
因為NativeScript應用程式直接執行在裝置上,並由執行在應用程式內部的JavaScript虛擬機器解釋,這意味著NativeScript應用程式不受訪問本機裝置api或硬體的限制,因此任何應用程式都可以編寫為NativeScript應用。理論上是這樣的,但是NativeScript應用程式是在JavaScript虛擬機器中執行的,所以在應用程式和裸機之間有一個額外的(儘管很小)抽象層。要從裝置中提取每一點效能,所以它不適合構建圖形密集的遊戲。
1.1.5 NativeScript是怎麼工作的
NativeScript Runtime
:NativeScript Runtime是連線JavaScript程式碼和Android和iOS原生API之間的介面程式碼。就像瀏覽器製造商教他們的JavaScript虛擬機器如何使用DOM和windows物件一樣,NativeScript Runtime也教JavaScript虛擬機器如何使用本機裝置底層的API。NativeScript Core Modules
: NativeScriptCore Modules是一組庫,這些庫是用來構建應用程式並指示NativeScript執行時在裝置上做什麼。核心模組由不同的庫組成,如UI元件(按鈕、列表檢視、標籤)、導航和應用程式。JavaScript virtual machine
:理解並執行JavaScript的程式碼,但是不知道怎麼與裝置互動,所以NativeScript開發團隊編寫了介面程式碼(稱為NativeScript Runtime和NativeScript Core Modules)來教JavaScript虛擬機器有關Android和iOS等移動裝置API的知識。NativeScript CLI
:NativeScript CLI抽離了本地工具和SDK的複雜性,為我們提供了一組與平臺無關的命令來構建和部署應用程式。
1.2 第一個NativeScript的應用
- 參考文件的Full Setup搭建環境
- 在頁面下載Android Studio然後按照教程配置Android的模擬器
- 開啟命令列工具,執行
tns create HelloWorld --template tns-template-hello-world
- 進入目標資料夾
cd [filename]
- 執行命令
tns run android --emulator
,即可在Android模擬器上執行程式碼
2. 構建應用
2.1 剖析NativeScript應用程式
2.1.1 探索NativeScript應用程式的結構
|- app //開發目錄
|- App_Resources //放置Android和iOS平臺特殊的配置,例如App的圖示等。
|- Android
|- iOS
|- app.css //全域性的CSS樣式,app執行時載入
|- app.js //啟動檔案,裡面說明了從哪個頁面啟動應用
|- bundle-config.js //用於配置webpack(如果已安裝了)
|- main-page.js //寫業務邏輯
|- main-page.xml //寫頁面程式碼
|- main-view-model.js //相當於MVVM框架的vm層
|- package.json //描述應用程式的特徵和依賴項
|- references.d.ts //為編輯器提供TypeScript的智慧提示
|- node_modules //依賴的庫檔案
|- platforms //由NativeScript自動生成和維護
|- package.json //描述應用程式的特徵和依賴項
複製程式碼
// package.json
{
"description": "NativeScript Application", //提供應用程式、功能和用途的簡要說明
"license": "SEE LICENSE IN <your-license-filename>", //將協作開發人員指向您的許可檔案,以描述其他人必須對您的應用程式程式碼作出貢獻、修改、更改和重新分發哪些許可權(可選)
"readme": "NativeScript Application", //指向應用程式的README檔案
"repository": "<fill-your-repository-here>", //應用程式公共或私有程式碼儲存庫的位置(可選)
"nativescript": { //一個特定於nativescript的部分,帶有應用程式的識別符號,Android和iOS平臺使用它惟一地標識應用程式
"id": "org.nativescript.myapp"
},
"dependencies": { //npm使用的外部庫和應用程式所依賴的庫版本的列表
"nativescript-theme-core": "~1.0.2",
"tns-core-modules": "3.1.0"
}
}
複製程式碼
2.2 頁面和導航
2.2.1 建立多頁面應用
- 按照之前的操作建立HelloWorld專案
- 進入app資料夾,刪除資料夾下的main-page.js, main-page.xml, main-view-model.js, 在app資料夾下建立view資料夾放頁面,每個頁面單獨建立資料夾,資料夾應該與頁面命名一致,檔案目錄如下:
|- app
|- views
|- home
|- home.css
|- home.xml
|- home.js
|- about
|- about.css
|- about.xml
|- about.js
複製程式碼
- 修改專案啟動檔案app.js,將app啟動頁面改成home頁面
require("./bundle-config");
var application = require("application");
application.start({ moduleName: "views/home/home" }); //將moduleName的鍵值修改成home頁面的路徑
複製程式碼
- 在home.xml檔案中寫入如下程式碼
<Page> //頁面中所有其他元素的容器,類似於網頁開發裡面的body標籤
<StackLayout> //佈局元素,類似於網頁開發裡面的div標籤
<Label text="Welcome to the Tekmo App!" /> //在螢幕上顯示文字
</StackLayout>
</Page>
複製程式碼
- 在about.xml檔案中寫入如下程式碼
<Page>
<StackLayout>
<Label textWrap="true" text="Small company that wants to bring you the best in retro gaming!" /> //textWrap為true時會自動換行
<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" /> //tap屬性告訴NativeScript在單擊按鈕時呼叫onTap這個JavaScript函式
</StackLayout>
</Page>
複製程式碼
- 在home.js檔案中寫入如下程式碼
var frameModule = require("ui/frame"); //獲取NativeScript框架中導航模組的引用
function onTap() {
frameModule.topmost().navigate("views/about/about "); //使用frame模組導航到about頁面
}
exports.onTap = onTap; //必須匯出該函式,以便NativeScript執行時可以從UI訪問它
複製程式碼
2.2.3 在頁面導航間應用轉換動畫
如下表所示是各個平臺支援的過渡動畫
接下來讓我們看一下如何將這些過渡動畫新增到導航跳轉,在home.js中寫入如下程式碼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(); //pet物件是一個可觀察的物件,它將繫結到頁面上的所有元素
page.bindingContext = pet; //將pet設定為頁面的繫結上下文,將其設定為用於繫結的頁面級可觀察物件
pet.set("Name", "Riven");
//也可以寫成下面的形式
//var pet = new observable.fromObject({
// Name: "Riven"
//});
//page.bindingContext = pet;
}
複製程式碼
3.1.2 列表資料的雙向繫結
- 在home.xml檔案中寫入如下程式碼
<Page loaded="onLoaded">
<StackLayout>
<ListView items="{{ pages }}" itemTap="onItemTap"> //pages是一個可觀察陣列
<ListView.itemTemplate> //pages中的每一項會被渲染一次
<StackLayout>
<Label text="{{ title, title + ' Scrapbook Page' }}" /> //pages每一項上的title
</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;
};
複製程式碼
可以將資料繫結的內容封裝成單獨的pageName-view-model.js檔案,方便多個頁面共用一個view model.
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); //使用documents資料夾儲存應用程式需要的離線檔案
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() //返回一個promise
.then(function (picture) { //當promise解析後,呼叫then()函式,傳遞圖片
image.fromAsset(picture).then(function (imageSource) {
scrapbookPage.set("image", imageSource); //建立要繫結到檢視的影像源物件
});
});
}
複製程式碼
如果儲存圖片,需要先用 image.toBase64String("png") 將圖片的二進位制資料轉換成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) { //關閉modal時的回撥函式,可以將modal頁面的資料傳遞回來
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.xml
,page-name.land.minWH600.js
,page-name.land.minWH600.css
檔案單獨寫平板端的頁面。
3.5 部署前的配置
3.5.1 修改app圖示
- Android
Android的app圖示放在 App_Resources目錄下的drawable-*的各個資料夾中,也就是不同解析度的裝置用相應的圖示
不通過資料夾對應的裝置的解析度如下表所示:
所以修改Android的app圖示時,可以先在這個網站生成需要的圖示再放入對應資料夾。
在
App_Resources/ AndroidManifest.xml
檔案中有關於app的各項設定
<application
android:name="com.tns.NativeScriptApplication"
android:allowBackup="true"
android:icon="@drawable/icon" //在這裡修改app的圖示
android:label="@string/app_name" //在string.xml裡配置app_name
android:theme="@style/AppTheme" >
<activity
android:name="com.tns.NativeScriptActivity"
android:label="@string/title_activity_kimera" //在string.xml裡配置使用時app的名字
android:configChanges="keyboardHidden|orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="com.tns.ErrorReportActivity"/>
</application>
複製程式碼
- iOS
在
App_Resources\iOS\Assets.xcassets\AppIcon.appiconset
檔案下放入用入在這個網站下生成的iOS需要的適配各種裝置的圖示
3.5.2 修改app名字
- Android
在
App_Resources/Android/values/strings.xml
裡面修改app的名字
<resources>
<string name="app_name">Pet Scrapbook</string> //在這裡改應用程式的名稱
<string name="title_activity_kimera">Pet Scrapbook</string> //使用時app的名字
</resources>
複製程式碼
- iOS
在
App_Resources/iOS/Info.plist
裡面修改app的名字
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string> //在這裡修改名字,可以把名字直接寫在<string>標籤裡
複製程式碼
3.5.3 修改app版本號
1. Android
在App_Resources/ AndroidManifest.xml
裡配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__PACKAGE__"
android:versionCode="1" //使用者看不到的內部版本號
android:versionName="1.0"> //使用者在谷歌商店可以看到的版本號
複製程式碼
2. iOS
在App_Resources/iOS/Info.plist
裡面修改app的版本號
<key>CFBundleVersion</key>
<string>1.0.0</string> //版本號
複製程式碼
<key>CFBundleShortVersionString</key>
<string>1.0.0</string> //構建號
複製程式碼
3.5.4 修改app適配機器
1. Android
在App_Resources/ AndroidManifest.xml
裡配置
<supports-screens
android:smallScreens="true" //支援約2-3英寸的螢幕
android:normalScreens="true" //支援約2-5英寸的螢幕
android:largeScreens="true" //支援約4-7英寸的螢幕
android:xlargeScreens="true"/> //支援約7+英寸的螢幕
複製程式碼
Android螢幕大小和相應的螢幕解析度DPIs如下表所示
2. iOS
在App_Resources/iOS/Info.plist
裡適配各種裝置
<key>UISupportedInterfaceOrientations</key> //適配iPhones
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key> //適配iPads
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
複製程式碼
3.5.5 修改app啟動頁面
1. Android
drawable-nodpi
目錄下的splash_screen.xml
配置了app的啟動頁面
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:gravity="fill">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background" /> //啟動螢幕的背景圖
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/logo" /> //啟動螢幕中心的logo圖片
</item>
</layer-list>
複製程式碼
啟動螢幕的背景圖和中心的logo圖配置的方法和app的圖示是相同的
2. iOS
- 在
App_Resources\iOS\Assets.xcassets\LaunchScreen.AspectFill.imageset
裡放啟動螢幕的背景圖 - 在
App_Resources\iOS\Assets.xcassets\LaunchScreen.Center.imageset
裡放啟動螢幕中心的logo圖片 可以在這個網站生成適配各種裝置的圖片
4. 結語
- 本文參考:官網書籍《The NativeScript Book》
- 更多學習資源: