檔案存取與拍照
在這個範例專案中,將會練習兩個在行動裝置開發過程中,相當重要的功能,那就是 檔案存取 與 行動裝置的專屬功能呼叫,在這裡將會展示如何在核心PCL專案內,呼叫個專屬平台的拍照功能,並且將拍照完成的照片回傳並顯示到行動裝置螢幕上。
由於每個行動裝置平台對於檔案存取與拍照功能的呼叫與使用方式都不盡相同,原則上,您可以透過相依服務注入(前面有提過的
DependencyService
),此時,您需要定義一個服務介面,並且在各個平台實作出這些服務功能,讓核心PCL的類別可以呼叫。由於 Xamarin.Forms 提供了許多插件(Plugin),因此,可以簡化許多開發上的需求。
因此,可以從
Components / Xamarin
https://components.xamarin.com/ 找到這些插件來使用,這裡,將會使用到 Media Plugin
與 PCL Storage
這兩個插件。建立標籤式的樣板式頁面方案
- 首先,開啟您的 Visual Studio 2015
- 接著透過 Visual Studio 2015 功能表,選擇這些項目
檔案
>新增
>專案
準備新增一個專案。 - 接著,Visual Studio 2015 會顯示
新增專案
對話窗,請在這個對話窗上,進行選擇Visual C#
>Cross-Platform
>Blank Xaml App (Xamarin.Forms Portable)
- 接著,在最下方的
名稱
文字輸入盒處,輸入XFFilePhoto
這個名稱,最後使用滑鼠右擊右下方的確定
按鈕。 - 當專案建立到一半,若您的開發環境還沒有建置與設定完成 Mac 電腦與 Xamarin Studio for Mac 系統,此時會看到
Xamarin Mac Agent Instructions
對話窗出現,這個對話窗是要提醒您進行與 Mac 電腦連線設定,這是因為,您無法在 Windows 作業系統進行 iOS 相關應用程式的建立與設計工作,而是需要透過 Mac 電腦安裝的 XCode 來協助您完成這些 iOS 應用程式的建立工作。不過,這不影響您繼續開發 Xamarin.Forms 的應用程式,只不過此時,您無法編譯與執行 iOS 的應用程式。 - 接著會看到
新的通用Windows專案
對話視窗,此時,您只需要按下確定
按鈕即可,此時,專案精靈會繼續完成相關平台的專案建立工作。 - 最後,整個新的 Xamarin.Forms 專案就建立完成了。
準備安裝套件
請依照底下說明,將這些插件安裝到方案的所有專案中。
Media Plugin
- 滑鼠右擊方案節點
XFFilePhoto
,接著選擇管理方案的 NuGet 套件
- 在
NuGet - 解決方案
子標籤頁次出現後,點選瀏覽
- 請在搜尋文字輸入盒內,輸入
Xam.Plugin.Media
,接著點選Xam.Plugin.Media
項目;在右方點選要安裝到全部的專案內,最後,點選安裝
按鈕
PCL Storage
- 滑鼠右擊方案節點
XFFilePhoto
,接著選擇管理方案的 NuGet 套件
- 在
NuGet - 解決方案
子標籤頁次出現後,點選瀏覽
- 請在搜尋文字輸入盒內,輸入
PCLStorage
,接著點選PCLStorage
項目;在右方點選要安裝到全部的專案內,最後,點選安裝
按鈕
MainPage
- 在安裝好所有的擴充插件(Plugins),打開核心PCL 的 MainPage.xaml 檔案,將底下的 XAML 宣告定義複製到這個檔案內。
- 首先為了避免在 iOS 平台執行時候,最上方的狀態烈被遮蔽掉的問題,因此,透過
ContentPage.Padding
來定義整個頁面皆內縮 20dp。 - 這個頁面的控制項,使用了
Grid
版面配置來進行所有控制項的定位,透過Grid.Row
Grid.Column
附加屬性,可以指定控制項要出現在哪個位置上。 - 這個頁面定義內容相當簡單,有四個
Image
控制項,五個按鈕,其中四個按鈕會啟動行動裝置的拍照功能,進行拍照一張照片,接著,顯示在行動裝置螢幕上;而最後一個按鈕,則是展示了如何使用 PCLStorage 插件所提供的功能,進行檔案存取。
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:XFFilePhoto"
x:Class="XFFilePhoto.MainPage">
<ContentPage.Padding>20</ContentPage.Padding>
<Grid
>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image x:Name="image1" Grid.Row="0" Grid.Column="0"/>
<Image x:Name="image2" Grid.Row="0" Grid.Column="1"/>
<Image x:Name="image3" Grid.Row="2" Grid.Column="0"/>
<Image x:Name="image4" Grid.Row="2" Grid.Column="1"/>
<Button x:Name="buttonImage1" Text="拍照" Clicked="OnbuttonImage1" Grid.Row="1" Grid.Column="0" />
<Button x:Name="buttonImage2" Text="拍照" Clicked="OnbuttonImage2" Grid.Row="1" Grid.Column="1" />
<Button x:Name="buttonImage3" Text="拍照" Clicked="OnbuttonImage3" Grid.Row="3" Grid.Column="0" />
<Button x:Name="buttonImage4" Text="拍照" Clicked="OnbuttonImage4" Grid.Row="3" Grid.Column="1" />
<Button x:Name="buttonFile" Text="檔案存取" Clicked="OnbuttonFile" Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>
</ContentPage>
- 請將下列 C# 程式碼複製到 MainPage.xaml.cs內。
- 在拍照按鈕處理事件中,會呼叫
public async Task 拍照(ImageLocation fooImageLocation = ImageLocation.Location1)
方法,並且傳遞列舉參數,告知是要將拍照圖片顯示在哪個 Image 控制項。 - 拍照方法,使用了
CrossMedia.Current
取得跨平台的拍照物件,透過TakePhotoAsync
非同步方法,呼叫當時行動裝置平台的拍照功能,一旦,使用這確定拍照完成,就會將所拍照照片資訊回傳給file
物件;若使用者取消拍照,則file
物件則會為空值。當取得了拍照照片檔案資訊,將會透過 PCLStorage 所提供的功能TakePhotoAsync
IFile
取得可以存取這個照片的內容,接著,將這個照片透過方法MoveAsync
將其搬移到該應用程式本地資料夾下。最後,使用IFile
提供的方法OpenAsync
取得對該照片檔案的Stream
,並且產生ImageSource
物件,接著綁定到指定的 Image 控制項,此時,行動裝置螢幕上,就會顯示出該圖片。 - 至於 檔案存取 按鈕,則是透過
IFolder
IFile
這兩個介面,取得指定檔案的存取權,使用WriteAllTextAsync
ReadAllTextAsync
對該檔案進行讀寫動作。由於這個檔案會繼續存在於行動裝置上(除非使用者移除了該應用程式),因此,當下次應用程式再度開啟後,是可以把這些資料重新取回。
MainPage.xaml.cs
using PCLStorage;
using Plugin.Media;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace XFFilePhoto
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
async void OnbuttonImage1(object sender, EventArgs e)
{
await 拍照(ImageLocation.Location1);
}
async void OnbuttonImage2(object sender, EventArgs e)
{
await 拍照(ImageLocation.Location2);
}
async void OnbuttonImage3(object sender, EventArgs e)
{
await 拍照(ImageLocation.Location3);
}
async void OnbuttonImage4(object sender, EventArgs e)
{
await 拍照(ImageLocation.Location4);
}
async void OnbuttonFile(object sender, EventArgs e)
{
IFolder rootFolder = FileSystem.Current.LocalStorage;
IFolder sourceFolder = await FileSystem.Current.LocalStorage.CreateFolderAsync("MyDatas", CreationCollisionOption.ReplaceExisting);
IFile sourceFile = await sourceFolder.CreateFileAsync("MyFile.dat", CreationCollisionOption.ReplaceExisting);
await sourceFile.WriteAllTextAsync("這是我自己寫入檔案的內容");
var fooContent = await sourceFile.ReadAllTextAsync();
await DisplayAlert("讀出檔案的內容", fooContent, "OK");
}
public async Task 拍照(ImageLocation fooImageLocation = ImageLocation.Location1)
{
string fooImageFilename = $"{fooImageLocation.ToString()}.jpg";
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
Directory = "Sample",
Name = fooImageFilename
});
if (file == null)
return;
IFolder rootFolder = FileSystem.Current.LocalStorage;
IFolder sourceFolder = await FileSystem.Current.GetFolderFromPathAsync(file.Path.Replace(fooImageFilename, ""));
IFile sourceFile = await FileSystem.Current.GetFileFromPathAsync(file.Path);
IFolder TargetFolder = await rootFolder.CreateFolderAsync("Images", CreationCollisionOption.OpenIfExists);
await sourceFile.MoveAsync($"{TargetFolder.Path}/{fooImageFilename}");
IFile fileX = await TargetFolder.GetFileAsync(fooImageFilename);
var s = await fileX.OpenAsync(FileAccess.Read);
switch (fooImageLocation)
{
case ImageLocation.Location1:
image1.Source = ImageSource.FromStream(() => s);
break;
case ImageLocation.Location2:
image2.Source = ImageSource.FromStream(() => s);
break;
case ImageLocation.Location3:
image3.Source = ImageSource.FromStream(() => s);
break;
case ImageLocation.Location4:
image4.Source = ImageSource.FromStream(() => s);
break;
default:
break;
}
}
}
public enum ImageLocation
{
Location1,
Location2,
Location3,
Location4,
}
}
實際執行畫面
Android 執行結果
請在方案總管內,滑鼠右擊
XFFilePhoto.Droid
專案,選擇 設定為起始專案
,接著按下 F5
開始執行。佈署注意事項
iOS 執行結果
請在方案總管內,滑鼠右擊
XFFilePhoto.iOS
專案,選擇 設定為起始專案
,接著按下 F5
開始執行。