XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

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

2019/04/25

使用 CollectionView 建立一個能以 GridView 呈現的效果

使用 CollectionView 建立一個能以 GridView 呈現的效果

CollectionView 現階段還是在 Preview 階段,當 Xamarin.Forms 4.0 (現階段 Xamarin.Forms 的版本是 3.6) 正式推出的時候,將可以使用這個 CollectionView 檢視。這個檢視除了具有 ListView 的功能之外,它還可以使用 GridView 的模式來顯示出每筆資料,現在,就來實際建立這個專案來體驗設計過程吧。
該文件的專案原始碼可以透過 GitHub 來取得

建立一個 使使用 FlexLayout 專案

  • 開啟 Visual Studio 2019 程式
  • 當 Visual Studio 2019 開始 視窗 出現之後,請點選左下角的 [建立新專案] 選項
  • 當 [建立新專案] 對話窗出現之後,請在中間最上方的搜尋文字輸入盒中輸入 [prism] 關鍵字,搜尋所有與 Prism 有關的專案樣板
  • 請選擇 [Prism Blank App (Xamarin.Forms)] 這個專案樣板
  • 當出現 [設定新的專案] 對話窗,請在 [專案名稱] 輸入 [XF3002]
  • 最後點選該對話窗右下方的 [建立] 按鈕
  • 現在將會看到 [PRISM PROJECT WIZARD] 對話窗,請勾選 ANDROID, iOS 這兩個行動裝置平台,接著在底下 [Container] 下拉選單,選擇 Unity 項目
  • 最後,點選 [CREATE PROJECT] 按鈕,以便產生 Xamarin.Forms 專案

安裝需要用到的 PropertyChanged.Fody NuGet 套件

  • 當這個 Xamarin.Forms 專案建立成功之後,請在該方案中,找到 Xamarin.Forms 使用的專案(這是一個 .NET Standard 類別庫,簡稱為 SCL ),請在該專案中,使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件] 選項
  • 在 [NuGet: XXX] 視窗中,點選 [瀏覽] 標籤頁次,並且在下方的搜尋文字輸入盒中,輸入 [propertychanged.fody] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [PropertyChanged.Fody] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內
  • 請查看 Xamarin.Forms 專案內,並沒有 [FodyWeavers.xml] 這個檔案,因此,使用滑鼠右擊 Xamarin.Forms 專案節點,選擇 [建置] 選項
  • 當建置完成之後,在這個 Xamarin.Forms 專案內將會出現 [FodyWeavers.xml] 檔案

安裝需要用到的 Xamarin.Essentials NuGet 套件

  • 在 [NuGet: XXX] 視窗中,搜尋文字輸入盒中,輸入 [Xamarin.Essentials] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [Xamarin.Essentials] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內

修正因為安裝 Xamarin.Essentials 帶來的錯誤

現在,可以從 Visual Studio 2019 的錯誤視窗中,看到底下的錯誤訊息
錯誤    NU1107    偵測到 Xamarin.Android.Support.Compat 有版本衝突。請將 Xamarin.Android.Support.Compat 28.0.0.1 直接安裝/參考到專案 XF3002.Android 來解決此問題。 
 XF3002.Android -> XF3002 -> Xamarin.Essentials 1.1.0 -> Xamarin.Android.Support.Compat (>= 28.0.0.1) 
 XF3002.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).    XF3002.Android    D:\Vulcan\GitHub\XCourse19\XF3002\XF3002\XF3002.Android\XF3002.Android.csproj    1
想要解決此一問題:
  • 使用滑鼠右擊方案節點(方案總管最上方的那個節點),選擇 [管理方案的 NuGet 套件]
  • 點選 [更新] 標籤頁次
  • 勾選該標籤頁次內的所有項目
  • 點選右上方的更新按鈕,就可以升級這些套件到最新版本了

升級 Xamarin.Forms 套件到 4.0 版本

  • 同樣的,使用滑鼠右擊方案節點(方案總管最上方的那個節點),選擇 [管理方案的 NuGet 套件]
  • 點選 [已安裝] 標籤頁次
  • 點選 [Xamarin.Forms] 套件項目
  • 勾選 [包括搶鮮版] 檢查盒,要求顯示搶鮮版的套件清單
  • 在右方的 Xamarin.Forms 套件清單中,勾選所有的專案
  • 在下方的版本下拉選單中,選擇 4.0 以上的版本,現階段只能夠看到 4.0.0.346134-pre9 這個版本
  • 點選右下方 [安裝] 按鈕,將這個 4.0 的 Xamarin.Forms 套件安裝到所有專案內

建立資料模型

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [Models]
  • 滑鼠右擊剛剛建立的 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [MyData]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
C Sharp / C#
public class MyData : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public string Name { get; set; }
    public int OrderId { get; set; }
}

修正該 Android 專案的 MainActivity.cs

因為現階段 CollectionView 還是在預覽階段,所以,需要在原生專案的進入點內,要執行 Xamarin.Forms.Forms.Init 這個方法前,要增加先執行底下的方法。
  • 打開 [MainActivity.cs] 檔案
  • 將第 18 行,此行沒有任何程式碼,在此加入這個 C# 敘述 Xamarin.Forms.Forms.SetFlags("CollectionView_Experimental");

建立使用 CollectionView 的頁面與商業邏輯

  • 在 [Views] 資料夾內,打開 [MainPage.xaml] 檔案
  • 修正使用底下的 XAML 語言宣告
在這裡的 CollectionView 將會透過 [ItemsSource] 這個可綁定屬性,從 ViewModel 物件內取得要顯示的清單集合資料,並且使用 [SelectionMode] 屬性,設定使用者僅能夠使用單選的方式來選取,最後透過 [SelectionChangedCommand] 這個命令,綁定到 ViewModel 內的 ICommand 屬性,也就是當使用者點選任一項目之後,將會觸發這個命令。
在 CollectionView 內,可以使用 [CollectionView.ItemsLayout] 來設定此次要顯示的資料格式,在這裡將會使用 [GridItemsLayout] 這個屬性來指定使用 GridView 的模式來呈現,並且是以垂直方式來顯示資料。
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="XF3002.Views.MainPage"
             Title="CollectionView 的應用練習">

    <StackLayout
        >
        <CollectionView
            x:Name="cv"
            ItemsSource="{Binding myItemList}"
            SelectionMode="Single"
            SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
            SelectionChangedCommand="{Binding SelectionChangedCommand}"
            >
            <CollectionView.ItemsLayout>
                <GridItemsLayout Orientation="Vertical" Span="2" />
            </CollectionView.ItemsLayout>
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Frame BorderColor="LightGray" CornerRadius="3" HasShadow="False">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="70"/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="150"/>
                            </Grid.ColumnDefinitions>
                            <Label
                                HorizontalOptions="Start"
                                Text="{Binding Name}"
                                FontAttributes="Bold"
                                FontSize="20"/>
                            <Label
                                HorizontalOptions="End" VerticalOptions="End"
                                Text="{Binding OrderId}"
                                TextColor="Red"
                                FontSize="16"/>
                        </Grid>
                    </Frame>
                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>
    </StackLayout>

</ContentPage>
  • 在 [ViewModels] 資料夾內,打開 [MainPageViewModel.xaml] 檔案
  • 修正使用底下的 C# 程式碼
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 XF3002.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using XF3002.Models;

    public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public ObservableCollection<MyData> myItemList { get; set; } = new ObservableCollection<MyData>();
        public MyData SelectedItem { get; set; }
        public int GridItemsLayoutSpan { get; set; } = 2;
        public DelegateCommand SelectionChangedCommand { get; set; }
        private readonly INavigationService navigationService;
        private readonly IPageDialogService dialogService;

        public MainPageViewModel()
        {
            ReadData();
        }
        public MainPageViewModel(INavigationService navigationService, IPageDialogService dialogService)
        {
            this.navigationService = navigationService;
            this.dialogService = dialogService;
            SelectionChangedCommand = new DelegateCommand(() =>
            {
                dialogService.DisplayAlertAsync("Info", SelectedItem.Name, "OK");
            });
        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            ReadData();
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }
        public void ReadData()
        {
            int cc = 1;
            for (int i = 0; i < 10; i++)
            {
                myItemList.Add(new MyData() { Name = "Baboon", OrderId=cc++ });
                myItemList.Add(new MyData() { Name = "Capuchin Monkey", OrderId = cc++ });
                myItemList.Add(new MyData() { Name = "Blue Monkey", OrderId = cc++ });
                myItemList.Add(new MyData() { Name = "Squirrel Monkey", OrderId = cc++ });
                myItemList.Add(new MyData() { Name = "Golden Lion Tamarin", OrderId = cc++ });
                myItemList.Add(new MyData() { Name = "Howler Monkey", OrderId = cc++ });
                myItemList.Add(new MyData() { Name = "Japanese Macaque", OrderId = cc++ });
            }
        }
    }
}

執行結果




2019/04/24

解決要建置 Xamarin.Forms 專案,發生 ResolveLibraryProjectImports 與 LinkAssemblies 和 參考 `Xamarin.Forms.Xaml` 的問題

解決要建置 Xamarin.Forms 專案,發生 ResolveLibraryProjectImports 的問題


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

有時候當建立好一個 Xamarin.Forms 專案,撰寫了一些程式碼之後,準備要進行建置該專案的動作,此時,會得到這樣的錯誤訊息
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        "ResolveLibraryProjectImports" 工作發生未預期的失敗。
System.IO.FileNotFoundException: Could not load assembly 'XF2001, Version=0.0.0.0, Culture=neutral, PublicKeyToken='. Perhaps it doesn't exist in the Mono for Android profile?
檔案名稱: 'XF2001.dll'
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(AssemblyNameReference reference, ReaderParameters parameters)
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(String fullName)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Extract(DirectoryAssemblyResolver res, ICollection`1 jars, ICollection`1 resolvedResourceDirectories, ICollection`1 resolvedAssetDirectories, ICollection`1 resolvedEnvironments)
   於 Xamarin.Android.Tasks.ResolveLibraryProjectImports.Execute()
   於 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   於 Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()    XF2001.Android
或者
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        Failed to generate resource table for split '' "Failed to generate resource table for split ''".    DynamicFlexLayout.Android
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        "LinkAssemblies" 工作發生未預期的失敗。
System.IO.FileNotFoundException: Could not load assembly 'DynamicFlexLayout.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken='. Perhaps it doesn't exist in the Mono for Android profile?
檔案名稱: 'DynamicFlexLayout.Android.dll'
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.Resolve(AssemblyNameReference reference, ReaderParameters parameters)
   於 Java.Interop.Tools.Cecil.DirectoryAssemblyResolver.GetAssembly(String fileName)
   於 Xamarin.Android.Tasks.LinkAssemblies.Execute(DirectoryAssemblyResolver res)
   於 Xamarin.Android.Tasks.LinkAssemblies.Execute()
   於 Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   於 Microsoft.Build.BackEnd.TaskBuilder.<ExecuteInstantiatedTask>d__26.MoveNext()    DynamicFlexLayout.Android
或者
嚴重性    程式碼    說明    專案    檔案    行    隱藏項目狀態
錯誤        Can not resolve reference: `Xamarin.Forms.Xaml`, referenced by `XF3031`. Please add a NuGet package or assembly reference for `Xamarin.Forms.Xaml`, or remove the reference to `XF3031`.    XF3031.Android            
錯誤        No resource found that matches the given name: attr 'colorPrimaryDark'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'windowNoTitle'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'colorPrimary'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'windowActionBar'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.DarkActionBar'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    2    
錯誤        No resource found that matches the given name: attr 'colorAccent'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    3    
錯誤        No resource found that matches the given name: attr 'colorAccent'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4    
錯誤        No resource found that matches the given name: attr 'windowActionModeOverlay'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4    
錯誤        Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light.Dialog'.    XF3031.Android    D:\Vulcan\GitHub\XCourse19\XF3031\Solution\XF3031\XF3031.Android\Resources\values\styles.xml    4
從此錯誤訊息中可以看到 "ResolveLibraryProjectImports" 工作發生未預期的失敗,而且有個 System.IO.FileNotFoundException: Could not load assembly 這樣的例外異常;當看到這樣的錯誤訊息,最快的解決方案就是先清除所有的專案,並且關閉您的 Visual Studio 2019 專案,並且重新開啟該 Xamarin.Forms 方案,重新再建置一次,一切問題都迎刃而解了。



2019/04/23

使用 FlexLayout 配合 BindableLayout 建立一個動態產生與自動配置的效果

使用 FlexLayout 配合 BindableLayout 建立一個動態產生與自動配置的效果

在這裡,將要使用 Visual Studio 2019 建立一個 Xamarin.Forms 的專案,並且使用 FlexLayout 彈性版面配置 這個檢視來做出一個可以動態成長的布局設計;在這裡將會一開始顯示三個區塊,這三個區塊將會顯示在同一個 Row,接下來將會顯示一個文字,該文字將會獨佔一個 Row,在下一個 Row 將會顯示出一個比較大的區塊而且也是獨占一個 Row,最後,將會產生出 31 個區塊,用來模擬可以做到動態的產生出不同數量的區塊檢視,但是一樣可以顯示在螢幕上的效果,而最後的執行效果將會如下圖所示,左下圖為一開始執行的畫面,而右下圖為向上捲動後的結果,此時底下是還有很多的區塊尚未顯示出來的。
 
該文件的專案原始碼可以透過 GitHub 來取得

建立一個 使使用 FlexLayout 專案

  • 開啟 Visual Studio 2019 程式
  • 當 Visual Studio 2019 開始 視窗 出現之後,請點選左下角的 [建立新專案] 選項
  • 當 [建立新專案] 對話窗出現之後,請在中間最上方的搜尋文字輸入盒中輸入 [prism] 關鍵字,搜尋所有與 Prism 有關的專案樣板
  • 請選擇 [Prism Blank App (Xamarin.Forms)] 這個專案樣板
  • 當出現 [設定新的專案] 對話窗,請在 [專案名稱] 輸入 [DynamicFlexLayout]
  • 最後點選該對話窗右下方的 [建立] 按鈕
  • 現在將會看到 [PRISM PROJECT WIZARD] 對話窗,請勾選 ANDROID, iOS, UWP 三個行動裝置平台,接著在底下 [Container] 下拉選單,選擇 Unity 項目
  • 最後,點選 [CREATE PROJECT] 按鈕,以便產生 Xamarin.Forms 專案

安裝需要用到的 PropertyChanged.Fody NuGet 套件

  • 當這個 Xamarin.Forms 專案建立成功之後,請在該方案中,找到 Xamarin.Forms 使用的專案(這是一個 .NET Standard 類別庫,簡稱為 SCL ),請在該專案中,使用滑鼠右擊 [相依性] 節點,選擇 [管理 NuGet 套件] 選項
  • 在 [NuGet: XXX] 視窗中,點選 [瀏覽] 標籤頁次,並且在下方的搜尋文字輸入盒中,輸入 [propertychanged.fody] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [PropertyChanged.Fody] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內
  • 請查看 Xamarin.Forms 專案內,並沒有 [FodyWeavers.xml] 這個檔案,因此,使用滑鼠右擊 Xamarin.Forms 專案節點,選擇 [建置] 選項
  • 當建置完成之後,在這個 Xamarin.Forms 專案內將會出現 [FodyWeavers.xml] 檔案

安裝需要用到的 Xamarin.Essentials NuGet 套件

  • 在 [NuGet: XXX] 視窗中,搜尋文字輸入盒中,輸入 [Xamarin.Essentials] 關鍵字,搜尋出這個 NuGet 套件
  • 當出現 [Xamarin.Essentials] NuGet 套件,請點選該套件,並且點選右方的 [安裝] 按鈕,將這個套件安裝到 Xamarin.Forms 專案內

修正因為安裝 Xamarin.Essentials 帶來的錯誤

現在,可以從 Visual Studio 2019 的錯誤視窗中,看到底下的錯誤訊息
NU1107    偵測到 Xamarin.Android.Support.Compat 有版本衝突。請將 Xamarin.Android.Support.Compat 28.0.0.1 直接安裝/參考到專案 DynamicFlexLayout.Android 來解決此問題。 
 DynamicFlexLayout.Android -> DynamicFlexLayout -> Xamarin.Essentials 1.1.0 -> Xamarin.Android.Support.Compat (>= 28.0.0.1) 
 DynamicFlexLayout.Android -> Xamarin.Android.Support.Design 27.0.2.1 -> Xamarin.Android.Support.Compat (= 27.0.2.1).    DynamicFlexLayout.Android    D:\Vulcan\GitHub\Xamarin2019\DynamicFlexLayout\DynamicFlexLayout\DynamicFlexLayout.Android\DynamicFlexLayout.Android.csproj    1
想要解決此一問題:
  • 使用滑鼠右擊方案節點(方案總管最上方的那個節點),選擇 [管理方案的 NuGet 套件]
  • 點選 [更新] 標籤頁次
  • 勾選該標籤頁次內的所有項目
  • 點選右上方的更新按鈕,就可以升級這些套件到最新版本了

建立資料模型

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [Models]
  • 滑鼠右擊剛剛建立的 [Models] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [ItemBlock]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
C Sharp / C#
public class ItemBlock
{
    public double Width { get; set; }
    public double Height { get; set; }
    public Color Color { get; set; }
    public bool ShowLabel { get; set; }
    public bool ShowBoxView { get; set; } = true;
}

建立支援方法類別

  • 滑鼠右擊 Xamarin.Forms 專案,選擇 [加入] > [新增資料夾]
  • 將新增資料夾的名稱設定為 [Helpers]
  • 滑鼠右擊剛剛建立的 [Helpers] 資料夾,選擇 [加入] > [類別]
  • 在 [新增項目] 對話窗下方的 [名稱] 欄位中,輸入 [ScreenInfo]
  • 點選右下方的 [新增] 按鈕
  • 將底下程式碼填入到這個新建立的類別檔案內
這個類別將會儲存來自於 Xamarin.Essentials:裝置顯示資訊 Device Display Information 所讀取到的相關螢幕資訊,例如:當時螢幕的實際可用寬度與高度的畫素是多少?這個裝置的密度是多少?接著將會經過計算,得知該螢幕的寬度與高度的設計尺寸(單位為 dip) 是多少?而這個裝置當時寬度的設計尺寸將會與 360 dip (這是在設計這個 App 畫面時所使用的設計尺寸)進行計算,得到一個縮放比例,將其值儲存到 DesignScalar 靜態屬性中。最後,將會提供一個靜態方法,該方法會使用剛剛計算出來的設計尺寸所放比例 DesignScalar,計算出現在提供的設計尺寸,是否放大多少還是要縮小多少。
C Sharp / C#
public class ScreenInfo
{
    public static double ScreenPixelWidth { get; set; }
    public static double ScreenPixelHeight { get; set; }
    public static double DesignScreenWidth { get; set; } 
    public static double DesignScreenHeight { get; set; }
    public static double DesignTimeScreenWidth { get; set; } = 360;
    public static double DesignScalar { get; set; }
    public static double Density { get; set; }
    public static double GetNewDesingSize(double value)
    {
        return DesignScalar * value;
    }
}

建立一個啟動頁面

  • 在 SCL 專案中,滑鼠右擊 [Views] 這個節點,選擇 [加入] > [新增項目]
  • 在 [新增項目] 對話窗的左方,分別點選 [已安裝] > [Visual C# 項目] > [Prism] > [Xamarin.Forms]
  • 在 [新增項目] 對話窗的中間,點選 [Prism ContentPage (Xamarin.Forms)] 這個項目
  • 在 [新增項目] 對話窗的最下方的名稱欄位,輸入 [SplashPage]
  • 最後點選 [新增] 按鈕,完成建立這個新頁面 View 與 檢視模型 ViewModel
  • 在 [ViewModels] 資料夾內,找到 [SplashPageViewModel.cs] 節點,並開啟這個節點
  • 使用底下程式碼覆寫這個這個檔案內容
在這個檢視模型類別中,將會透過 OnNavigatedTo 事件,從 Xamarin.Essentials:裝置顯示資訊 Device Display Information 所讀取到的相關螢幕資訊,儲存到 ScreenInfo 靜態屬性內,最後將會 navigationService.NavigateAsync("/NavigationPage/MainPage"); 敘述,導航到 MainPage 這個頁面
C Sharp / C#
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;

namespace DynamicFlexLayout.ViewModels
{
    using System.ComponentModel;
    using DynamicFlexLayout.Helpers;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Essentials;

    public class SplashPageViewModel : INotifyPropertyChanged, INavigationAware
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private readonly INavigationService navigationService;

        public SplashPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;

        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            var mainDisplayInfo = DeviceDisplay.MainDisplayInfo;
            ScreenInfo.Density = mainDisplayInfo.Density;
            ScreenInfo.ScreenPixelWidth = mainDisplayInfo.Width;
            ScreenInfo.ScreenPixelHeight = mainDisplayInfo.Height;
            ScreenInfo.DesignScreenWidth = ScreenInfo.ScreenPixelWidth/ScreenInfo.Density;
            ScreenInfo.DesignScreenHeight = ScreenInfo.ScreenPixelHeight / ScreenInfo.Density;
            ScreenInfo.DesignScalar = ScreenInfo.DesignScreenWidth / ScreenInfo.DesignTimeScreenWidth;
            navigationService.NavigateAsync("/NavigationPage/MainPage");
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}

修正該 Xamarin.Forms 的第一個顯示頁面

  • 打開 [App.xaml.cs] 檔案
  • 將第 26 行修正為 await NavigationService.NavigateAsync("SplashPage");

建立使用 FlexLayout 的頁面與商業邏輯

  • 在 [Views] 資料夾內,打開 [MainPage.xaml] 檔案
  • 修正使用底下的 XAML 語言宣告
在這裡的 FlexLayout 版面配置,將會透過 [BindableLayout.ItemsSource] 這個附加屬性,指定其子項目的來源,而這些子項目將會由該頁面的 檢視模型 ViewModel 來自動產生,因此 [BindableLayout.ItemsSource] 的屬性值將會是透過資料綁定的方式來取得。
而該 FlexLayout 的每個子項目將會使用 [BindableLayout.ItemTemplate] 這個附加屬性來指定,透過 [DataTemplate] 這個項目 Element,將會指定該子項目要顯示的 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="DynamicFlexLayout.Views.MainPage"
             Title="FlexLayout 的動態調適">

    <Grid>
        <ScrollView>
            <Grid
                HorizontalOptions="Center">
                <FlexLayout
                    Wrap="Wrap"
                    Direction="Row"
                    AlignItems="Start"
                    AlignContent="Start"
                    JustifyContent="Start"
                    BindableLayout.ItemsSource="{Binding myItemList}"  
                    >
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Grid
                                WidthRequest="{Binding Width}" HeightRequest="{Binding Height}">
                                <Label
                                    Text="Logo"
                                    FontSize="30"
                                    HorizontalTextAlignment="Center"
                                    WidthRequest="{Binding Width}" HeightRequest="{Binding Height}"
                                    IsVisible="{Binding ShowLabel}"/>
                                <BoxView
                                    Color="{Binding Color}"
                                    WidthRequest="{Binding Width}" HeightRequest="{Binding Height}"
                                    IsVisible="{Binding ShowBoxView}"/>
                            </Grid>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>
                </FlexLayout>
            </Grid>
        </ScrollView>
    </Grid>

</ContentPage>
  • 在 [ViewModels] 資料夾內,打開 [MainPageViewModel.xaml] 檔案
  • 修正使用底下的 C# 程式碼
在 [OnNavigatedTo] 事件中,將會建立 [ItemBlock] 類別物件,並且加入到型別為 ObservableCollection<ItemBlock> 的 [myItemList] 屬性內。
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 DynamicFlexLayout.ViewModels
{
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using DynamicFlexLayout.Helpers;
    using DynamicFlexLayout.Models;
    using Prism.Events;
    using Prism.Navigation;
    using Prism.Services;
    using Xamarin.Forms;

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

        public MainPageViewModel(INavigationService navigationService)
        {
            this.navigationService = navigationService;

        }

        public void OnNavigatedFrom(INavigationParameters parameters)
        {
        }

        public void OnNavigatedTo(INavigationParameters parameters)
        {
            Random rnd = new Random();
            ItemBlock fooItem;
            for (int i = 0; i < 3; i++)
            {
                fooItem = new ItemBlock()
                {
                    Width = ScreenInfo.GetNewDesingSize(100),
                    Height = ScreenInfo.GetNewDesingSize(100),
                    Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                    ShowLabel = false,
                };
                myItemList.Add(fooItem);
            }
            fooItem = new ItemBlock()
            {
                Width = ScreenInfo.GetNewDesingSize(300),
                Height = ScreenInfo.GetNewDesingSize(50),
                Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                ShowLabel = true,
                ShowBoxView = false
            };
            myItemList.Add(fooItem);
            fooItem = new ItemBlock()
            {
                Width = ScreenInfo.GetNewDesingSize(301),
                Height = ScreenInfo.GetNewDesingSize(200),
                Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                ShowLabel = false,
            };
            myItemList.Add(fooItem);
            for (int i = 0; i < 31; i++)
            {
                fooItem = new ItemBlock()
                {
                    Width = ScreenInfo.GetNewDesingSize(100),
                    Height = ScreenInfo.GetNewDesingSize(100),
                    Color = Color.FromRgba(rnd.Next(256), rnd.Next(256), rnd.Next(256), rnd.Next(256)),
                    ShowLabel = false,
                };
                myItemList.Add(fooItem);
            }
        }

        public void OnNavigatingTo(INavigationParameters parameters)
        {
        }

    }
}

執行結果

在這裡,將會開啟兩種模式的 Android 模擬器,在下圖左方的是 xxhdpi (螢幕密度為 2.65)、1080x1920 的裝置,而右下方的是 xhdpi (螢幕密度為 1.84)、720x1280 的裝置。透過上面的演算法之後,不論當時螢幕的設計尺寸是否大於 360 dip(device independent pixels 與裝置無關的畫素) 或者小於 360 dip 的裝置,都可以完美的顯示比例將這個內容顯示在裝置螢幕上;不過,對於不需要做到完美排版的需求,其實是不需要針對顯示縮放比例做這樣的獨特設計的。
 
在下方,將會使用 iOS 模擬器作為執行測試環境,在下圖左方(iPhone 6)的螢幕密度為2、750x1334 的裝置,而右下方(iPhone5s)的是螢幕密度為2、640x1136 的裝置。透過上面的演算法之後,不論左下方螢幕的設計尺寸(為375)是大於 360 dip(device independent pixels 與裝置無關的畫素) 而右下方螢幕的設計尺寸(為320)是小於 360 dip 的裝置,都可以完美的顯示比例將這個內容顯示在裝置螢幕上。
 
在下方(iPhone 8 Plus)的螢幕密度為3、1242x2208 的裝置,此時螢幕的設計尺寸為 414x736,是大於 360 dip(device independent pixels 與裝置無關的畫素) 可以完美的顯示比例將這個內容顯示在裝置螢幕上。