XAML in Xamarin.Forms 基礎篇 電子書

XAML in Xamarin.Forms 基礎篇 電子書
XAML in Xamarin.Forms 基礎篇 電子書

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
Xamarin.Forms 快速入門 電子書

2018/06/16

Xamarin.Essentials 體驗 1 : File System Helpers 檔案系統存取

根據 Xamarin.Essentials 官方文件中指出:Xamarin.Essentials 跨平台應用程式開發介面的開發人員提供他們的行動應用程式。透過 Xamarin.Essentials 的套件幫忙,可以將這些功能整合到您的應用程式:加速計 應用程式資訊 電池 剪貼簿 指南針 資料傳輸 裝置顯示資訊 裝置資訊 電子郵件 檔案系統的協助程式 手電筒 地理編碼 地理位置 迴轉儀 磁力計 開啟瀏覽器 電話撥號員 喜好設定 螢幕鎖定 安全儲存體 SMS 文字轉換語音 追蹤版本 震動。
我們首先來體驗關於 File System Helpers 檔案系統的協助程式 這項功能,因此,可以參考底下的專案範例,該專案範例位於 https://github.com/vulcanlee/xamarin-forms-sample2018/tree/master/XFESFileSystem

建立測試專案

  • 首先,我們先使用 Prism Template Pack 擴充功能所提供的專案樣板,建立起一個 Xamarin.Forms 專案,在這裡我們僅選擇 Android / iOS / UWP 類型的專案;接著,我們需要把 PropertyChanged.Fody NuGet 套件安裝到 .NET Standard 專案類別庫內,並且安裝 FodyWeavers.xml 檔案。
  • 緊接著,我們要開始安裝 Xamarin.Essentials NuGet 套件到所有的專案內,在這裡,請使用滑鼠右擊方案節點,選擇 管理方案的 NuGet 套件 選項
  • 請在 NuGet - 解決方案 的視窗中,輸入要搜尋的套件名稱 : Xamarin.Essentials
    並且,請勾選 包括搶鮮版 的文字檢查盒,並且安裝這個套件;我寫這篇文章的時候,Xamarin.Essentials 的最新版本為 0.7.0.17 版本,因此,我們安裝這個版本。
    Xamarin Essentials NuGet Installation
    Xamarin Essentials NuGet Installation
  • 當 Xamarin.Essentials 套件安裝完成之後,您將會發現到在 Visual Studio 2017 的錯誤視窗中,出現了底下錯誤訊息
Warning
偵測到 Xamarin.Android.Support.Compat 的版本衝突。請直接從專案參考套件,以解決此問題。 
 XFESFileSystem.Android -> Xamarin.Essentials 0.7.0.17-preview -> Xamarin.Android.Support.CustomTabs 27.0.2 -> Xamarin.Android.Support.Compat (= 27.0.2) 
 XFESFileSystem.Android -> Xamarin.Android.Support.Design 25.4.0.2 -> Xamarin.Android.Support.Compat (= 25.4.0.2).
  • 現在讓我們來修正這個錯誤,在這裡,我使用了 Visual Studio Code 開啟了 Android 專案下的 .csproj 檔案,請搜尋這個關鍵字 PackageReference Include="Xamarin.Essentials"
    此時,您將會看到底下的內容
XML
  <ItemGroup>
    <PackageReference Include="Xamarin.Essentials">
      <Version>0.7.0.17-preview</Version>
    </PackageReference>
    <PackageReference Include="Xamarin.Forms" Version="2.5.0.122203" />
    <PackageReference Include="Xamarin.Android.Support.Design" Version="25.4.0.2" />
    <PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="25.4.0.2" />
    <PackageReference Include="Xamarin.Android.Support.v4" Version="25.4.0.2" />
    <PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="25.4.0.2" />
    <PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="25.4.0.2" />
    <PackageReference Include="Prism.Unity.Forms" Version="7.0.0.396" />
  </ItemGroup>
  • 我們需要將 Xamarin.Android.Support.xxx 的套件,全部都變更成從 "25.4.0.2" 到 "27.0.2.1"
    修改完成結果如下所示,請進行存檔該 .csproj 檔案,並且回到 Visual Studio 2017 下,此時,將會重新載入這個 Android 專案
XML
  <ItemGroup>
    <PackageReference Include="Xamarin.Essentials">
      <Version>0.7.0.17-preview</Version>
    </PackageReference>
    <PackageReference Include="Xamarin.Forms" Version="2.5.0.122203" />
    <PackageReference Include="Xamarin.Android.Support.Design" Version="27.0.2.1" />
    <PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="27.0.2.1" />
    <PackageReference Include="Xamarin.Android.Support.v4" Version="27.0.2.1" />
    <PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="27.0.2.1" />
    <PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="27.0.2.1" />
    <PackageReference Include="Prism.Unity.Forms" Version="7.0.0.396" />
  </ItemGroup>
  • 現在,我們需要安裝 Xamarin.Android.Support.CustomTabs 這個套件到 Android 專案下,在這個時間點,我們將會安裝最新版本的 "27.0.2.1"
    Xamarin Essentials NuGet Installation
  • 如此,最初產生的錯誤訊息便會消失了。
  • 我們將開始要進行 View ViewModel Model 的設計了,首先,我們開啟 MainPage.xaml 這個檔案,底下是我們測試的頁面 XAML 語法
    這個頁面相當的精簡,有兩個文字輸入盒,用來讓使用者輸入帳號或者密碼,接著,有三個按鈕,可以清空剛剛輸入的內容、將輸入的帳號與密碼儲存到檔案內或者從檔案內讀取出來。在這裡,我們將會要把檔案寫到應用程式的 sandbox 沙箱資料夾內。
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"
             x:Class="XFESFileSystem.Views.MainPage"
             Title="Xamarin.Essentials: 檔案系統的協助程式">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Entry Text="{Binding Account}"
               Placeholder="請輸入帳號"/>
        <Entry Text="{Binding Password}" IsPassword="True"
               Placeholder="請輸入密碼"/>
        <StackLayout
            Orientation="Horizontal">
            <Button Text="清空" Command="{Binding CleanCommand}"/>
            <Button Text="儲存" Command="{Binding SaveCommand}"/>
            <Button Text="讀取" Command="{Binding ReadCommand}"/>
        </StackLayout>
    </StackLayout>

</ContentPage>
  • 請打開 MainPageViewModel.cs 這個檔案
    在這個檔案中,我們定義一個類別 MyUser,其目的是要把讀取或者寫入到檔案的資料,先填入到這個類別的物件內,緊接著透過 Json.NET 套件,將這個物件進行 JSON 的序列化或者反序列化;也就是說,當要寫入到檔案前,我們先取得該物件的 JSON 序列化文字,緊接著把這些字串寫入到檔案內;若要讀取出來的話,則反向操作,先從檔案讀出文字,接著將 JSON 文字反序列化成為 .NET 物件。
    當要讀寫檔案的時候,我們需要指定該應用程式沙箱的目錄位置,我們可以透過 Xamarin.Essentials.FileSystem.AppDataDirectory 屬性來取得各個原生平台的應用程式 sandbox 目錄名稱,接著,使用 System.IO.Path.Combine 建立起該檔案的完整路徑名稱,最後,透過 .NET Standard 中的 System.IO.File 功能,進行檔案的存取。
C#
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XFESFileSystem.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Account { get; set; }
        public string Password { get; set; }
        public DelegateCommand CleanCommand { get; set; }
        public DelegateCommand SaveCommand { get; set; }
        public DelegateCommand ReadCommand { get; set; }
        private readonly INavigationService _navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            CleanCommand = new DelegateCommand(() =>
            {
                Account = "";
                Password = "";
            });
            SaveCommand = new DelegateCommand(() =>
            {
                var foo = new MyUser()
                {
                    Account = Account,
                    Password = Password,
                };
                var fooSerial = Newtonsoft.Json.JsonConvert.SerializeObject(foo);
                var mainDir = Xamarin.Essentials.FileSystem.AppDataDirectory;
                var fileName = System.IO.Path.Combine(mainDir, "MyData.txt");
                System.IO.File.WriteAllText(fileName, fooSerial);
            });
            ReadCommand = new DelegateCommand(() =>
            {
                var mainDir = Xamarin.Essentials.FileSystem.AppDataDirectory;
                var fileName = System.IO.Path.Combine(mainDir, "MyData.txt");
                var fooSerial= System.IO.File.ReadAllText(fileName);
                var foo = Newtonsoft.Json.JsonConvert.DeserializeObject<MyUser>(fooSerial);
                if(foo!=null)
                {
                    Account = foo.Account;
                    Password = foo.Password;
                }
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }

    public class MyUser
    {
        public string Account { get; set; }
        public string Password { get; set; }
    }
}
  • Android 平台執行結果
    Xamarin Essentials
  • UWP 平台執行結果
    Xamarin Essentials NuGet Installation



2018/06/15

Xamarin.Forms 使用 MediaPlugin 進行拍照並且顯示在螢幕上

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式

在這篇文章中,我們將需要使用 Xam.Plugin.Media 這個套件,讓您的 App 可以使用手機鏡頭來進行拍照,並且將這個拍照後的圖片儲存在該應用程式的沙箱 sandbox 資料夾內,之後,我們要使用 ImageSource 這個類別,把這個圖片檔案讀取出來,並且顯示在螢幕上,更多關於 Xam.Plugin.Media NuGet 套件的說明,可以參考 MediaPlugin
本文章的範例專案位於 
https://github.com/vulcanlee/xamarin-forms-sample2018/tree/master/XFMediaPlugin

建立測試專案

  • 首先,我們先使用 Prism Template Pack 擴充功能所提供的專案樣板,建立起一個 Xamarin.Forms 專案,在這裡我們僅選擇 Android / iOS 類型的專案;接著,我們需要把 PropertyChanged.Fody NuGet 套件安裝到 .NET Standard 專案類別庫內,並且安裝 FodyWeavers.xml 檔案。
  • 接下來,我們需要參考 MediaPlugin 文件,從 Setup 段落中提到,我們需要安裝 Xam.Plugin.MediaNuGet 套件到 .NET Standard 類別庫與原生專案內 (Install into your PCL/.NET Standard project and Client projects) 。
    Xam.Plugin.Media
    底下是當您安裝好 Xam.Plugin.Media NuGet 套件之後,會顯示這個套件 readme.txt 檔案,其內容如下:
Media Plugin for Xamarin & Windows

Find the latest at: https://github.com/jamesmontemagno/MediaPlugin

## Additional Required Setup (Please Read!)

## Android 
In  your BaseActivity or MainActivity (for Xamarin.Forms) add this code:


Add to Activity:

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
{
    Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

The `WRITE_EXTERNAL_STORAGE`, `READ_EXTERNAL_STORAGE` permissions are required, but the library will automatically add this for you. Additionally, if your users are running Marshmallow the Plugin will automatically prompt them for runtime permissions.

Additionally, the following has been added for you:
[assembly: UsesFeature("android.hardware.camera", Required = false)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = false)]


You must also add a few additional configuration files to adhere to the new strict mode:

1.) Add the following to your AndroidManifest.xml inside the <application> tags:

<provider android:name="android.support.v4.content.FileProvider" 
                android:authorities="${applicationId}.fileprovider" 
                android:exported="false" 
                android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" 
                android:resource="@xml/file_paths"></meta-data>
</provider>

YOUR_APP_PACKAGE_NAME must be set to your app package name!

2.) Add a new folder called xml into your Resources folder and add a new XML file called `file_paths.xml`

Add the following code:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
    <external-files-path name="my_movies" path="Movies" />
</paths>

YOUR_APP_PACKAGE_NAME must be set to your app package name!

You can read more at: https://developer.android.com/training/camera/photobasics.html

## Android Current Activity Setup

This plugin uses the [Current Activity Plugin](https://github.com/jamesmontemagno/CurrentActivityPlugin/blob/master/README.md) to get access to the current Android Activity. Be sure to complete the full setup if a MainApplication.cs file was not automatically added to your application. Please fully read through the [Current Activity Plugin Documentation](https://github.com/jamesmontemagno/CurrentActivityPlugin/blob/master/README.md). At an absolute minimum you must set the following in your Activity's OnCreate method:

CrossCurrentActivity.Current.Init(this, bundle);

It is highly recommended that you use a custom Application that are outlined in the Current Activity Plugin Documentation](https://github.com/jamesmontemagno/CurrentActivityPlugin/blob/master/README.md)

### iOS

Your app is required to have keys in your Info.plist for `NSCameraUsageDescription` and `NSPhotoLibraryUsageDescription` in order to access the device's camera and photo/video library. If you are using the Video capabilities of the library then you must also add `NSMicrophoneUsageDescription`.  The string that you provide for each of these keys will be displayed to the user when they are prompted to provide permission to access these device features. You can read me here: https://blog.xamarin.com/new-ios-10-privacy-permission-settings/

Such as:
<key>NSCameraUsageDescription</key>
<string>This app needs access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to photos.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs access to microphone.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app needs access to the photo gallery.</string>

### UWP
Set `Webcam` permission.

### Tizen
Please add the following Privileges in tizen-manifest.xml file

http://tizen.org/privilege/appmanager.launch
http://tizen.org/privilege/mediastorage

See below for additional instructions.
https://developer.tizen.org/development/visual-studio-tools-tizen/tools/tizen-manifest-editor#privileges
  • 宣告頁面 XAML 內容如下,在這裡,我們僅有一個 Image 控制項目,並且設定這個圖片的手勢操作,當使用者點選這個圖片的時候,將會執行 ViewModel 內的 TapCommand 命令的委派方法
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XFMediaPlugin.Views.MainPage"
             Title="使用 MediaPlugin 進行拍照">

    <StackLayout BackgroundColor="#FFC000">
        <Image
            Source="{Binding MyImageSource}" Aspect="AspectFit">
            <Image.GestureRecognizers>
                <TapGestureRecognizer Command="{Binding TapCommand}" />
            </Image.GestureRecognizers>
        </Image>
    </StackLayout>

</ContentPage>
  • 現在,我們開始撰寫 ViewModel 的 C# 程式碼
    在 ViewModel 內,我們宣告一個型別為 ImageSource 的 MyImageSource 屬性 Property,這個屬性將會綁定到頁面 View 中的 Image.Source 屬性 Attribute 中。
    關於當使用者點選這個圖片的時候,我們也在這裡定義了 TapCommand 這個 DelegateCommand 物件,並且設定他的委派方法,也就是當使用點選圖片之後,需要執行的方法。
    我們可以透過 Plugin.Media.CrossMedia.Current.TakePhotoAsync 方法,讓我們的 App 切換到拍照模式,一旦拍照完成之後,將會回傳一個 MediaFile 型別物件,我們便可以透過這個物件,取得該圖片的存取 Stream,並透過 Stream 建立 ImageSource 物件,此時,螢幕上就可以看到我們剛剛拍照的圖片了。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XFMediaPlugin.ViewModels
{
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Forms;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ImageSource MyImageSource { get; set; } = ImageSource.FromFile("DefaultImage.jpg");
        public DelegateCommand TapCommand { get; set; }
        private readonly INavigationService _navigationService;
        public readonly IPageDialogService _dialogService;
        public MainPageViewModel(INavigationService navigationService,
            IPageDialogService dialogService)
        {
            _navigationService = navigationService;
            _dialogService = dialogService;

            TapCommand = new DelegateCommand(async () =>
            {
                // 進行 Plugin.Media 套件的初始化動作
                await Plugin.Media.CrossMedia.Current.Initialize();

                // 確認這個裝置是否具有拍照的功能
                if (!Plugin.Media.CrossMedia.Current.IsCameraAvailable || !Plugin.Media.CrossMedia.Current.IsTakePhotoSupported)
                {
                    await _dialogService.DisplayAlertAsync("No Camera", ":( No camera available.", "OK");
                    return;
                }

                // 啟動拍照功能,並且儲存到指定的路徑與檔案中
                var file = await Plugin.Media.CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
                {
                    Directory = "Sample",
                    Name = "Sample.jpg"
                });

                if (file == null)
                    return;

                // 讀取剛剛拍照的檔案內容,轉換成為 ImageSource,如此,就可以顯示到螢幕上了

                MyImageSource = ImageSource.FromStream(() =>
                {
                    var stream = file.GetStream();
                    file.Dispose();
                    return stream;
                });
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}
  • 現在我們可以開始進行測試,請將這個 Android 專案執行起來,在這裡,我們將會在模擬器上來測試,因此,將會看到下圖畫面。
    Xam.Plugin.Media
    不過,當我們點選最上方的圖片,很不幸的,現在這個系統是無法正常運作,並且顯示底下的例外異常錯誤訊息。
Unhandled Exception:

System.ArgumentException: Unable to get file location. This most likely means that the file provider information is not set in your Android Manifest file. Please check documentation on how to set this up in your project.

修正 System.ArgumentException 錯誤

會發生這樣問題,這是因為您對於 MediaPlugin 套件的初始化與相關設定並沒有完成,在這裡,您可以參考 MediaPlugin 文件中的 Important Permission Information 小節,在這裡,我們僅說明 Android 平台的做法,而 iOS 平台的做法,請參考該文件上的名。
  • 權限部分,關於 WRITE_EXTERNAL_STORAGE & READ_EXTERNAL_STORAGE 這兩個權限,該套件會自動幫我們加入,而其他的部分,請打開 Android 專案內的 MainActivity.cs 檔案
  • 請在 OnCreate 方法內的 base.OnCreate(bundle); 敘述的下方,加入該行程式碼
Plugin.CurrentActivity.CrossCurrentActivity.Current.Init(this, bundle);
  • 請在 MainActivity 類別中,加入這個方法定義
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults)
{
    Plugin.Permissions.PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
  • 請使用滑鼠雙擊 Android 專案內的 Properties 節點
Andproid Properties
  • 當 Android 專案的屬性視窗顯示出來之後,請切換到 Android 資訊清單頁次,並且確實記下 Android 專案的套件名稱,我們這個練習專案的套件名稱為 com.companyname.appname
Andproid Properties
  • 現在,我們需要在 AndroidManifest.xml 檔案內加入一些設定,這個時候,請把 Android 專案內的 Properties 節點展開,此時,您將會看到 AndroidManifest.xml 解點,使用滑鼠雙擊,打開這個 xml 檔案
Andproid Properties AndroidManifest.xml
  • 請將底下的 XML 加入進去,不過,請記得要將這個文字
    [您的專案套件名稱]
    替換成為您的 Android 專案套件名稱,在我們這個練習中,也就是
    com.companyname.appname
    <provider android:name="android.support.v4.content.FileProvider"
          android:authorities="[您的專案套件名稱].fileprovider"
          android:exported="false"
          android:grantUriPermissions="true">

      <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                       android:resource="@xml/file_paths"></meta-data>
    </provider>
  • 請展開 Android 專案的 Resources 資料夾,並且滑鼠右擊 Resources 資料夾,選擇 [加入] > [新增資料夾] 選項,新增一個名稱為 xml 的資料夾
  • 滑鼠右擊剛剛建立的 xml 資料夾,選擇 [加入] > [新增項目] > [Visual C#] > [XML 檔]
    請在 新增項目 - XFMediaPlugin.Android 對話窗中的名稱欄位,輸入 file_paths.xml
Visual Studio 2017 加入一個 xml 檔案
  • 請打開剛剛建立的 xml 檔案,填入底下內容
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-files-path name="my_images" path="Pictures" />
  <external-files-path name="my_movies" path="Movies" />
</paths>
  • 使用滑鼠點擊剛剛建立一的 XML 檔案,且在屬性視窗中找到 自訂工具 項目,輸入
    MSBuild:UpdateGeneratedFiles
android.support.FILE_PROVIDER_PATHS
  • 最後,您可以執行這個專案,看看能否使用拍照功能,並且將拍照後的圖片顯示在螢幕上
拍照與顯示圖片 拍照與顯示圖片

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式