2017/6/30

Xamarin.Forms 教學系列文(十九.貳 - 1)ListView - ItemSelected & ObservableCollection



學習目標
  • ListView 的 ItemSource - 賦予資料的屬性
  • data binding with SelecctedItem
  • ObservableCollection - 會發出更動通知的資料集

ListView 應該是 Xamarin.Forms 最重要的物件之一,如果有寫過 Webform,等級大概跟 GridView 差不多...

ListView 是一個顯示 相同類型資料集  的物件,

其中最重要的屬性就是 ItemsSource (type of IEnumerable ),一個可以讓我們指派資料集的屬性,有了這個屬性我們才能隨心所欲地讓 List 顯示資料。

ListView 還有支援 單一項目 選擇,會將點選的項目設為 SelectedItem (type of object),SelectedIem 初始值為 null。

點選項目時會觸發 ItemTapped ItemSelected 兩個 事件,點擊同樣的項目只會再次觸發 ItemTapped
只有 SelectedItem 屬性更改時,才會觸發 ItemSelected 事件。



先來看個簡單的範例:
本範例定義了擁有 17 個 顏色集合的 ListView

XAML:
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ListViewList.ListViewListPage">
    
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="10, 20, 10, 0"
                    Android="10, 0"
                    WinPhone="10, 0" />
    </ContentPage.Padding>

    <ListView x:Name="listView" />

</ContentPage>


.cs
    public partial class ListViewListPage : ContentPage
    {
        public ListViewListPage()
        {
            InitializeComponent();
            listView.ItemsSource = new List
            {
             Color.Aqua, Color.Black, Color.Blue, Color.Fuchsia,
             Color.Gray, Color.Green, Color.Lime, Color.Maroon,
             Color.Navy, Color.Olive, Color.Pink, Color.Purple,
             Color.Red, Color.Silver, Color.Teal, Color.White, Color.Yellow
             };
        }
    }


執行結果:



* SeparatorVisibility = "None" (取消格線)
* SeparatorColor = "Red" (格線顏色)



夠簡單吧.. 接著來看 ListView 點選時如何做 data binding。

點選綁定 (Data binding the selected item) 

點選 ListView 的項目時,對應的顏色去更改 BoxView 的顏色:
*OnPlatform 寫法已更改,請參考此篇文章

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ListViewArray.ListViewArrayPage">
    
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="10, 20, 10, 0"
                    Android="10, 0"
                    inPhone="10, 0" />
    </ContentPage.Padding>
    
    <StackLayout>
        
        <!-- ListView,雙向綁定 BoxView-->
        <ListView x:Name="listView"
                  SelectedItem="{Binding Source={x:Reference boxView},
                  Path=Color,
                  Mode=TwoWay}">
            
            <ListView.ItemsSource>
                <x:Array Type="{x:Type Color}">
                    <x:Static Member="Color.Aqua" />
                    <x:Static Member="Color.Black" />
                    <x:Static Member="Color.Blue" />
                    <x:Static Member="Color.Fuchsia" />
                    <x:Static Member="Color.Gray" />
                    <x:Static Member="Color.Green" />
                    <x:Static Member="Color.Lime" />
                    <x:Static Member="Color.Maroon" />
                    <Color>Navy</Color>
                    <Color>Olive</Color>
                    <Color>Pink</Color>
                    <Color>Purple</Color>
                    <Color>Red</Color>
                    <Color>Silver</Color>
                    <Color>Teal</Color>
                    <Color>White</Color>
                    <Color>Yellow</Color>
                </x:Array>
            </ListView.ItemsSource>
        </ListView>
        
        <!-- 要被更改BoxView -->
        <BoxView x:Name="boxView"
                           Color="Lime"
                           HeightRequest="100" />

    </StackLayout>
</ContentPage>
本範例直接將 DataSource 寫在 XAML,ItemsSource 不是 Content Property,所以需要寫 <ListView.ItemsSource> 標籤

執行結果:


看這範例可能會有個疑惑,
為什麼不是將 data binding 寫在 BoxView 的 Color?

若我要因為 ListView 所選的項目 去更改 BoxView 的顏色,
很直觀的就是將 data binding 寫在 BoxView,並把 SelectedItem 視為 Source 吧! ?
    <BoxView x:Name="boxView"
             Color="{Binding Source={x:Reference listView},
                             Path=SelectedItem}"
                             HeightRequest="100" />

但這樣執行後,會很直白的產生 NullReferenceException 錯誤。

因為 SelectedItem 初始值為 null,若要使其運作,就要先初始化:
    <ListView x:Name="listView">
        <ListView.ItemsSource>
            <x:Array Type="{x:Type Color}">
                …
            </x:Array>
        </ListView.ItemsSource>
        
        <ListView.SelectedItem>
            <Color>Lime</Color>
        </ListView.SelectedItem>
    </ListView>

針對這個問題更好的解法就是,回到一開始的範例,反過來將 BoxView 成為 SelectedItem 的Source,並將 Binding Mode 改成 TwoWay。

ObservableCollection

我們將一個 List<DateTime> 集合 指派給 ItemsSource,且每秒鐘向此集合新增一個 DateTime 值:
        public ListViewLoggerPage()
        {
            InitializeComponent();
            List list = new List();
            listView.ItemsSource = list;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
            {
                list.Add(DateTime.Now);
                return true;
            });
        }

這裡會發現一個問題,只有當螢幕轉動時 ListView 才會刷新,
而這個問題源自 List 類別並沒有實作 通知機制,當集合更改時不會通知 ListView 一起更動。

在此,微軟大大提供一個很貼心的類別 - ObservableCollection
可以視為擁有通知機制的 List<T>
ObservableCollection 有實作 INotifyCollectionChanged 介面

每當集合項目 新增刪除被替換重新排序  時,ObservableCollection 都會觸發 CollectionChanged 事件,進而通知 ListView 更動。

底下範例與上方程式邏輯相同,除了將 List<DateTime> 替換成 ObservableCollection<DateTime>,如此一來就能看到每秒刷新的 ListView:
    public partial class ObservableLoggerPage : ContentPage
    {
        public ObservableLoggerPage()
        {
            InitializeComponent();
            ObservableCollection list = new ObservableCollection();
            listView.ItemsSource = list;

            Device.StartTimer(TimeSpan.FromSeconds(1), () =>
            {
                list.Add(DateTime.Now);
                return true;
            });
        }
    }


沒有留言:

張貼留言