XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

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 快速入門] 的使用方式

2018/06/12

Xamarin.Forms 圖片資料綁定的處理

相信大家對於如何在 Xamarin.Forms 中進行字串型別的資料綁定都非常的孰悉,今天,我們來練習如何進行圖片的資料綁定操作。我們的模擬情境為:當我們使用 HttpClient 進行非同步網頁讀取之後,便會變更要綁定的圖片內容,顯示出不同的圖片內容,這些圖片內容將會存在於原生專案。

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

首先,我們先建立的 Xamarin.Forms for Prism 的專案,並且完成 PropertyChanged.Fody 的套件安裝與設定工作,接著,要把這兩個圖片檔案
Facebook Facebook
拖拉到各原生專案的圖片資源所在資料夾內,下圖示 Android 原生專案內的 Resource/drawable 資料夾,裡面就有這兩個圖片檔案。
Image Data Binding
在這個範例中,僅有一個頁面,從底下的 XAML 內容中,我們可以很清楚的看到,圖片控制項 Image 的 Source 屬性,是綁定到 ViewModel 中的 ShowImage C# Property 上。另外,還有一個按鈕,當按下這個按鈕之後,就會透過 HttpClient 使用非同步的方式抓取 www.google.com 網頁內容,緊接著就會變更 ShowImage 這個字串屬性內容,當然,我們就可以從螢幕上,看到圖片有所變動了。
<?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="XFImgBinding.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Image Source="{Binding ShowImage}"
               WidthRequest="200" HeightRequest="200"/>
        <Button Text="呼叫 Web API"
                Command="{Binding CallWebCommand}"/>
    </StackLayout>

</ContentPage>
底下為這個頁面的 ViewModel 程式碼,在這裡,我們撰寫了兩個方法,GetWebAndShowImage & GetWebAndShowImageAsync,兩者皆為非同步的方法,不過,後者是可以透過 await 關鍵字來等候 GetWebAndShowImageAsync 這個方法執行完畢後,繼續來執行的方法;而前者,因為使用了 async void GetWebAndShowImage 這樣的函式標示,因此,呼叫這個函式雖然是個非同步的運算,但是,卻是屬於設後不理的運作模式,也就是說,我們無法掌握 GetWebAndShowImage 這個非同步方法何時會執行完畢。
另外,在建構函式內,我們最後呼叫了 GetWebAndShowImage() 這個方法,這樣的做法非常的不恰當,因為,建構函式內的敘述,不應該執行這些非同步的方法,而且這些建構式內的敘述都應該要可以在很短的時間內執行完成,這樣,才不會影響這個 ViewModel 物件建立與取得的時間。若在 Xamarin.Forms 專案內,真的有甚麼工作需要在建構函式內,需要呼叫這些非同步的方法,建議寫在 OnNavigatedTo 事件內。
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using Prism.Events;
using Prism.Navigation;
using Prism.Services;

namespace XFImgBinding.ViewModels
{
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public int ImageStatus { get; set; }
        public string ShowImage { get; set; }
        public DelegateCommand CallWebCommand { get; set; }
        private readonly INavigationService _navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;
            CallWebCommand = new DelegateCommand(async () =>
            {
                await GetWebAndShowImageAsync();
            });

            GetWebAndShowImage();
        }

        public async void GetWebAndShowImage()
        {
            var client = new HttpClient();
            var fooTmp = await client.GetStringAsync("https://www.google.com");
            ShowImage = $"facebook{(ImageStatus++ % 2) + 1}.png";
        }
        public async Task GetWebAndShowImageAsync()
        {
            var client = new HttpClient();
            var fooTmp = await client.GetStringAsync("https://www.google.com");
            ShowImage = $"facebook{(ImageStatus++ % 2) + 1}.png";
        }
        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}
這裡是在 Android & UWP 專案內的執行結果畫面截圖
Image Data Binding Image Data Binding
Image Data Binding Image Data Binding



2018/06/11

Xamarin.Android 專案封存與三個 Android 選項 AOT LLVM Bundle Assembly 測試

當我們想要進行 Xamarin.Android 專案封存 Archive 作業,並且要產生出可以發布佈署的 .apk 檔案,我們可以雙擊 Android 專案的 Properties 節點,進入到 Android 專案的屬性頁面;在這個頁面中,切換到 Android 選項標籤頁次,我們可以看到這三個選項
  • 將組譯碼組合成機器碼
  • 符合 AOT 規範 (實驗性)
  • 使用 LLVM 最佳化編譯器
關於這三個項目的說明,可以參考準備可供發行的應用程式
Xamarin.Android Option
我曾在 2017.06 也寫過一篇類似的文章 Xamarin.Android 各種封存屬性測試 。當時所採用的測試專案為一個工作管理的範例專案,不過,今天,我們將會採用 Prism Template Pack 所提供的專案範本所產生的一個專案,這個專案內將沒有再去撰寫任何 C# / XAML 的程式碼。
我的測試環境為 Visual Studio Enterprise 2017 15.7.2 版本,我將會切到 Release 組態模式下,並且調整這三個選項:將組譯碼組合成機器碼 / 符合 AOT 規範 (實驗性) / 使用 LLVM 最佳化編譯器,最後,使用封存功能,產生出尚未進行程式碼簽名的 .apk 檔案,我們將會來觀察這些不同 Android 選項下所產生的 .apk 檔案,他的檔案大小為何?
Xamarin.Android Option
透過這次的實驗,我發現到現在所使用 Visual Studio 2017 15.7.2 版本,不論使用上面三種選項的哪種組合,所產生出來的 .apk 檔案都已經大幅降低了許多,並且最佳化的處理也都表現得相當優異。

實驗 1 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (無) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 17.7 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,僅有這些檔案
Xamarin.Android apk file

實驗 2 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 29.2 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案,這些檔案內容與大小,皆與沒有設定 AOT 選項所產生的內相同。
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,卻發現到更多的檔案出現,由副檔案名稱為 .so ,我們可以知道這些是 Android 系統下的 Native Code。因此,我們也就知道了當設定 AOT 模式,所產生的 .apk 檔案內,已經有這些 .NET 組件的機器碼 Native Code 產生在裡面了,因為,當實際執行這個 Xamarin.Android 專案的時候,我們就不再需要透過 JIT 來即時轉譯 IL 碼到 Native Code,因為,這些 Native Code 都已經產生好了,所以,按照理論上來說,執行效能應該會更好。
Xamarin.Android apk file

實驗 3 : 將組譯碼組合成機器碼 (無) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (有)

在此模式下產生的 .apk 檔案大小為 : 26.7 MB
在這個 .apk 檔案的 assemblies 目錄下,存在這這些 .NET 組件檔案,這些檔案內容與大小,皆與沒有或者有設定 AOT 選項所產生的內相同。
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,我們發現到這些 .so 檔案數量不變,不過,您可以發現到這些檔案的大小卻明顯的變小了許多,這些檔案變小,都是有助於 LVVM 有啟用的關係。
Xamarin.Android apk file

實驗 4 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (無) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 8.83 MB
在這裡,我們要來檢測 將組譯碼組合成機器碼 選項,根據微軟官方文件上的說明:啟用此選項時,系統會將組件組合成原生共用程式庫。 此選項會保護程式碼的安全。它會將受控組件內嵌於原生二進位檔來予以保護。
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,僅有這些檔案,比起實驗 1,多了一個 libmonodroid_bundle_app.so 檔案,我們可以知道,這個就是被保護起來的 .NET 組件檔案。
Xamarin.Android apk file

實驗 5 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (無)

在此模式下產生的 .apk 檔案大小為 : 20.3 MB
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,多了取多檔案,這些都是 .NET 組件的 Native Code,不過,比起實驗 1,多了一個 libmonodroid_bundle_app.so 檔案,我們可以知道,這個就是被保護起來的 .NET 組件檔案。
Xamarin.Android apk file

實驗 6 : 將組譯碼組合成機器碼 (有) / 符合 AOT 規範 (實驗性) (有) / 使用 LLVM 最佳化編譯器 (有)

在此模式下產生的 .apk 檔案大小為 : 17.8 MB
現在,因為這些 .NET 組件被保護起來了,因此,我們在這個 .apk 檔案內,也就無法發現到 assemblies 目錄存在
Xamarin.Android apk file
在這個 .apk 檔案的 \lib\armeabi-v7a 目錄下,可以看得出來,經過 LLVM 的最佳化處理,這些 .NET 組件所產生的 Native Code,也都變小了許多。
Xamarin.Android apk file



2018/06/10

在 ListView 中,使用 ContextActions 來實作兩個內容動作

當我們在進行 Xamarin.Forms 專案開發的時候,ListView 是個相當重要的控制項,因為,他可以顯示一群資料在螢幕上,當集合資料顯示於 ListView 控制項,使用者可以點選任一紀錄,此時,ListView 控制項可以進行相對應的商業邏輯運作處理。另外,有些時候,我們希望能夠在 ListView 上,使用者可以長按紀錄,這個時候,期望會有一個彈出選項來讓使用者進行選擇,這個時候,我們就需要使用 ViewCell 的 ContextActions 功能。
底下截圖是我們實際執行這個範例的結果,當使用者長按任何一筆 ListView 中的紀錄,這個時候,隨著這個 App 所運行的平台不同 (Android / iOS / UWP),會出現不同的畫面,這裡我們是使用 Android 平台下執行的結果。所以,您會看到下圖右上方,會有兩個選項出現,使用者可以依照當時需求,點選這兩個選項的任何一個。
ListView ContentActions MenuItem
在這個練習範例中,我們有實作出選項一與選項二這兩個功能,他分別會顯示一個對話窗出來。
ListView ContentActions MenuItem ListView ContentActions MenuItem
首先,我們來看看 XAML 的標記語言宣告。
我們在這個 ListView 中,使用了 ViewCell 宣告出每筆紀錄要顯示的內容,現在,我們可以在 ViewCell 控制項內,使用 ViewCell.ContextActions 項目屬性標誌法,來宣告當長按這個紀錄的時候,需要顯示出哪寫選擇項目。從底下的範例中,我們使用 MenuItem 來宣告兩個選項,並且是 CommandParameter 來將使用者點選的紀錄物件,傳遞到 ViewModel 中的 DelegateCommand 中。
<?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="XFContextAction.Views.MainPage"
             Title="ListView的ContextActions使用範例">

    <ListView
        x:Name="MyListView"
        ItemsSource="{Binding MyDatasSource}"
        SelectedItem="{Binding MyDataSelected}"
       >
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ViewCell.ContextActions>
                        <MenuItem
                            IsDestructive="True"
                            Text="選項1" Command="{Binding BindingContext.Option1Command, Source={x:Reference MyListView}}"
                            CommandParameter="{Binding .}"/>
                        <MenuItem
                            Text="選項2" Command="{Binding BindingContext.Option2Command, Source={x:Reference MyListView}}"
                            CommandParameter="{Binding .}"/>
                    </ViewCell.ContextActions>
                    <StackLayout>
                        <Label Text="{Binding Title}"
                           FontSize="30"/>
                    </StackLayout>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

</ContentPage>
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace XFContextAction.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<MyDataItem> MyDatasSource { get; set; } = new ObservableCollection<MyDataItem>();
        public MyDataItem MyDataSelected { get; set; }
        private readonly INavigationService _navigationService;
        public DelegateCommand<MyDataItem> Option1Command { get; set; }
        public DelegateCommand<MyDataItem> Option2Command { get; set; }
        public readonly IPageDialogService _dialogService;
        public MainPageViewModel(INavigationService navigationService, IPageDialogService dialogService)
        {
            _navigationService = navigationService;
            _dialogService = dialogService;

            Option1Command = new DelegateCommand<MyDataItem>(async (x) =>
            {
                await _dialogService.DisplayAlertAsync("你按下了", $"{x.Title} 的第1個 ActionMenu", "OK");
            });

            Option2Command = new DelegateCommand<MyDataItem>(async (x) =>
            {
                await _dialogService.DisplayAlertAsync("你按下了", $"{x.Title} 的第2個 ActionMenu", "OK");
            });
        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {
            for (int i = 0; i < 20; i++)
            {
                MyDatasSource.Add(new MyDataItem()
                {
                    Title = $"主題 ~~{i}~~"
                });
            }
        }

    }

    public class MyDataItem : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Title { get; set; }

    }
}