XAML in Xamarin.Forms 基礎篇 電子書

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

Xamarin.Forms 快速入門 電子書

Xamarin.Forms 快速入門 電子書
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 與裝置無關的畫素) 可以完美的顯示比例將這個內容顯示在裝置螢幕上。




沒有留言:

張貼留言