2017/7/4

Xamarin.Forms 教學系列文(十九.貳 - 2)ListView - Template & Grouping



學習目標
  • Template - 自訂顯示資料格式
  • Grouping - 資料群組化顯示

本章節的程式都會用到一支稱作 NamedColor 的類別當作資料來源,NamedColor.All可以撈出 147 種顏色,程式碼附在文章最後

本章節主要在介紹 ListView 如何顯示資料~

ListView 定義了一個 ItemTemplate 屬性 (DataTemplate 類別),而 DataTemplate 使用 Cell 來協助項目的呈現

ListView 內建好幾組 Cell 可以使用:
Object
     BindableObject
        Element
            Cell
                TextCell — 單純顯示文字,可加入第二行備註行
                ImageCell — 繼承自 TextCell,可加入圖檔
                EntryCell — 可放置 Entry
                SwitchCell — 可放置  Switch
                ViewCell — 客製化呈現方式


Cell 不只在 ListView 會用到,在稍等的章節 TableView 也會出現,只是用法不太一樣
在 ListView 內最常用到的應該是 TextCell、ImageCell 和 ViewCell 

先來看 TextCell~

TextCell

定義了六個可被綁定的屬性:
  • Text of type string
  • TextColor of type Color
  • Detail of type string
  • DetailColor of type Color
  • Command of type ICommand
  • CommandParameter of type Object
TextCell 包含了兩個 Label views,可以將之設置成不同的字串和顏色

直接來看 XAML 範例,資料來源為 NamedColor:
*OnPlatform 寫法已更改,請參考此篇文章

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
             x:Class="TextCellListXaml.TextCellListXamlPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="10, 20, 10, 0"
                    Android="10, 0"
                    WinPhone="10, 0" />
    </ContentPage.Padding>
    
    <!--資料來源-->
    <ListView ItemsSource="{x:Static toolkit:NamedColor.All}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextCell Text="{Binding FriendlyName}"
                          Detail="{Binding RgbDisplay, StringFormat='RGB = {0}'}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

執行結果:

一般來說,ListView不會一次創建所有要呈現的資料,為了照顧到效能,只有在滑到那個物件時才會被創建出來。

Cell 大概就似這樣用,注意一下巢狀標籤位置就好,ImageCell 自己玩。

我們來看比較重要的 ViewCell

ViewCell
大部分都是用這個,客製化想要的內容,想放什麼就放什麼

先來看結果,資料來源一樣用 NamedColor:
你看看,比起上一個 TextCell 範例有顏色多了~

XAML 來啦:
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:toolkit= "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit" 
             x:Class="CustomNamedColorList.CustomNamedColorListPage">
    
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="10, 20, 10, 0" 
                    Android="10, 0" 
                    WinPhone="10, 0" />
    </ContentPage.Padding>
    
    <ListView SeparatorVisibility="None" 
              ItemsSource="{x:Static toolkit:NamedColor.All}">
        
        <!--這高度的值,原文書說是用 try error 方式慢慢測出來符合各平台的高度...-->
        <ListView.RowHeight>
            <OnPlatform x:TypeArguments="x:Int32" 
                        iOS="80" 
                        Android="80" 
                        WinPhone="90" />
        </ListView.RowHeight>
        
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <!--注意 ViewCell 內會先包一個 ContentView-->
                    <ContentView Padding="5">
                        
                        <!--裡面想怎麼包就怎麼包-->
                        <Frame OutlineColor="Accent" Padding="10">
                            <StackLayout Orientation="Horizontal">
                                <BoxView x:Name="boxView" 
                                         Color="{Binding Color}" 
                                         WidthRequest="50" 
                                         HeightRequest="50" />
                                <StackLayout>
                                    <Label Text="{Binding FriendlyName}"
                                           FontSize="22" 
                                           VerticalOptions="StartAndExpand" />
                                    <Label Text="{Binding RgbDisplay, StringFormat='RGB = {0}'}" 
                                           FontSize="16" 
                                           VerticalOptions="CenterAndExpand" />
                                </StackLayout>
                            </StackLayout>
                        </Frame>
                    </ContentView>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

關於那 RowHeight,其實 ListView 有提供一個屬性 HasUnevenRows,設成 True 會自動計算 Cell 所需的高度:
<ListView SeparatorVisibility="None"
          ItemsSource="{x:Static toolkit:NamedColor.All}"
          HasUnevenRows="True">
    <ListView.ItemTemplate>
        …
    </ListView.ItemTemplate>
</ListView>
但 極度耗效能,不建議使用,還是自己慢慢 Try 出 RowHeight 就好。


Grouping

一個 ListView 的子項目有可能非常的多,而 ListView 提供了 Grouping Navigation 的功能,讓 找尋項目 更加便利。

Gruoping 的 ItemSource 比較特殊,其形態需為 集合中的集合

以下範例還是以顏色列表為例,

一樣先看結果:


範例以主色調來做分類,NamedColorGroup 繼承自 List<NamedColor>:
    public class NamedColorGroup : List<NamedColor>
    {
        // Instance members.
        private NamedColorGroup(string title, string shortName, Color colorShade)
        {
            this.Title = title;
            this.ShortName = shortName;
            this.ColorShade = colorShade;
        }

        public string Title { private set; get; }
        public string ShortName { private set; get; }
        public Color ColorShade { private set; get; }

        // Static members.
        static NamedColorGroup()
        {
            // 創建第一個集合 groups
            List groups = new List
            {
               new NamedColorGroup("Grays", "Gry", new Color(0.75, 0.75, 0.75)),
               new NamedColorGroup("Reds", "Red", new Color(1, 0.75, 0.75)),
               new NamedColorGroup("Yellows", "Yel", new Color(1, 1, 0.75)),
               new NamedColorGroup("Greens", "Grn", new Color(0.75, 1, 0.75)),
               new NamedColorGroup("Cyans", "Cyn", new Color(0.75, 1, 1)),
               new NamedColorGroup("Blues", "Blu", new Color(0.75, 0.75, 1)),
               new NamedColorGroup("Magentas", "Mag", new Color(1, 0.75, 1))
            };

            // 將顏色加進群組內
            foreach (NamedColor namedColor in NamedColor.All)
            {
                Color color = namedColor.Color;
                int index = 0;
                if (color.Saturation != 0)
                {
                    index = 1 + (int)((12 * color.Hue + 1) / 2) % 6;
                }
                groups[index].Add(namedColor);
            }

            foreach (NamedColorGroup group in groups)
            {
                //重新分配 List 的容量
                group.TrimExcess();
            }

            All = groups;
        }
        public static IList All { private set; get; }
    }

XAML:
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             xmlns:toolkit= "clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit" 
             x:Class="ColorGroupList.ColorGroupListPage">
    
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness" 
                    iOS="10, 20, 10, 0" 
                    Android="10, 0" 
                    WinPhone="10, 0" />
    </ContentPage.Padding>
    
    <!--重點在這,要設置 IsGroupingEnabled="True"-->
    <ListView ItemsSource="{x:Static toolkit:NamedColorGroup.All}" 
              IsGroupingEnabled="True" 
              GroupDisplayBinding="{Binding Title}" 
              GroupShortNameBinding="{Binding ShortName}">
        
        <ListView.RowHeight>
            <OnPlatform x:TypeArguments="x:Int32" 
                        iOS="80" 
                        Android="80" 
                        WinPhone="90" />
        </ListView.RowHeight>
        
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <ContentView Padding="5">
                        <Frame OutlineColor="Accent" 
                               Padding="10">
                            
                            <StackLayout Orientation="Horizontal">
                                <BoxView x:Name="boxView"
                                         Color="{Binding Color}" 
                                         WidthRequest="50" 
                                         HeightRequest="50" />
                                <StackLayout>
                                    <Label Text="{Binding FriendlyName}" 
                                           FontSize="22" 
                                           VerticalOptions="StartAndExpand" />
                                    <Label Text="{Binding RgbDisplay, StringFormat='RGB = {0}'}"
                                           FontSize="16" 
                                           VerticalOptions="CenterAndExpand" />
                                </StackLayout>
                            </StackLayout>
                        </Frame>
                    </ContentView>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

如果不喜歡內建的 Header 怎辦!!??

別擔心,Xamarin.Forms 也提供 Header 客製化功能:

看看那浮誇的 Header...
*OnPlatform 寫法已更改,請參考此篇文章
<ListView ItemsSource="{x:Static toolkit:NamedColorGroup.All}"
          IsGroupingEnabled="True"
          GroupShortNameBinding="{Binding ShortName}">
    …

    <!--客製化 Header-->
    <ListView.GroupHeaderTemplate>
        <DataTemplate>
            <ViewCell>
                <Label Text="{Binding Title}"
                       BackgroundColor="{Binding ColorShade}"
                       TextColor="Black"
                       FontAttributes="Bold,Italic"
                       HorizontalTextAlignment="Center"
                       VerticalTextAlignment="Center">

                    <Label.FontSize>
                        <OnPlatform x:TypeArguments="x:Double"
                                    iOS="30"
                                    Android="30"
                                    WinPhone="45" />
                    </Label.FontSize>
                </Label>
            </ViewCell>
        </DataTemplate>
    </ListView.GroupHeaderTemplate>

</ListView>


這小節就先介紹這樣啦,下回待續... ListView 的互動,既然提到互動就會有 MVVM!!!


差點忘了附上 NamedColor:
    public class NamedColor
    {
        private NamedColor()
        {
        }
        public string Name { private set; get; }
        public string FriendlyName { private set; get; }
        public Color Color { private set; get; }
        public string RgbDisplay { private set; get; }

        // 靜態建構子
        static NamedColor()
        {
            List all = new List();
            StringBuilder stringBuilder = new StringBuilder();

            // 將所有顏色撈出
            foreach (FieldInfo fieldInfo in typeof(NamedColor).GetRuntimeFields())
            {
                if (fieldInfo.IsPublic && fieldInfo.IsStatic && fieldInfo.FieldType == typeof(Color))
                {
                    // 轉換顏色的名字
                    string name = fieldInfo.Name;
                    stringBuilder.Clear();
                    int index = 0;

                    foreach (char ch in name)
                    {
                        if (index != 0 && Char.IsUpper(ch))
                        {
                            stringBuilder.Append(' ');
                        }

                        stringBuilder.Append(ch);
                        index++;
                    }

                    // 初始化 NamedColor 物件
                    Color color = (Color)fieldInfo.GetValue(null);
                    NamedColor namedColor = new NamedColor
                    {
                        Name = name,
                        FriendlyName = stringBuilder.ToString(),
                        Color = color,
                        RgbDisplay = String.Format("{0:X2}-{1:X2}-{2:X2}",
                        (int)(255 * color.R), (int)(255 * color.G), (int)(255 * color.B))
                    };

                    // 加進集合
                    all.Add(namedColor);

                }
            }
            all.TrimExcess();
            All = all;
        }
        public static IList All { private set; get; }

        // Color names and definitions from http://www.w3.org/TR/css3-color/ 
        // (but with color names converted to camel case). 
        public static readonly Color AliceBlue = Color.FromRgb(240, 248, 255);
        public static readonly Color AntiqueWhite = Color.FromRgb(250, 235, 215);
        public static readonly Color Aqua = Color.FromRgb(0, 255, 255);
        … 
        public static readonly Color WhiteSmoke = Color.FromRgb(245, 245, 245);
        public static readonly Color Yellow = Color.FromRgb(255, 255, 0);
        public static readonly Color YellowGreen = Color.FromRgb(154, 205, 50);
    }







11 則留言:

  1. 請問老師,在 iOS 上的手機模擬器,模擬器外觀要如何顯示,try 都是一個長方形而已,有甚麼需要設定嗎?

    回覆刪除
    回覆
    1. 您好,如果您是用 Mac 開發的話我幫不太上忙... 我現在身邊只有邪惡 Windows

      刪除
    2. 老師您好
      我是利用 Visual Stuiio 2017 Commuity 開發的,
      只是在 Mac 上看到的 i OS模擬器都沒有手機外觀

      刪除
    3. 抱歉,手邊沒 MAC 幫你確認...但我幫你查了一下,
      https://stackoverflow.com/questions/18961382/ios-7-simulator-showing-screen-but-not-phone
      似乎沒手機外觀是正常現象...可以用⌘ + Shift + H 替代 Home 鍵

      刪除
    4. 感謝老師說明,謝謝

      刪除
  2. 羅根大您好:
    請教一下,ListView 有內建 高至低 -> 低至高 排序 的那種按鈕嗎?
    目前想要做一個清單,上方有排序按鈕,建議該如何製作?

    回覆刪除
    回覆
    1. 沒有內建,建議用MVVM的架構,將ListView的ItemSource做Binding,
      當排序按鈕按下時去重新載入ViewModel的資料

      刪除
  3. 為什麼不把using .... 也打上去 每次都要找很久

    回覆刪除
    回覆
    1. 你是用 Visual Studio 還是 NotePad++ 在寫 Code?

      刪除
  4. 請問一下,為什麼加上

    我的 內的資料就無法顯示出來呢???

    回覆刪除

注意:只有此網誌的成員可以留言。