在 Xamarin.Forms 所提供的內建控制項中,僅能夠提供垂直捲動的方式來呈現出清單資料,不過,有很多時候,我們需要使用能夠水平捲動的 ListView 功能,這個時候,Xamarin.Forms內建的控制項就無法幫助您做到這樣的需求。
不過,在網路上找到一篇文章
他提供了相當不錯的水平捲動的客製化控制項的原始碼,我在這裡進行一些簡化與修正,做到如下圖的效果;不過,若您對這樣的Custom Control 有興趣的話,您首先需要對於 BindableProperty 要有所認識與知道如何實作。
專案原始碼
客製化水平捲動 ListView 控制項
其實,這個客製化控制項相當的簡單,那就是繼承了
ScrollView
這個 Xamarin.Forms 的控制項,並且設定使用的是水平捲動的方式,而要顯示的內容,則是透過了一個可綁定屬性 ItemsSourceProperty
讓開發者可以在 XAML 中來綁定這個屬性,設定要顯示的資料清單來源。在這個 ItemsSourceProperty
可綁定屬性中,會訂閱 INotifyCollectionChanged
這個事件,其用於告知接收程式發生動態變更,例如當加入和移除項目時,或重新整理整份清單時。因此,當資料有異動的時候,就會同步將 ScrollView 內的 StackLayout 裡面的項目,進行新增或者刪除。
這個客製化控制項,可以使用
DataTemplate
來在 XAML 中自行擴充與定義想要每筆資料長成甚麼樣子。
原作者的有些內容,在這裡我將其移除,因為,我用不到,有興趣的人,可以自行參考原作者的文章。
原始碼如下
public class HorizontalListView : ScrollView
{
readonly StackLayout _imageStack;
public HorizontalListView()
{
this.Orientation = ScrollOrientation.Horizontal;
_imageStack = new StackLayout
{
Orientation = StackOrientation.Horizontal
};
this.Content = _imageStack;
}
public IList<View> Children
{
get
{
return _imageStack.Children;
}
}
// http://dotnetbyexample.blogspot.tw/2016/03/xamarin-form-21-upgradesome-surprises.html
public static readonly BindableProperty ItemsSourceProperty =
BindableProperty.Create(nameof(ItemsSource), // 屬性名稱
typeof(IList), // 回傳類型
typeof(HorizontalListView), // 宣告類型
default(IList), // 預設值
BindingMode.TwoWay, // 預設資料繫結模式
propertyChanging: (bindableObject, oldValue, newValue) =>
{
((HorizontalListView)bindableObject).ItemsSourceChanging();
},
propertyChanged: (bindableObject, oldValue, newValue) =>
{
((HorizontalListView)bindableObject).ItemsSourceChanged(bindableObject, oldValue as IList, newValue as IList);
}
);
public IList ItemsSource
{
get
{
return (IList)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
void ItemsSourceChanging()
{
if (ItemsSource == null)
return;
}
void ItemsSourceChanged(BindableObject bindable, IList oldValue, IList newValue)
{
if (ItemsSource == null)
return;
var notifyCollection = newValue as INotifyCollectionChanged;
if (notifyCollection != null)
{
notifyCollection.CollectionChanged += (sender, args) =>
{
if (args.NewItems != null)
{
foreach (var newItem in args.NewItems)
{
var view = (View)ItemTemplate.CreateContent();
var bindableObject = view as BindableObject;
if (bindableObject != null)
bindableObject.BindingContext = newItem;
_imageStack.Children.Add(view);
}
}
if (args.OldItems != null)
{
// not supported
_imageStack.Children.RemoveAt(args.OldStartingIndex);
}
};
}
}
public DataTemplate ItemTemplate
{
get;
set;
}
}
如何使用客製化水平捲動 ListView 控制項
由於這個是自訂控制項,因此,想要在頁面中使用,當然必須先要自訂一個命名空間,在這裡,定義了一個
xmlns:Controls="clr-namespace:XFHorListView.Controls"
指向了這個自訂控制項。
這個客製化水平捲動 ListView 控制項支援了 DataTemplate 功能,可以讓開發者自行透過 XAML 來描述資料需要以甚麼方式來呈現;另外,在這個控制項內,若想要設定當使用者點選某個項目的時候,需要執行 ViewModel 內的某個 DelegatedCommand 命令,可以在 DataTemplate 內,設定 View.GestureRecognizers 使用
TapGestureRecognizer
來觸發 ViewModel 內的命令;在範例中,所要觸發的命令將會定義在頁面 ViewModel 內,因此,若要綁定這個命令,需要在進行資料綁定的時候,重新指定綁定資料來源:Command="{Binding Path=BindingContext.使用者點選Command, Source={x:Reference ThisPage}}"
在這裡,當要進行資料繫結的時候,會重新設定綁定資料來源為根結點,也就是這個頁面,接著使用 Path 來指定要綁定的來源是頁面的 BindingContext 下的
使用者點選Command
。<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:Controls="clr-namespace:XFHorListView.Controls"
xmlns:behaviors="clr-namespace:Behaviors;assembly=Behaviors"
x:Class="XFHorListView.Views.MainPage"
Title="MainPage"
x:Name="ThisPage">
<StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Title}" />
<Controls:HorizontalListView
ItemsSource="{Binding MyDatas}"
HorizontalOptions="Fill"
>
<Controls:HorizontalListView.ItemTemplate>
<DataTemplate>
<Grid BackgroundColor="Transparent">
<Image
Source="{Binding ImageUrl}"
Aspect="AspectFit">
<Image.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Path=BindingContext.使用者點選Command, Source={x:Reference ThisPage}}"
CommandParameter="{Binding}" />
</Image.GestureRecognizers>
</Image>
<BoxView
HeightRequest="30"
VerticalOptions="End"
Color="{Binding Color}" />
<Label
Margin="0,0,0,10"
VerticalOptions="End"
TextColor="White"
Text="{Binding Title}" />
</Grid>
</DataTemplate>
</Controls:HorizontalListView.ItemTemplate>
</Controls:HorizontalListView>
<ListView
ItemsSource="{Binding MyDatas}"
SelectedItem="{Binding 使用者點選項目}"
>
<ListView.Behaviors>
<behaviors:EventHandlerBehavior EventName="ItemTapped">
<behaviors:InvokeCommandAction Command="{Binding ListView使用者點選Command}" />
</behaviors:EventHandlerBehavior>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
Source="{Binding ImageUrl}" Aspect="AspectFit" />
<BoxView
Grid.Column="1"
Color="{Binding Color}" />
<Label
Grid.Column="1"
TextColor="White"
Text="{Binding Title}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>