XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2016/07/12

Xamarin.Forms 相依性服務注入

相依性服務注入

雖然,在採用 Xamarin.Forms 套件進行跨平台應用程式開發,可以做到 程式碼 與 UI視覺 共享的目的,也就是說,您僅需要開發出一套商業邏輯程式碼與一套UI視覺,就可以在各個平台,Android / iOS / Windows Phone / Windows UWP,上直接執行,Xamarin.Forms 會自動幫您處理定義在 Xamarin.Forms 的 XAML 版面配置與視覺控制項,找出與對應到各原生平台相對應的控制項,讓您開發的應用程式使用原生的方式來運行,進而雖然是同一套 UI視覺,但可能在不同的行動平台下,會稍微長得不太一樣。
雖然如此,Xamarin.Forms 已將為開發者帶來相當大的幫助與好處,但是,Xamarin.Forms 還是不太能夠處理任何不同平台上的一些需求,例如:在這個範例專案中,想要使用將文字可以透過手機來自動說出來、檢查手機的電池使用狀態、檢查手機裝置的方向。要提供這些功能,需要在每個行動系統平台使用不同API來處理,而且,這些功能在 Xamarin.Forms Toolkit 內,也沒有提供。
在此,Xamarin.Forms 允許開發者可以自行定義出每個平台專屬一些行為,而在核心PCL專案內,透過DependencyService 這個靜態類別所提供的 Get 方法,於執行時期找到當時執行行動裝置平台對應的建置程式碼,接著執行這些功能,使得當要存取原生方法的時候,可以使用分享程式碼的方式。

建立標籤式的樣板式頁面方案

  1. 首先,開啟您的 Visual Studio 2015
  2. 接著透過 Visual Studio 2015 功能表,選擇這些項目 檔案 > 新增 > 專案 準備新增一個專案。
  3. 接著,Visual Studio 2015 會顯示 新增專案 對話窗,請在這個對話窗上,進行選擇 Visual C# >Cross-Platform > Blank Xaml App (Xamarin.Forms Portable)
  4. 接著,在最下方的 名稱 文字輸入盒處,輸入 XFDependency 這個名稱,最後使用滑鼠右擊右下方的確定 按鈕。
  5. 當專案建立到一半,若您的開發環境還沒有建置與設定完成 Mac 電腦與 Xamarin Studio for Mac 系統,此時會看到 Xamarin Mac Agent Instructions 對話窗出現,這個對話窗是要提醒您進行與 Mac 電腦連線設定,這是因為,您無法在 Windows 作業系統進行 iOS 相關應用程式的建立與設計工作,而是需要透過 Mac 電腦安裝的 XCode 來協助您完成這些 iOS 應用程式的建立工作。不過,這不影響您繼續開發 Xamarin.Forms 的應用程式,只不過此時,您無法編譯與執行 iOS 的應用程式。
  6. 接著會看到 新的通用Windows專案 對話視窗,此時,您只需要按下 確定 按鈕即可,此時,專案精靈會繼續完成相關平台的專案建立工作。
  7. 最後,整個新的 Xamarin.Forms 專案就建立完成了。

定義平台專屬服務與介面

為了要能夠讓核心PCL的類別可以呼叫平台專屬的行為或功能,您需要先在核心PCL專案內定義出這些介面。

定義介面

要定義出介面功能,需要透過關鍵字 interface

ITextToSpeech

請在核心PCL專案,使用滑鼠右擊 XFDependency,在彈出功能表中,點選 加入 > 類別,當出現 加入新項目 - XFDependency 對話視窗後,點選 Visual C# > 介面,接著在最下方的 名稱 欄位文字輸入盒中,輸入 ITextToSpeech
接著,請將底下 C# 程式碼置換剛剛產生的介面程式碼。
ITextToSpeech 介面僅僅定義了一個方法,Speak,將想要讓行動裝置說出的話的文字,透過 text 參數傳遞過去即可

ITextToSpeech.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XFDependency
{
    public interface ITextToSpeech
    {
        void Speak(string text);
    }
}

IBattery

請在核心PCL專案,使用滑鼠右擊 XFDependency,在彈出功能表中,點選 加入 > 類別,當出現 加入新項目 - XFDependency 對話視窗後,點選 Visual C# > 介面,接著在最下方的 名稱 欄位文字輸入盒中,輸入 IBattery
接著,請將底下 C# 程式碼置換剛剛產生的介面程式碼。
IBattery 這個介面,定義了三個屬性與另外定義了兩個列舉,開發者可以透過這些定義取得當時行動裝置的電池使用狀態

IBattery.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XFDependency
{
    public enum BatteryStatus
    {
        Charging,
        Discharging,
        Full,
        NotCharging,
        Unknown
    }

    public enum PowerSource
    {
        Battery,
        Ac,
        Usb,
        Wireless,
        Other
    }

    public interface IBattery
    {
        int RemainingChargePercent { get; }
        BatteryStatus Status { get; }
        PowerSource PowerSource { get; }
    }
}

IDeviceOrientation

請在核心PCL專案,使用滑鼠右擊 XFDependency,在彈出功能表中,點選 加入 > 類別,當出現 加入新項目 - XFDependency 對話視窗後,點選 Visual C# > 介面,接著在最下方的 名稱 欄位文字輸入盒中,輸入 IDeviceOrientation
接著,請將底下 C# 程式碼置換剛剛產生的介面程式碼。
IDeviceOrientation 這個介面將會使用方法 GetOrientation 來取得行動裝置的螢幕轉向。

IDeviceOrientation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace XFDependency
{
    public enum DeviceOrientations
    {
        Undefined,
        Landscape,
        Portrait
    }

    public interface IDeviceOrientation
    {
        DeviceOrientations GetOrientation();
    }
}

Android 平台相依服務定義

您需要在 Android 平台內實作出這些介面。

TextToSpeechImplementation

  • 在 DependencyServiceSample.Droid 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.Droid 對話窗,點選 Visual C# > Class,然後在最下方的名稱欄位旁,輸入 TextToSpeechImplementation
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: [assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))] 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

TextToSpeechImplementation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using XFDependency.Droid;
using Xamarin.Forms;
using Android.Speech.Tts;

[assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))]
namespace XFDependency.Droid
{
    public class TextToSpeechImplementation : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener
    {
        TextToSpeech speaker;
        string toSpeak;

        public TextToSpeechImplementation() { }

        public void Speak(string text)
        {
            var ctx = Forms.Context; // useful for many Android SDK features
            toSpeak = text;
            if (speaker == null)
            {
                speaker = new TextToSpeech(ctx, this);
            }
            else
            {
                var p = new Dictionary<string, string>();
                //speaker.SetLanguage(Java.Util.Locale.China);

                speaker.Speak(toSpeak, QueueMode.Flush, p);
            }
        }

        #region IOnInitListener implementation
        public void OnInit(OperationResult status)
        {
            if (status.Equals(OperationResult.Success))
            {
                var p = new Dictionary<string, string>();
                speaker.Speak(toSpeak, QueueMode.Flush, p);
            }
        }
        #endregion
    }
}

BatteryImplementation

  • 在 DependencyServiceSample.Droid 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.Droid 對話窗,點選 Visual C# > Class,然後在最下方的名稱欄位旁,輸入 BatteryImplementation
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: [assembly: Xamarin.Forms.Dependency(typeof(BatteryImplementation))] 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

BatteryImplementation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using XFDependency.Droid;

[assembly: Xamarin.Forms.Dependency(typeof(BatteryImplementation))]
namespace XFDependency.Droid
{
    public class BatteryImplementation : IBattery
    {

        public BatteryImplementation()
        {
        }

        public int RemainingChargePercent
        {
            get
            {
                try
                {
                    using (var filter = new IntentFilter(Intent.ActionBatteryChanged))
                    {
                        using (var battery = Application.Context.RegisterReceiver(null, filter))
                        {
                            var level = battery.GetIntExtra(BatteryManager.ExtraLevel, -1);
                            var scale = battery.GetIntExtra(BatteryManager.ExtraScale, -1);

                            return (int)Math.Floor(level * 100D / scale);
                        }
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine("Ensure you have android.permission.BATTERY_STATS");
                    throw;
                }
            }
        }

        public BatteryStatus Status
        {
            get
            {
                try
                {
                    using (var filter = new IntentFilter(Intent.ActionBatteryChanged))
                    {
                        using (var battery = Application.Context.RegisterReceiver(null, filter))
                        {
                            int status = battery.GetIntExtra(BatteryManager.ExtraStatus, -1);
                            var isCharging = status == (int)BatteryStatus.Charging || status == (int)BatteryStatus.Full;

                            var chargePlug = battery.GetIntExtra(BatteryManager.ExtraPlugged, -1);
                            var usbCharge = chargePlug == (int)BatteryPlugged.Usb;
                            var acCharge = chargePlug == (int)BatteryPlugged.Ac;
                            bool wirelessCharge = false;
                            wirelessCharge = chargePlug == (int)BatteryPlugged.Wireless;

                            isCharging = (usbCharge || acCharge || wirelessCharge);
                            if (isCharging)
                                return BatteryStatus.Charging;

                            switch (status)
                            {
                                case (int)BatteryStatus.Charging:
                                    return BatteryStatus.Charging;
                                case (int)BatteryStatus.Discharging:
                                    return BatteryStatus.Discharging;
                                case (int)BatteryStatus.Full:
                                    return BatteryStatus.Full;
                                case (int)BatteryStatus.NotCharging:
                                    return BatteryStatus.NotCharging;
                                default:
                                    return BatteryStatus.Unknown;
                            }
                        }
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine("Ensure you have android.permission.BATTERY_STATS");
                    throw;
                }
            }
        }

        public PowerSource PowerSource
        {
            get
            {
                try
                {
                    using (var filter = new IntentFilter(Intent.ActionBatteryChanged))
                    {
                        using (var battery = Application.Context.RegisterReceiver(null, filter))
                        {
                            int status = battery.GetIntExtra(BatteryManager.ExtraStatus, -1);
                            var isCharging = status == (int)BatteryStatus.Charging || status == (int)BatteryStatus.Full;

                            var chargePlug = battery.GetIntExtra(BatteryManager.ExtraPlugged, -1);
                            var usbCharge = chargePlug == (int)BatteryPlugged.Usb;
                            var acCharge = chargePlug == (int)BatteryPlugged.Ac;

                            bool wirelessCharge = false;
                            wirelessCharge = chargePlug == (int)BatteryPlugged.Wireless;

                            isCharging = (usbCharge || acCharge || wirelessCharge);

                            if (!isCharging)
                                return PowerSource.Battery;
                            else if (usbCharge)
                                return PowerSource.Usb;
                            else if (acCharge)
                                return PowerSource.Ac;
                            else if (wirelessCharge)
                                return PowerSource.Wireless;
                            else
                                return PowerSource.Other;
                        }
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine("Ensure you have android.permission.BATTERY_STATS");
                    throw;
                }
            }
        }
    }
}

DeviceOrientationImplementation

  • 在 DependencyServiceSample.Droid 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.Droid 對話窗,點選 Visual C# > Class,然後在最下方的名稱欄位旁,輸入 DeviceOrientationImplementation
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: [assembly: Xamarin.Forms.Dependency(typeof(DeviceOrientationImplementation))] 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

DeviceOrientationImplementation.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using XFDependency.Droid;

[assembly: Xamarin.Forms.Dependency(typeof(DeviceOrientationImplementation))]
namespace XFDependency.Droid
{
    public class DeviceOrientationImplementation : IDeviceOrientation
    {
        public DeviceOrientationImplementation() { }

        public static void Init()
        {
        }

        public DeviceOrientations GetOrientation()
        {
            IWindowManager windowManager = Android.App.Application.Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();

            var rotation = windowManager.DefaultDisplay.Rotation;
            bool isLandscape = rotation == SurfaceOrientation.Rotation90 || rotation == SurfaceOrientation.Rotation270;
            return isLandscape ? DeviceOrientations.Landscape : DeviceOrientations.Portrait;
        }
    }
}

iOS 平台相依服務定義

您需要在 iOS 平台內實作出這些介面。

TextToSpeechImplementation

  • 在 DependencyServiceSample.iOS 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.iOS 對話窗,點選 Apple > Code > 類別,然後在最下方的名稱欄位旁,輸入 TextToSpeech_iOS
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: [assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))] 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

TextToSpeech_iOS.cs

using AVFoundation;
using System;
using System.Collections.Generic;
using System.Text;
using XFDependency.iOS;

[assembly: Xamarin.Forms.Dependency(typeof(TextToSpeechImplementation))]
namespace XFDependency.iOS
{
    public class TextToSpeechImplementation : ITextToSpeech
    {
        public TextToSpeechImplementation() { }

        public void Speak(string text)
        {
            var speechSynthesizer = new AVSpeechSynthesizer();
            var speechUtterance = new AVSpeechUtterance(text)
            {
                Rate = AVSpeechUtterance.MaximumSpeechRate / 4,
                Voice = AVSpeechSynthesisVoice.FromLanguage("en-US"),
                Volume = 0.5f,
                PitchMultiplier = 1.0f
            };

            speechSynthesizer.SpeakUtterance(speechUtterance);
        }
    }
}

BatteryImplementation

  • 在 DependencyServiceSample.iOS 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.iOS 對話窗,點選 Apple > Code > 類別,然後在最下方的名稱欄位旁,輸入 BatteryImplementation
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: [assembly: Xamarin.Forms.Dependency(typeof(BatteryImplementation))] 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

BatteryImplementation.cs

using Foundation;
using System;
using System.Collections.Generic;
using System.Text;
using UIKit;
using XFDependency.iOS;

[assembly: Xamarin.Forms.Dependency(typeof(BatteryImplementation))]
namespace XFDependency.iOS
{
    public class BatteryImplementation : IBattery
    {
        NSObject batteryLevel, batteryState;
        public BatteryImplementation()
        {
            UIDevice.CurrentDevice.BatteryMonitoringEnabled = true;
        }

        public int RemainingChargePercent
        {
            get
            {
                return (int)(UIDevice.CurrentDevice.BatteryLevel * 100F);
            }
        }

        public BatteryStatus Status
        {
            get
            {
                switch (UIDevice.CurrentDevice.BatteryState)
                {
                    case UIDeviceBatteryState.Charging:
                        return BatteryStatus.Charging;
                    case UIDeviceBatteryState.Full:
                        return BatteryStatus.Full;
                    case UIDeviceBatteryState.Unplugged:
                        return BatteryStatus.Discharging;
                    default:
                        return BatteryStatus.Unknown;
                }
            }
        }

        public PowerSource PowerSource
        {
            get
            {
                switch (UIDevice.CurrentDevice.BatteryState)
                {
                    case UIDeviceBatteryState.Charging:
                        return PowerSource.Ac;
                    case UIDeviceBatteryState.Full:
                        return PowerSource.Ac;
                    case UIDeviceBatteryState.Unplugged:
                        return PowerSource.Battery;
                    default:
                        return PowerSource.Other;
                }
            }
        }
    }
}

DeviceOrientationImplementation

  • 在 DependencyServiceSample.iOS 專案節點上,使用滑鼠右鍵點選此節點,接著選擇 加入 > 類別
  • 當出現 加入項目 - XFDependency.iOS 對話窗,點選 Apple > Code > 類別,然後在最下方的名稱欄位旁,輸入 DeviceOrientationImplementation
  • 請將底下列表的 C# 程式碼這換到剛剛產生的檔案內容。
  • 在 namespace 關鍵字前一行的程式碼: `` 這是C#的屬性(Attribute),用來指明這個型別提供了介面之具體實作。

DeviceOrientationImplementation.cs

using System;
using System.Collections.Generic;
using System.Text;
using UIKit;
using XFDependency.iOS;

[assembly: Xamarin.Forms.Dependency(typeof(DeviceOrientationImplementation))]
namespace XFDependency.iOS
{
    public class DeviceOrientationImplementation : IDeviceOrientation
    {
        public DeviceOrientationImplementation() { }

        public DeviceOrientations GetOrientation()
        {
            var currentOrientation = UIApplication.SharedApplication.StatusBarOrientation;
            bool isPortrait = currentOrientation == UIInterfaceOrientation.Portrait
                || currentOrientation == UIInterfaceOrientation.PortraitUpsideDown;

            return isPortrait ? DeviceOrientations.Portrait : DeviceOrientations.Landscape;
        }
    }
}

開始進行 核心PCL 頁面設計

App.xaml.cs

在 App 類別的建構式內,使用底下程式碼置換。
在 App 建構式內,首先建立一個 TabbedPage 頁面,接著,新增四個子標籤頁面,這四個子標籤頁面都是ContentPage 類型,且每個子標籤頁面,都有設定專屬的圖示圖片,這將會顯示在應用程式頁面上。

App.xaml.cs

        public App()
        {
            InitializeComponent();

            //MainPage = new XFDependency.MainPage();

            #region 產生 MainPage 內容
            var stack = new StackLayout();
            MainPage = new ContentPage
            {
                Content = stack
            };


            var speak = new Button
            {
                Text = "Hello, Forms !",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };
            speak.Clicked += (sender, e) => {
                DependencyService.Get<ITextToSpeech>().Speak("你好 Hello from Xamarin Forms");
            };
            stack.Children.Add(speak);

            var button = new Button
            {
                Text = "Click for battery info",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };
            button.Clicked += (sender, e) => {
                var bat = DependencyService.Get<IBattery>();

                switch (bat.PowerSource)
                {
                    case PowerSource.Battery:
                        button.Text = "Battery - ";
                        break;
                    case PowerSource.Ac:
                        button.Text = "AC - ";
                        break;
                    case PowerSource.Usb:
                        button.Text = "USB - ";
                        break;
                    case PowerSource.Wireless:
                        button.Text = "Wireless - ";
                        break;
                    case PowerSource.Other:
                    default:
                        button.Text = "Unknown - ";
                        break;
                }
                switch (bat.Status)
                {
                    case BatteryStatus.Charging:
                        button.Text += "Charging";
                        break;
                    case BatteryStatus.Discharging:
                        button.Text += "Discharging";
                        break;
                    case BatteryStatus.NotCharging:
                        button.Text += "Not Charging";
                        break;
                    case BatteryStatus.Full:
                        button.Text += "Full";
                        break;
                    case BatteryStatus.Unknown:
                    default:
                        button.Text += "Unknown";
                        break;
                }
            };
            stack.Children.Add(button);
            var orient = new Button
            {
                Text = "Get Orientation",
                VerticalOptions = LayoutOptions.CenterAndExpand,
                HorizontalOptions = LayoutOptions.CenterAndExpand,
            };
            orient.Clicked += (sender, e) => {
                var orientation = DependencyService.Get<IDeviceOrientation>().GetOrientation();
                switch (orientation)
                {
                    case DeviceOrientations.Undefined:
                        orient.Text = "Undefined";
                        break;
                    case DeviceOrientations.Landscape:
                        orient.Text = "Landscape";
                        break;
                    case DeviceOrientations.Portrait:
                        orient.Text = "Portrait";
                        break;
                }
            };
            stack.Children.Add(orient);
            // The root page of your application
            #endregion
        }

實際執行畫面

請再度重新執行,這個時候應用程式可以順利正常運行,螢幕截圖如下:
Android_XFDependency_1

佈署注意事項

iOS 執行結果

請在方案總管內,滑鼠右擊 XFDependency.iOS 專案,選擇 設定為起始專案,接著按下 F5 開始執行。
iOS_XFDependency_1

佈署注意事項

參考

沒有留言:

張貼留言