XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2019/04/22

Xamarin.Forms 之 XAML 設計預覽的設計時期資料

Xamarin.Forms 之 XAML 設計預覽的設計時期資料

在前一篇文章 Xamarin.Forms 之 XAML 設計預覽 有說明,如何在 Visual Studio 2019 下,使用頁面預覽的功能,以便在進行 XAML 語言設計過程中,可以即時看到這些設計後的執行結果。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
現在遇到一個問題,那就是通常在進行 XAML 設計的時候,都會使用 資料綁定 Data Binding 手法,與該頁面的 ViewModel 進行綁定再一起,可是,當使用這樣的設計方式的時候,又想要使用設計時期預覽功能,就會發現到有許多內容,還是需要在執行時期的時候,才能夠看到該頁面的執行結果。
為了解決這樣的問題,需要使用所謂的 [設計時期資料] 這樣的機制,需要使用 Xamarin.Forms 提供的新功能,首先,需要在 ContentPage 內加入底下的命名空間宣告,有了這些宣告,才能夠在 XAML 中使用設計時期資料的功能。
xaml
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
現在,可以將 MainPage.xaml 的內容修正成為如下所示,其中對於 Label Text="{Binding Message}" />這個 XAML 標記,由於使用資料綁定來宣告,因此,這樣的用法只能夠在該專案執行的時候,才能夠看到該文字標籤顯示的內容;若想要在設計時期指定該 Label 這個文字標籤的 Text 屬性值,則需要使用 d: 這個命名空間來指定設計時期的屬性名稱,指定該設計時期的屬性值內容,如此,才能夠在設計階段在預覽畫面上看到這些內容,在此,要使用 <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/> 這樣的宣告語法。從下圖將會看到該 XAML 所指定的設計時期的資料值顯示情況。
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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="{Binding Message}" />
        <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/>
        <BoxView Color="Blue" d:Color="Red"/>
    </StackLayout>

</ContentPage>
可是,這個頁面在執行時期,將會顯成為這樣的情況,從這兩個畫面,可以看到當初指定在設計時期的 XAML 屬性值,是沒有影響到執行時期的 XAML 屬性值。
然而,對於像是 ListView 這樣的檢視,需要指定一個集合紀錄物件給 ItemsSource 這個使用,當然,最為方便的方式還是使用 ViewModel 來建立這樣的物件,並且透過資料綁定的方式指定給 ListView。
首先,在 ContentPage 內加入一個 XAML 命名空間,該命名空間將會指向該頁面的 ViewModel,在此,將會使用 xmlns:ViewModel="clr-namespace:BlankApp5.ViewModels" 這樣的宣告語法。接著,可以在該頁面內,使用 d: 命名空間,指定設計時期的該頁面之 BindingContext 屬性值,指向為 MainPageViewModel。
xaml
    <d:ContentPage.BindingContext>
        <ViewModel:MainPageViewModel/>
    </d:ContentPage.BindingContext>
不過,這樣又產生一個問題,那就是原有的 ViewModel 類別中,沒有預設建構式存在,而會得到 : XLS0507 類型 'MainPageViewModel' 無法用做為物件元素,因為它並非公用,或是未定義公用的無參數建構函式或類型轉換子 這樣的錯誤訊息。
因此,需要在 MainPageViewModel 類別中加入一個預設建構式,並且把 XAML 頁面修改成為底下的宣告,如此,就可以在設計時期,使用 Visual Studio 2019 的頁面預覽功能,看到 ListView 的設計時期呈現的樣貌了。
底下是執行螢幕截圖
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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             xmlns:ViewModel="clr-namespace:BlankApp5.ViewModels"
             x:Class="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <d:ContentPage.BindingContext>
        <ViewModel:MainPageViewModel/>
    </d:ContentPage.BindingContext>

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="{Binding Message}" />
        <Label Text="{Binding Message}" d:Text="這是設計時期指定資料"/>
        <BoxView Color="Blue" d:Color="Red"/>
        <ListView
            ItemsSource="{Binding myItemList}"
            >
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Label Text="{Binding Name}"
                               FontSize="20"/>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>

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

namespace BlankApp5.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    public class MyModel : INotifyPropertyChanged
    {
        public string Name { get; set; }

        public event PropertyChangedEventHandler PropertyChanged;
    }
    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public string Message { get; set; }
        public ObservableCollection<MyModel> myItemList { get; set; } = new ObservableCollection<MyModel>();
        private readonly INavigationService navigationService;

        public MainPageViewModel()
        {
            Message = "Come From Default Constuctor  !!";
            myItemList.Add(new MyModel() { Name = "張三" });
            myItemList.Add(new MyModel() { Name = "李四" });
            myItemList.Add(new MyModel() { Name = "王五" });
        }

        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;
            Message = "Come From Injection Constructor";
            myItemList.Add(new MyModel() { Name = "張三A" });
            myItemList.Add(new MyModel() { Name = "李四B" });
            myItemList.Add(new MyModel() { Name = "王五C" });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}



2019/04/18

Xamarin.Forms 之 XAML 設計預覽

當 Visual Studio 2019 推出之後,我個人覺得最為振奮的一個新功能就是,Xamarin.Froms 的頁面預覽功能,也就是說,當在進行 Xamarin.Forms 的頁面內容設計的時候,只要在 XAML 上進行各種修正或者增刪動作的時候,可以即時看到這個頁面的實際執行時期的效果。

了解更多關於 [Xamarin.Android] 的使用方式
了解更多關於 [Xamarin.iOS] 的使用方式
了解更多關於 [Xamarin.Forms] 的使用方式
了解更多關於 [Hello, Android:快速入門] 的使用方式
了解更多關於 [Hello, iOS – 快速入門] 的使用方式
了解更多關於 [Xamarin.Forms 快速入門] 的使用方式
現在,使用 Prism Template Pack 專案樣板來建立一個專案(在這個時間點,所使用的 Prism Template Pack 擴充功能版本為 2.1.6 ),並且打開 MainPage.xaml 這個檔案,在 MainPage.xaml 視窗的右上方,可以參考下圖,會有兩個按鈕圖示,可以用於切換該頁面要顯示 XAML 設計語言或者是預覽實際執行結果。
因此,想要進行預覽頁面的時候,請點選 [設計] 圖示,而想要查看這個頁面的 XAML 語言的時候,可以點選 [來源] 圖示;不過,當在進行設計頁面的時候,需要這樣反覆的切換,似乎也顯得不太方便,因此,個人通常會使用該視窗右下角的 [垂直分割] 這個功能,將這個 XAML 語言與預覽部分,同時顯示在螢幕上。
可是,現在看到在預覽子視窗中看到底下的錯誤訊息,並且是無法預覽到這個頁面的執行結果畫面。
Xamarin.Forms 預覽程式已經更新,現在需要 Xamarin.Forms 3.6 或更新版本。請將您的專案升級成最新版的 Xamarin.Forms 來啟用預覽程式。
因此,使用滑鼠右擊 Xamarin.Forms 的 [方案],選擇 [管理方案的 NuGet 套件],接著點選 [更新] 這個標籤頁次
勾選 [選取所有封裝] 這個檢查盒,接著,點選 [更新] 按鈕,將所有的套件更新到最新版本
下圖為此次更新的所有相關套件清單,直接點選 [確定] 按鈕,完成更新動作
完成後,將 [MainPage.xaml] 視窗關閉起來,並且重新開啟這個檔案,如此,就會看到該頁面的預覽效果了
現在,請在這個 MainPage.xaml 頁面上,填入底下的 XAML 語言,此時,就會看到在螢幕右方的 XAML Previewer 預覽器畫面,即時呈現出這個剛剛加入的 XAML 項目內容。
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="BlankApp5.Views.MainPage"
             Title="{Binding Title}">

    <StackLayout HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand">
        <Label Text="Welcome to Xamarin Forms and Prism!" />
        <Label Text="XAML Preview" />
        <Button Text="確定"/>
        <BoxView Color="Red"/>
        <ProgressBar Progress="70"/>
    </StackLayout>

</ContentPage>



2019/04/17

自動轉換生成 iOS 與 Android 使用的各種不同尺寸之應用程式 Icon

自動轉換生成 iOS 與 Android 使用的各種不同尺寸之應用程式 Icon

當使用 Prism Template Pack 專案樣板產生一個 Xamarin.Forms 專案後,並且開發完成相關需求程式碼,此時,將會發現到,不論 iOS 或者 Android 平台下的該應用程式圖示,並不是程式設計師所想要到,那麼,要怎麼進行設定與變更了。

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

不論在 iOS 或者 Android 平台下,對於應用程式圖示 Application Icon,需要提供不同縮放比例大小的圖片檔案,而且,在不同平台下,對於這些圖片資源檔案的命名規則也都不盡相同,在 iOS 平台下,每個圖片都會放置在 Resource 的目錄下,並且每個圖片都可以有三種尺寸,正常大小、放大兩倍與放大三倍,不同放大倍率的圖片,則需要該圖片檔案的檔名(在 .副檔案名稱前)後,加入 @1x, @2x, @3x,另外,對於正常大小的 @1x 這個文字,是可以省略的。
而對於 Android 平台下,不同放大倍率的圖片,其圖片檔案名稱都是相同的,不過,不同放大倍率的圖片,需要放置到 Resources 目錄下的不同目錄下,在 Android 平台下,共有這些放大倍率可以選擇:
附加名稱DPI放大倍率
mdpi160
hdpi2401.5×
xhdpi320
xxhdpi480
xxxhdpi640

對於應用程式圖示 Application Icon ,需要在不同放大倍率代表附加名稱前面,要加上 mipmap- 這樣的文字,例如:mipmap-mdpi,對於一般的圖片資源,需要在不同放大倍率的代表附加名稱前面,要加上 drawable 這樣的文字,例如: drawable-xhdpi。
因此,當視覺設計師完成該應用程式的圖片檔案設計之後,需要請視覺設計師產出這些不同平台需要用到的放大倍率圖片,以及根據該行動平台的規範,將不同倍率的圖片,設定為適當的檔案名稱,或者要放大適當的目錄下。
為了要解決這些問題,所以設計了 AppIconBuilder 這個專案,該專案是由兩個類別來組成 iOSImageDefinition / AndroidImageDefinition ,而完整的原始碼可以從從 Github 取得
C Sharp / C#
namespace AppIconBuilder
{
    public class iOSImageDefinition
    {
        public List<iOSImageItem> Images { get; set; }
        public Info info { get; set; }
        public void CalculateSize()
        {
            foreach (var item in Images)
            {
                item.CalculateSize();
            }
        }
        public void GenerateIcons(string mainPath)
        {
            if (Directory.Exists(mainPath) == false)
            {
                Directory.CreateDirectory(mainPath);
            }
            foreach (var item in Images)
            {
                using (Image<Rgba32> image = Image.Load("icon.png"))
                {
                    image.Mutate(x => x
                         .Resize((int)item.Width, (int)item.Height));
                    string filename = Path.Combine(mainPath, item.Filename);
                    image.Save(filename); // Automatic encoder selected based on extension.
                }
            }

        }
    }
    public class iOSImageItem
    {
        public string Idiom { get; set; }
        public string Size { get; set; }
        public string Scale { get; set; }
        public string Filename { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }

        public void CalculateSize()
        {
            var fooSize = Convert.ToDouble(Size.Split('x')[0]);
            var fooScale = Convert.ToInt32(Scale.Replace("x", ""));
            Width = fooSize * fooScale;
            Height = fooSize * fooScale;
        }
    }

    public class Info
    {
        public int Version { get; set; }
        public string Author { get; set; }
    }
}
C Sharp / C#
namespace AppIconBuilder
{
    public class AndroidImageDefinition
    {
        public List<AndroidImageItem> Images { get; set; }
        public void Initialization()
        {
            Images = new List<AndroidImageItem>();
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-hdpi",
                Filename = "ic_launcher.png",
                Width = 72,
                Height = 72,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-ldpi",
                Filename = "ic_launcher.png",
                Width = 36,
                Height = 36,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-mdpi",
                Filename = "ic_launcher.png",
                Width = 48,
                Height = 48,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xhdpi",
                Filename = "ic_launcher.png",
                Width = 96,
                Height = 96,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xxhdpi",
                Filename = "ic_launcher.png",
                Width = 144,
                Height = 144,
            });
            Images.Add(new AndroidImageItem()
            {
                Path = "mipmap-xxxhdpi",
                Filename = "ic_launcher.png",
                Width = 192,
                Height = 192,
            });
        }
        public void ChangeRole()
        {
            foreach (var item in Images)
            {
                item.Path = item.Path.Replace("mipmap", "drawable");
                item.Filename = item.Filename.Replace("ic_launcher", "icon");
            }
        }
        public void GenerateIcons(string mainPath)
        {
            foreach (var item in Images)
            {
                string path = Path.Combine(mainPath, item.Path);
                if (Directory.Exists(path) == false)
                {
                    Directory.CreateDirectory(path);
                }
                using (Image<Rgba32> image = Image.Load("icon.png"))
                {
                    image.Mutate(x => x
                         .Resize((int)item.Width, (int)item.Height));
                    string filename = Path.Combine(path, item.Filename);
                    image.Save(filename); // Automatic encoder selected based on extension.
                }
            }

        }
    }
    public class AndroidImageItem
    {
        public string Filename { get; set; }
        public string Path { get; set; }
        public double Width { get; set; }
        public double Height { get; set; }
    }
}
底下為這兩個類別的使用方式
C Sharp / C#
namespace AppIconBuilder
{
    class Program
    {
        // http://iconhandbook.co.uk/reference/chart/
        static async Task Main(string[] args)
        {
            string mainPath = "iOS";
            var fooContents = await File.ReadAllTextAsync("Contents.json");
            iOSImageDefinition imageDefinition = JsonConvert.DeserializeObject<iOSImageDefinition>(fooContents);
            imageDefinition.CalculateSize();
            imageDefinition.GenerateIcons(mainPath);

            mainPath = "Android_Launcher";
            AndroidImageDefinition androidImageDefinition = new AndroidImageDefinition();
            androidImageDefinition.Initialization();
            androidImageDefinition.GenerateIcons(mainPath);

            mainPath = "Android_Resource";
            androidImageDefinition.ChangeRole();
            androidImageDefinition.GenerateIcons(mainPath);

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
}