XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2018/08/28

使用 Visual Studio 工具箱 Toolbox 來設計頁面 XAML 語言

使用 Visual Studio 2017 15.8 工具箱 Toolbox 來設計頁面 XAML 語言


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

在以往我們進行 Xamarin.Forms 專案開發的時候,我們需要使用 C# 來設計這個行動應用程式的商業邏輯運作行為,對於這個行動應用程式,我們將會透過 XAML 宣告標記語言來設計可用於跨平台的頁面之使用者介面內容。不過,要如何使用這項功能呢,您需要升級 VS4W Visual Studio 2017 for Windows 到 Visual Studio 2017 15.8 版本,並且升級到 Xamarin.Forms NuGet 套件到 3.0 以上的版本。
Xamarin.Forms Toolbar
讓我們使用 Prism Template Pack (2.0.9版本) 來建立一個 Xamarin.Forms 專案
Xamarin.Forms Prism Template Pack
當專案建立完成之後,我們打開 MainPage.xaml 檔案,接著,請打開工具箱視窗,若您找不到工具箱視窗,可以點選 Visual Studio 2017 功能表,點選 [檢視] > [工具箱]。不過,您會很失望,雖然,我這裡的 VS2017 已經升級到 15.8 以上版本,可是,還是看不到關於 Xamarin.Form XAML 的工具箱。
Xamarin.Forms MainPage.xaml
檢查一下您的專案使用的 Xamarin.Forms NuGet 套件,結果發現到 Prism Template Pack 專案樣板建立起來的 Xamarin.Forms 專案,使用的是 2.5.0.122203 版本的 NuGet 套件。
Xamarin.Forms Prism Template Pack
因此,我們需要把整個方案中的所有專案都升級到 Xamarin.Forms 3.0 以上版本的 NuGet 套件,不過,若您只是升級 .NET Standard 專案中的 Xamarin.Forms NuGet 套件到最新版本,您將會得到底下錯誤訊息;所以,記得要把所有方案內的專案,都升級 Xamarin.Forms NuGet 套件到最新版本
Xamarin.Forms NuGet 套件
Xamarin.Forms tasks do not match targets. Please ensure that all projects reference the same version of Xamarin.Forms, and if the error persists, please restart the IDE.    XFToolbar.Android
最後,若您完成了上述動作,您可以切換到 MainPage.xaml 視窗中,打開工具箱視窗,您就會看到了 XAML 可以使用到的各個控制項。您可以點選工具箱中 [Controls] / [Layouts] / [Cells] 的任一項目,拖拉到 XAML 適當地方,Visual Studio 就會幫您產生相關 XAML 語法出來。在這裡,我們從 [工具箱] > [Controls] 拖拉 [Entry] 控制項到右邊的 MainPage.xaml 視窗中的 </StackLayout> 項目前,此時,就會自動產生出 Entry 的 XAML 的語言宣告 <Entry Placeholder="" /> 出來
Xamarin.Forms NuGet 套件

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程

2018/08/10

在 Xamarin.Forms 使用 SkiaSharp 來進行 2D 圖形繪製 2 : 使用 MVVM 設計方式,在 ViewModel 內來呼叫 SkiaSharp API

根據微軟官方文件 SkiaSharp 簡介 上的說明,SkiaSharp 提供豐富且功能強大 2D 圖形 API,可用來呈現到 2D 的緩衝區。 您可以使用這些來實作自訂使用者介面項目和可整合到您的應用程式的 2D 圖形。 SkiaSharp 是.NET 繫結至Skia程式庫會繼承此文件庫的強大的功能。這也就是說,您可以建立一個 SKCanvasView 控制項,我們便擁有了一個畫布,使用 SkiaSharp 所提供的相關 API,即可以開發出繪製出各種效果的圖片。
在這篇文章中,我們將會 使用 MVVM 設計方式,在 ViewModel 內來呼叫 SkiaSharp API,而在前一篇文章中,我們都是在頁面的 Code Behind 內撰寫呼叫 SkiaSharp 程式碼。
在這個練習專案頁面中,我們設計了兩個 200x200 的 SKCanvasView 控制項,並且使用了 Prism 所提供的 行為 Behavior 功能中的 EventToCommandBehavior,讓我們指定了這些 SKCanvasView 控制項內的指定事件 PaintSurface 若被觸發的話,將會自動去執行 ViewModel 內的 PaintSurfaceCommand 命令。
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:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             xmlns:behavior="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
             xmlns:local="clr-namespace:XFSkiaMVVM"
             x:Class="XFSkiaMVVM.Views.MainPage"
             Title="{Binding Title}">

    <ContentPage.Resources>
        <ResourceDictionary>
            <local:SKPaintSurfaceEventArgsConverter x:Key="SKPaintSurfaceEventArgsConverter"/>
        </ResourceDictionary>
    </ContentPage.Resources>

    <ScrollView
        >
        <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
            <BoxView
                Color="Black"
                WidthRequest="200" HeightRequest="200"/>
            <skia:SKCanvasView
                BackgroundColor="Black"
                IgnorePixelScaling="True" 
                WidthRequest="200" HeightRequest="200"
                >
                <skia:SKCanvasView.Behaviors>
                    <behavior:EventToCommandBehavior
                        EventName="PaintSurface"
                        Command="{Binding PaintSurfaceCommand}"
                        EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}"
                    />
                </skia:SKCanvasView.Behaviors>
            </skia:SKCanvasView>
            <skia:SKCanvasView
                BackgroundColor="Black"
                IgnorePixelScaling="False" 
                WidthRequest="200" HeightRequest="200"
                >
                <skia:SKCanvasView.Behaviors>
                    <behavior:EventToCommandBehavior
                        EventName="PaintSurface"
                        Command="{Binding PaintSurfaceSelfScaleCommand}"
                        EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}"
                        />
                </skia:SKCanvasView.Behaviors>
            </skia:SKCanvasView>
        </StackLayout>
    </ScrollView>

</ContentPage>
不過,在這裡,您也看到 EventArgsConverter="{StaticResource SKPaintSurfaceEventArgsConverter}" 這個 XAML 敘述,底下將會是這個 數值轉換器 Value Converter 之 SKPaintSurfaceEventArgsConverter 定義程式碼。由於我們需要在 ViewModel 內取得該事件傳入進來的參數,因此,我們建立一個數值轉換器,並且使用 EventArgsConverter 來指定要將事件傳入進來的參數,可以我們在 ViewModel 內來使用。我們可以看到,我們會透過數值轉換器傳入進來的 value 參數值,轉型成為該事件的參數型別,也就是,SKPaintSurfaceEventArgs,並且將這個值回傳回去。
C#
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Xamarin.Forms;

namespace XFSkiaMVVM
{
    class SKPaintSurfaceEventArgsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var skPaintSurfaceEventArgs = value as SKPaintSurfaceEventArgs;
            if (skPaintSurfaceEventArgs == null)
            {
                throw new ArgumentException("Expected value to be of type SKPaintSurfaceEventArgs", nameof(value));
            }
            return skPaintSurfaceEventArgs;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}
那麼,究竟要如何在 ViewModel 內讀取該事件傳入進來的參數值呢?在這裡,當我們要宣告事件2命令的命令時候,要使用泛型的方式來宣告其型別,也就是類似這樣 public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceCommand { get; set; } 在這裡,我們就看到了剛剛的數值轉換器的轉型型別,也就是我們命令中的要指定的泛型型別。接下來,在我們建立這個命令的物件時候,就可以使用 PaintSurfaceCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args => { ... } 這樣敘述來建立,我們使用了 Lambda 匿名函式傳入到這個命令建構式內,而我們在這個匿名函式內,就可以透過 args 這個參數變數,取得原先在 SKCanvasView 控制項 PaintSurface 事件內的事件參數物件。有了 SKPaintSurfaceEventArgs 這個參數物件,我們便可以開始進行針對 SKCanvasView 畫布,呼叫各種 SkiaSharp API,進行繪圖需求工作了。
若您仔細觀察執行結果,您將會發現到第一個 SKCanvasView 控制項所繪製出來的圓形與線條會有鋸齒狀,而且,這裡所指定畫線與畫圓的 API 使用到的尺寸都是 與裝置無關的畫素,也就是沒有根據當時螢幕的縮放比來自行計算出真實畫素實際值,這是因為,我們在 SKCanvasView 控制項內,設定了 IgnorePixelScaling="True" 屬性值,當 IgnorePixelScaling 屬性值為真,我們在 SKCanvasView 控制項內的所指定的尺寸,就可以使用 XAML 中設定的尺寸大小, SkiaSharp 會自動幫我們進行縮放,可是,您看到這樣的效果並不是很好,因為,會有鋸齒狀出現。
所以,我們還是建議類似 PaintSurfaceSelfScaleCommand 命令中的做法,使用 float fooScale = info.Width / 200.0f; canvas.Scale(fooScale); 敘述,呼叫 Scale 方法,指定縮放比率值,這樣就不會有鋸齒狀出現了。
C#
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace XFSkiaMVVM.ViewModels
{

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceCommand { get; set; }
        public DelegateCommand<SKPaintSurfaceEventArgs> PaintSurfaceSelfScaleCommand { get; set; }
        private readonly INavigationService _navigationService;

        public MainPageViewModel(INavigationService navigationService)
        {
            _navigationService = navigationService;

            PaintSurfaceCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args =>
           {
               SKImageInfo info = args.Info;
               SKSurface surface = args.Surface;
               SKCanvas canvas = surface.Canvas;

               canvas.Clear();

               SKPaint fooCirclePaint = new SKPaint
               {
                   Style = SKPaintStyle.Fill,
                   Color = SKColors.DeepSkyBlue,
                   StrokeWidth=1,
               };

               SKPaint fooLinePaint = new SKPaint
               {
                   Style = SKPaintStyle.Stroke,
                   Color = SKColors.White,
                   StrokeWidth = 1,
               };

               canvas.DrawCircle(100, 100, 100, fooCirclePaint);
               canvas.DrawLine(0, 0, 200, 140, fooLinePaint);
           });

            PaintSurfaceSelfScaleCommand = new DelegateCommand<SKPaintSurfaceEventArgs>(args =>
           {
               SKImageInfo info = args.Info;
               SKSurface surface = args.Surface;
               SKCanvas canvas = surface.Canvas;

               canvas.Clear();

               float fooScale = info.Width / 200.0f;

               SKPaint fooCirclePaint = new SKPaint
               {
                   Style = SKPaintStyle.Fill,
                   Color = SKColors.DeepSkyBlue,
                   StrokeWidth = 1,
               };

               SKPaint fooLinePaint = new SKPaint
               {
                   Style = SKPaintStyle.Stroke,
                   Color = SKColors.White,
                   StrokeWidth = 1,
               };

               canvas.Scale(fooScale);
               canvas.DrawCircle(100, 100, 100, fooCirclePaint);
               canvas.DrawLine(0, 0, 200, 140, fooLinePaint);
           });

        }

        public void OnNavigatedFrom(NavigationParameters parameters)
        {

        }

        public void OnNavigatingTo(NavigationParameters parameters)
        {

        }

        public void OnNavigatedTo(NavigationParameters parameters)
        {

        }

    }
}
  • Android 平台執行結果
    SkiaSharp
  • UWP 平台執行結果
    SkiaSharp

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程




2018/08/09

在 Xamarin.Forms 使用 SkiaSharp 來進行 2D 圖形繪製 1

根據微軟官方文件 SkiaSharp 簡介 上的說明,SkiaSharp 提供豐富且功能強大 2D 圖形 API,可用來呈現到 2D 的緩衝區。 您可以使用這些來實作自訂使用者介面項目和可整合到您的應用程式的 2D 圖形。 SkiaSharp 是.NET 繫結至Skia程式庫會繼承此文件庫的強大的功能。這也就是說,您可以建立一個 SKCanvasView 控制項,我們便擁有了一個畫布,使用 SkiaSharp 所提供的相關 API,即可以開發出繪製出各種效果的圖片。
想要能夠使用 SkiaSharp API 各項功能,首先,您需要知道在 SkiaSharp 裡面的座標系統與尺寸單位定義;左上角是這個畫布的原點,越往右邊, X 軸的值越大,越往下面,Y 軸的值越大;另外,在 SKCanvasView 控制項內,若要執行任何繪製圖形的 API,其標示尺寸單位畫素 Pixel。
在我們這個練習範例中,我們在 XAML 中,建立了三個 SKCanvasView 控制項,其指定的寬度與高度分別為 80x200,在 XAML 中所指定的高度與寬度的單位為 與裝置無關的畫素,也就是會依據您當時執行的裝置之螢幕縮放比與當時裝置螢幕的解析度,經過換算之後,所得到的值,在我們針對 Android 專案執行的時候,當時使用的 Android 模擬器的縮放比為 2.65,那麼,也就是說,這個 SKCanvasView 控制項內可以使用的畫素為 80 x 2.65 (W) / 200 x 2.65 (H) = 212 (W) / 530 (H),若您呼叫 SkiaSharp API 進行繪圖的時候,超過這個寬度與高度,您所繪製的圖形將不會顯示在螢幕上。
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:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="XFSkiaSharp.Views.MainPage"
             Title="SkiaSharp 練習">

    <StackLayout
        Orientation="Vertical"
        >
        <Label
            x:Name="label1"/>
        <Label
            x:Name="label2"/>
        <Label
            x:Name="label3"/>
        <StackLayout 
            Orientation="Horizontal"
            HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">

            <BoxView
                x:Name="boxview"
                Color="LightBlue"
                WidthRequest="80" HeightRequest="200"/>

            <skia:SKCanvasView
                x:Name="canvasView"
                WidthRequest="80" HeightRequest="200"
                PaintSurface="OnCanvasViewPaintSurface"/>

            <skia:SKCanvasView
                WidthRequest="80" HeightRequest="200"
                PaintSurface="OnCanvasViewPaintSurfaceScale"/>

            <skia:SKCanvasView
                WidthRequest="80" HeightRequest="200"
                PaintSurface="OnCanvasViewPaintSurfaceScale2"/>
        </StackLayout>
    </StackLayout>

</ContentPage>
想要取得 SKCanvasView 控制項的有效高度與寬度尺寸畫素,可以在該控制項 SKCanvasView 的 OnCanvasViewPaintSurface 事件內,透過該事件的參數 SKPaintSurfaceEventArgs.Canvas 取得當時畫布的可用畫素 Pixel。
我們在這裡了解幾個 SkiaSharp API 用法,首先是把畫布完全清空,在這裡,我們可以呼叫 SKPaintSurfaceEventArgs.Canvas.Clear() 方法即可。若要指定要繪製圖形的畫筆顏色或者填滿背景顏色等等,可以建立一個 SKPaint 物件,指定相關屬性值即可。在這裡,我們會繪製出一個有顏色填滿的矩形,我們可以使用 SKPaintSurfaceEventArgs.Canvas.DrawRect(0, 0, 80, 200, paint); 這個方法,這裡將會指定要在 SKCanvasView 畫布內,繪製出寬度為 80 畫素,高度為 200 畫素的矩形。由上面所提到的,若在螢幕縮放比為 2.65 的裝置上,執行這 DrawRect API ,所繪製出來的矩形,將不會完全佔據整個 SKCanvasView 控制項,而是一小塊,且位於該畫布左上較區域的一個矩形,會造成這樣的情況,那是因為該畫布可用的畫素為 212 (W) / 530 (H)。這裡所提到的執行結果與相關程式碼,可以參考這個 OnCanvasViewPaintSurface 事件內的程式碼。
不過,這樣可以能對於要進行畫布上進行呼叫 SkiaSharp 繪圖 API 時候,產生一些困擾,這裡可以參考 OnCanvasViewPaintSurfaceScale 這個事件內的程式碼,在這裡,我們使用了這個程式碼,指定繪製出一個矩形 float fooScale = 2.65f; SKPaintSurfaceEventArgs.Canvas.DrawRect(0, 0, 80 * fooScale, 200 * fooScale, paint); ;您可以看到,我們把要繪製的 80W x 200H 的矩形,放大了 2.65 倍率,也就是要指定 SkiaSharp 繪製出 212W x 530H ,這樣,就可以繪製出充滿整個 SKCanvasView 控制項的矩形圖形了。
當然,您會覺得這樣的使用方式還是很不方便,我們每次都需要自行來計算要繪製的座標點與尺寸的縮放倍率,此時,您可以參考 OnCanvasViewPaintSurfaceScale2 事件內的用法,在這裡,我們完全都使用 與裝置無關的畫素 作為標示尺寸的單位,在這裡,我們只要在呼叫任何 SkiaSharp API 之前,先執行這樣的程式碼 SKPaintSurfaceEventArgs.Canvas.Scale(2.65f); 這樣,我們一樣呼叫 SKPaintSurfaceEventArgs.Canvas.DrawRect(0, 0, 80, 200, paint); API,這樣所繪製出來的矩形,也是充滿整個畫布的。
C#
using SkiaSharp;
using SkiaSharp.Views.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace XFSkiaSharp.Views
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
        void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
        {
            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            SKPaint paint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = SKColors.LightBlue,
                StrokeWidth = 1,

            };
            canvas.DrawRect(0, 0, 80, 200, paint);

            label1.Text = $"SKCanvasView Independent Pixel= {canvasView.Width} x {canvasView.Height} ";
            label2.Text = $"SKCanvasView Pixel= {canvasView.CanvasSize.Width} x {canvasView.CanvasSize.Height} ";
            label3.Text = $"SKCanvasView Scale = {canvasView.CanvasSize.Width / canvasView.Width} x {canvasView.CanvasSize.Height / canvasView.Height} ";
        }
        void OnCanvasViewPaintSurfaceScale(object sender, SKPaintSurfaceEventArgs args)
        {
            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            float fooScale = 2.65f;
            SKPaint paint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = SKColors.LightBlue,
                StrokeWidth = 1,

            };
            canvas.DrawRect(0, 0, 80 * fooScale, 200 * fooScale, paint);
        }
        void OnCanvasViewPaintSurfaceScale2(object sender, SKPaintSurfaceEventArgs args)
        {
            SKImageInfo info = args.Info;
            SKSurface surface = args.Surface;
            SKCanvas canvas = surface.Canvas;

            canvas.Clear();

            SKPaint paint = new SKPaint
            {
                Style = SKPaintStyle.Fill,
                Color = SKColors.LightBlue,
                StrokeWidth = 1,

            };
            canvas.Scale(2.65f);
            canvas.DrawRect(0, 0, 80, 200, paint);
        }
    }
}
  • Android 平台執行結果
    SkiaSharp
  • UWP 平台執行結果
    SkiaSharp

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
+

Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程