本課的目標是整合Camera這樣我們可以用他來拍照了,但是要達到這個目標需要下點功夫踏堡。
除了要出發(fā)相機拍照之外,我們還要:
- 將照片移動到手機上的持久儲存
- 在模板中展示這些照片
- 顯示其他的照片
- 允許刪除照片
這樣看來本課程還是蠻大的。在本應(yīng)用的開始部分我們設(shè)置了Ionic Native锅尘,也就是基礎(chǔ)部分里面的內(nèi)容,Ionic Native是Ionic包裝好的Cordova插件布蔗,使用更簡單藤违。
本課程中將用到Ionic Native中的Camera插件和File插件。
讓我們愉快的開始吧纵揍!
創(chuàng)建一個Photo數(shù)據(jù)模型
在我們添加相片功能之前顿乒,我們需要創(chuàng)建一個數(shù)據(jù)模型來代表照片對象。當我們想要存放照片存儲路徑泽谨,照片拍攝日期的時候璧榄。數(shù)據(jù)模型就能夠很輕松的處理這樣的事情,像這樣:
let photo = new PhotoModel('path/to/image', new Date());
rather than this:
let photo = {
image: 'path/to/image',
date: newDate
};
區(qū)別不是太明顯吧雹,也不是所有的原因都需要用到骨杂,但是是一個很好的模式,特別是當數(shù)據(jù)越來越復雜的時候吮炕。舉個復雜數(shù)據(jù)模型的例子腊脱,去看看Quick List課程,如果你還沒看過的話龙亲。
> 修改 src/models/photo-model.ts 為如下:
export class PhotoModel {
constructor(public image: string, public date: Date){
}
}
這個模型很直白陕凹,我們傳入了圖片和日期。如果想要在home.ts中使用他的話鳄炉,得先導入杜耙。
> 修改 src/pages/home/home.ts 為如下:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { PhotoModel } from '../../models/photo-model';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController) {
}
}
制作一個簡單的Alert服務(wù)
Surprise!(譯者:驚喜拂盯!好像不大好聽)在進入開心環(huán)節(jié)之前佑女,我們還有一個要做的事情。由于很多地方可能會有錯誤提示(拍照谈竿,移動照片)团驱,所以我們出發(fā)大量的警告框,當然空凸,事情處理好了之后我們也要告訴用戶一聲不是嚎花。
創(chuàng)建警告提示的語法是這樣的:
let alert = this.alertCtrl.create({
title: "My Title",
message: "My Message",
buttons: [
{
text: 'Ok'
}
]
});
alert.present();
代碼量還是有點的,如果我們在同一個文件里多次調(diào)用這個代碼呀洲,看起來會很亂紊选。所以我們新建另一個服務(wù)來處理這樣的事情啼止,就像數(shù)據(jù)模型一樣。創(chuàng)建完之后兵罢,我們就可以像下面這樣去彈出警告框:
let alert = this.simpleAlert.createAlert('Oops!', 'Something went wrong.');
alert.present();
> 修改 src/providers/simple-alert.ts 為如下:
import { Injectable } from '@angular/core';
import { AlertController } from 'ionic-angular';
@Injectable()
export class SimpleAlert {
constructor(public alertCtrl: AlertController){
}
createAlert(title: string, message: string): any {
return this.alertCtrl.create({
title: title,
message: message,
buttons: [
{
text: 'Ok'
}
]
});
}
}
這里我們導入了AlertController服務(wù)用來創(chuàng)建警告框献烦,然后創(chuàng)建了一個函數(shù),結(jié)束一個title和一個message卖词,然后返回一個用這些信息組裝的的警告框巩那。這雖然給我們創(chuàng)建了警告提示框,但是展示還是要為我們手動去展示出來坏平。
> 修改 src/pages/home/home.ts 如下:
import { Component } from '@angular/core';
import { NavController } from 'ionic-angular';
import { PhotoModel } from '../../models/photo-model';
import { SimpleAlert } from '../../providers/simple-alert/simple-alert';
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public simpleAlert: SimpleAlert)
{
}
}
注意我們在構(gòu)造器里面注入了SimpleAlert和NavController拢操,但是沒有注入照片數(shù)據(jù)模型。這是因為每次我們要用到PhotoModel的時候舶替,我們都是通過new新建一個實例的令境,但是對于SimpleAlert而言我們都是一次一次調(diào)用他統(tǒng)一實例的同一函數(shù)的。
使用Camera照相
好了顾瞪,完成了照片模型和彈窗服務(wù)之后 -- 我們終于來到了有趣的部分了舔庶,也就是拍照陈醒。我們現(xiàn)在要使用Ionic Native的Camera插件惕橙,我們得先進行導入:
> 在 src/pages/home/home.ts 中加入以下導入語句:
import { Camera, File } from 'ionic-native';
現(xiàn)在钉跷,我們可以通過Camera對象來訪問這個功能了弥鹦。我們將一下如何使用這個插件,但是記住所有Ionic Native里面的插件都可以在這里找到文檔:http://ionicframework.com/docs/native/Camera/
在寫拍照相關(guān)的代碼之前膝晾,我們將要到構(gòu)造器里面添加一些變量臊旭,這樣后面就不要老是導入這個導入那個领跛,我們把應(yīng)用后面會用到的全部都導入進來好了。我們將要加入一些新的函數(shù)撤奸,一些引用了其他函數(shù)的函數(shù)吠昭。這樣我們就不會遇到函數(shù)undefined的問題,我們?nèi)慷x好胧瓜,但是都留空矢棚。我們后續(xù)會詳細實現(xiàn)他們(不全部是在本課中)。
> 修改 src/pages/home/home.ts 為如下:
import { Component } from '@angular/core';
import { ModalController, AlertController, Platform } from 'ionic-angular';
import { PhotoModel } from '../../models/photo-model';
import { SimpleAlert } from '../../providers/simple-alert';
import { SlideshowPage } from '../slideshow/slideshow';
import { Data } from '../../providers/data'
import { Camera, File } from 'ionic-native';
declare var cordova;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
loaded: boolean = false;
photoTaken: boolean = false;
photos: PhotoModel[] = [];
constructor(public dataService: Data, public platform: Platform, public simpleAlert: SimpleAlert, public modalCtrl: ModalController, public alertCtrl: AlertController) {
}
ionViewDidLoad(){
// Uncomment to use test data
/*this.photos = [
new PhotoModel('http://placehold.it/100x100', new Date()),
new PhotoModel('http://placehold.it/100x100', new Date()),
new PhotoModel('http://placehold.it/100x100', new Date())
]*/
this.platform.ready().then(() => {
this.loadPhotos();
});
document.addEventListener('resume', () => {
if(this.photos.length > 0){
let today = new Date();
if(this.photos[0].date.setHours(0,0,0,0) === today.setHours(0,0,0,0)){
this.photoTaken = true;
} else {
this.photoTaken = false;
}
}
}, false);
}
loadPhotos(): void {
}
takePhoto(): any {
}
createPhoto(photo): void {
}
removePhoto(photo): void {
}
playSlideshow(): void {
}
sharePhoto(image): void {
}
save(): void {
}
}
上面的代碼中府喳,我們新增了一個成員變量loaded用于保持追蹤照片是否都從存儲中加載完成(下節(jié)課要做的)蒲肋,photoTaken用于標記今天是否有拍照,photos用于持有所有的照片數(shù)據(jù)钝满。由于照片只會在應(yīng)用運行在真實設(shè)備的情況下才會加載兜粘,所以我們在ionViewDidLoad中加入了一些測試數(shù)據(jù),我們在此處給this.photos添加了一個測試數(shù)據(jù)這樣就可以在瀏覽器中進行測試弯蚜。如果你想在測試的時候看到相片呈現(xiàn)孔轴,解除其中的注釋即可。(記得后面刪掉K檗唷)
我們已經(jīng)設(shè)置好了所有需要用到的服務(wù)的引用路鹰,包括稍后要用到的數(shù)據(jù)服務(wù)和平臺服務(wù)。
在這里我們也添加了一個奇怪的resume監(jiān)聽器收厨。resume事件的觸發(fā)時機是用戶把應(yīng)用發(fā)配到后臺然后重新使用的時候?qū)|發(fā)晋柱。例如,當用戶打開了你的應(yīng)用诵叁,然后去玩Facebook雁竞,然后有回到我們應(yīng)用的時候。我們這么做的原因是因為我們的photoTaken變量有一些極端的特例拧额。想象一下碑诉,某人今天拍照了,但是當他們關(guān)閉應(yīng)用但是沒有全部關(guān)閉的時候势腮,他只是被發(fā)配后臺联贩。第二天我們使用這個應(yīng)用的時候,我們在loadPhoto函數(shù)中運行的用于判斷當日是否拍照的邏輯將不會執(zhí)行捎拯,因為只是恢復而不是重新加載泪幌。所以當應(yīng)用恢復的時候,我們都要檢查最后照片拍攝日期是否跟今日日期椅子署照,然后根據(jù)他去設(shè)置photoTaken變量祸泪。
注意:我們這里加上來declare var cordova,這樣TypeScript不會抱怨:我根本不知道cordova是啥建芙。
現(xiàn)在我們將實現(xiàn)takePhoto函數(shù)没隘,這個函數(shù)是用來拍照的,所以我們先來個基礎(chǔ)版禁荸。
> 修改 takePhoto 函數(shù)為如下:
takePhoto(): any {
if(!this.loaded || this.photoTaken){
return false;
}
if(!this.platform.is('cordova')){
console.log("You can only take photos on a device!");
return false;
}
let options = {
quality: 100,
destinationType: 1, //return a path to the image on the device
sourceType: 1, //use the camera to grab the image
encodingType: 0, //return the image in jpeg format
cameraDirection: 1, //front facing camera
saveToPhotoAlbum: true //save a copy to the users photo album as well
};
Camera.getPicture(options).then((imagePath) => {
console.log(imagePath);
},
(err) => {
let alert = this.simpleAlert.createAlert('Oops!', 'Something went wrong.');
alert.present();
});
}
執(zhí)行Camera代碼之前右蒲,我們檢查了一系列的條件檢查阀湿。如果之前創(chuàng)建的變量顯示數(shù)據(jù)沒有加載完成的話,或者今日已拍照的話瑰妄,函數(shù)內(nèi)后面的代碼就不會執(zhí)行了陷嘴。我們也檢查了我們是否是運行在‘cordova’平臺上,也就是真機上间坐,如果沒有的話就直接退出函數(shù)灾挨。因為啊,你只能在真實設(shè)備上訪問這些插件竹宋,如果不是在真實設(shè)備上運行的話劳澄,那么除了報錯就只能報錯了。
接著我們設(shè)置了一些選項傳入Camera插件蜈七,這些配置了我們要干啥秒拔,我們要返回啥。這些值可以配置的東西包括宪潮,是否要用攝像頭或者用戶的照片庫溯警,返回圖片的格式,用前置攝像頭呢狡相,還是用后置攝像頭等等梯轻。我在每個選項后面都添加了注釋,來解釋他們分別是干啥的尽棕。
創(chuàng)建好了選項對象之后喳挑,我們調(diào)用了Camera對象的getPicture函數(shù)然后傳入其中。這個函數(shù)講會返回一個Promise滔悉,在執(zhí)行完成之后伊诵,這個Promise將會給我們返回一個圖片在設(shè)備上的存儲路徑。現(xiàn)在我們只是用日志輸出這個值回官,但是這個值后續(xù)會好好用上的曹宴。如果返回的是一個錯誤值的話,我們就用SimpleAlert來展示錯誤信息給用戶了歉提。
照片照好了笛坦,存放路徑也返回了,臨時文件夾里苔巨。這樣圖片臨時顯示出來了版扩,照片不會長久的保存,因為臨時目錄會被隨時清理掉侄泽。為解決此問題礁芦,我們需要把照片移動其他地方去,然后再次存儲這個照片的新路徑。我們可以用File插件來完成這個柿扣。
將照片移動到永久存儲中
為了用上File插件肖方,我們需要對takePhoto函數(shù)進行一些修改:
- 通過返回的 imagePath 來找到臨時存儲里面的照片
- 重命名,然后移動到設(shè)備上的‘snapaday’文件夾(也就是永久存儲)
- 基于這個新的位置新建一個照片對象
看來takePhoto函數(shù)會變得復雜了好多未状。因為要寫這么多代碼窥妇。我會對代碼詳細添加注釋流译,當然也會一步步的去講解牲尺。
> 修改 takePhoto里的 getPicture 調(diào)用如下:
Camera.getPicture(options).then((imagePath) => {
//Grab the file name
let currentName = imagePath.replace(/^.*[\\\/]/, '');
//Create a new file name
let d = new Date(),
n = d.getTime(),
newFileName = n + ".jpg";
if(this.platform.is('ios')){
//Move the file to permanent storage
File.moveFile(cordova.file.tempDirectory, currentName,cordova.file.dataDirectory, newFileName).then((success: any)=> {
this.photoTaken = true;
this.createPhoto(success.nativeURL);
this.sharePhoto(success.nativeURL);
}, (err) => {
console.log(err);
let alert = this.simpleAlert.createAlert('Oops!', 'Something went wrong.');
alert.present();
});
} else {
this.photoTaken = true;
this.createPhoto(imagePath);
this.sharePhoto(imagePath);
}
},(err) => {
let alert = this.simpleAlert.createAlert('Oops!', 'Something went wrong.');
alert.present();
});
希望上面添加的注釋可以讓后面將要進行的東西變得簡單些陪腌,這里有一個高級別的手把手的講解,從camera返回imagePath后發(fā)生了什么:
- 移除 imagePath 最后一個 / 前面的所有內(nèi)容得到當前文件名
- 使用日期新建一個唯一的文件名翻伺,這樣我們不會覆蓋任何東西
- 如果我們是在iOS上運行的話,我們將照片從臨時存儲移動到持續(xù)存儲中
- 將 photoTaken 設(shè)為 true沮焕,將新路徑傳給圖片然后傳到 createPhoto 和 sharePhoto吨岭。
做完這些的話咱們的相片就存儲到永久存儲空間去了。記住峦树,咱們現(xiàn)在還沒有定義createPhoto和sharePhoto函數(shù)辣辫。我們現(xiàn)在來創(chuàng)建createPhoto函數(shù),對于sharePhoto的話魁巩,我們留到整合社交分享插件的時候再來實現(xiàn)急灭。
createPhoto函數(shù)接受一個路徑參數(shù)(nativeURL本地路徑)并在應(yīng)用中持有他的引用。
> 修改 createPhoto 函數(shù)為如下:
createPhoto(photo): void {
let newPhoto = new PhotoModel(photo, new Date());
this.photos.unshift(newPhoto);
this.save();
}
你也看到了好簡單的說谷遂。我們在使用Photo Model新建相片實例的時候傳入了一個相片的本地路徑和創(chuàng)建日期葬馋,然后將新建的相片對象加入到this.photos數(shù)組。我們沒有用push方法(因為是將相片添加到數(shù)組尾部)肾扰,我們通過unshift將他添加到數(shù)組頭部畴嘶。我們這部做的原因是我們需要以反向的方式來展示圖片(最新的最先顯示),這么做我們的生活就更輕松集晚。
同時我們也調(diào)用了save函數(shù)窗悯,他會將我們的數(shù)據(jù)保存到數(shù)據(jù)存儲中,但是我們目前還沒有實現(xiàn)這個函數(shù)偷拔。
更新模板
我們現(xiàn)在將相片添加到了this.photos數(shù)組蒋院,我們現(xiàn)在可以將模板更新為循環(huán)顯示他們了。由于你可能是想通過瀏覽器測試条摸,不能通過照相機來添加照片悦污,那么請隨意打開測試數(shù)據(jù)的注釋,這樣就可以看到本部分代碼的效果钉蒲。
我們現(xiàn)在來更新模板切端。
> 修改 src/pages/home/home.html 為如下:
<ion-header>
<ion-navbar color="danger">
<ion-title>
<img src="assets/images/logo.png" />
</ion-title>
<ion-buttons end>
<button ion-button icon-only (click)="playSlideshow()"><ion-icon name="play"></ion-icon></button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content>
<ion-list>
<button ion-item *ngIf="!photoTaken" detail-none (click)="takePhoto()">
<img src="assets/images/smile.png" />
</button>
<ion-item-sliding *ngFor="let photo of photos">
<ion-item>
<img [src]="photo.image" />
<ion-badge item-right light>0 days ago</ion-badge>
</ion-item>
<ion-item-options>
<button ion-button icon-only color="light" (click)="removePhoto(photo)"><ion-icon name="trash"></ion-icon></button>
</ion-item-options>
</ion-item-sliding>
</ion-list>
</ion-content>
對模板主要有兩大變更。首先顷啼,我們用上了ngFor來村換相片數(shù)組踏枣,然后為每個項創(chuàng)建了一個<ion-item-sliding>入口昌屉。通過在let photo of photos中的let photo,我們可以得到當前渲染的照片的應(yīng)用茵瀑,即:photo.image訪問照片的存儲路徑间驮。
由于我們可以訪問到每張照片的路徑,我們就可以設(shè)置img元素來展示他了马昨。注意我們用來方括號[src]竞帽,他允許我們將photo.image設(shè)置到image元素上來。這意味著會先評估photo.image鸿捧,然后蛇之都奧src屹篓。如果我們不用方括號的話,src將是字符串"photo.image"匙奴。
我們也有一個刪除按鈕堆巧,他會將相片(也就是let photo)傳入到removePhoto函數(shù)。我們現(xiàn)在就來實現(xiàn)這個方法吧泼菌。
> 修改 src/pages/home/home.ts 里的 removePhoto 函數(shù):*
removePhoto(photo): void {
let today = new Date();
if(photo.date.setHours(0,0,0,0) === today.setHours(0,0,0,0)){
this.photoTaken = false;
}
let index = this.photos.indexOf(photo);
if(index > -1){
this.photos.splice(index, 1);
this.save();
}
}
這個函數(shù)的第二部分夠直白了谍肤,我們在photos數(shù)組中找到photo,移除他哗伯,觸發(fā)save荒揣。第一部分看起來有點迷。這里搞事的是笋颤,如果用戶拍勒個照乳附,然后刪除這個照片,他們就可以另外拍一張了(因為當日無照哇)伴澄。但是當他們刪除了較早的照片的時候赋除,他們就沒法拍照。
所以啦非凌,我們就獲取被刪除的照片的拍攝日期举农,然后跟今天日期進行對比。如果刪除的是今日的照片敞嗡,那么我們就把this.photoTaken設(shè)置為false這樣用戶今天有可以開心的拍另一張照片了颁糟。
總結(jié)
這節(jié)課實在是...!:磴病棱貌!這節(jié)課設(shè)計了相當復雜的東西,但是我們在這節(jié)課中實現(xiàn)了應(yīng)用的核心功能箕肃。還剩下一點點東西婚脱,但是我們現(xiàn)在可以拍照了,可以在將他們在一個列表中展示出來了,酷不酷障贸!如果你想看看效果的話错森,到真機上去跑一下吧。如果你不知道我們做了些啥篮洁,可以直接跳到本書的 測試與調(diào)試部分去了解如何在設(shè)備上安裝應(yīng)用涩维,然后回來完成應(yīng)用剩余部分。
重要:如果現(xiàn)在想要在真機上測試拍照袁波,那么你就需要將loadPhotos更新一下:
loadPhotos(): void {
this.loaded = true;
}
你不能在數(shù)據(jù)加載完成之前拍照瓦阐,所以我們得先偽造一下。在接下來的課程中篷牌,我們將學習如何創(chuàng)建一個數(shù)據(jù)服務(wù)來永久存儲我們的照片垄分,這樣在用戶后面回到應(yīng)用的時候可以獲取他們。