重新整理内容
如您所見,如果您使用ObservableCollection作為ListView的源,則對集合的任何更改都會導緻ObservableCollection觸發CollectionChanged事件,而ListView會通過重新整理項目的顯示來響應。
有時,這種類型的重新整理必須由使用者控制的東西補充。例如,考慮一個電子郵件用戶端或RSS閱讀器。這樣的應用程式可能被配置為每15分鐘左右查找新的電子郵件或RSS檔案的更新,但是使用者可能有點不耐煩并且可能希望程式立即檢查新資料。
為此,開發了ListView支援的約定。如果ListView的IsPullToRefresh屬性設定為true,并且使用者向下滑動ListView,則ListView将通過調用綁定到其RefreshCommand屬性的ICommand對象的Execute方法來響應。 ListView還将其IsRefreshing屬性設定為true并顯示某種動畫,表明它正忙。
實際上,ListView并不忙。隻是等待通知新資料可用。您可能已經編寫了由ICommand對象的Execute方法調用的代碼來執行異步操作,例如Web通路。它必須通過将ListView的IsRefreshing屬性設定為false來通知ListView它已完成。此時,ListView顯示新資料并完成重新整理。
這聽起來有點複雜,但如果您将這個功能建構到提供資料的ViewModel中,它會變得更加容易。整個過程通過一個名為RssFeed的程式進行示範,該程式通路來自NASA的RSS源。
RssFeedViewModel類負責使用RSS提要下載下傳XML并對其進行解析。首先在設定Url屬性并且set通路器調用LoadRssFeed方法時發生:
public class RssFeedViewModel : ViewModelBase
{
string url, title;
IList<RssItemViewModel> items;
bool isRefreshing = true;
public RssFeedViewModel()
{
RefreshCommand = new Command(
execute: () =>
{
LoadRssFeed(url);
},
canExecute: () =>
{
return !IsRefreshing;
});
}
public string Url
{
set
{
if (SetProperty(ref url, value) && !String.IsNullOrEmpty(url))
{
LoadRssFeed(url);
}
}
get
{
return url;
}
}
public string Title
{
set { SetProperty(ref title, value); }
get { return title; }
}
public IList<RssItemViewModel> Items
{
set { SetProperty(ref items, value); }
get { return items; }
}
public ICommand RefreshCommand { private set; get; }
public bool IsRefreshing
{
set { SetProperty(ref isRefreshing, value); }
get { return isRefreshing; }
}
public void LoadRssFeed(string url)
{
WebRequest request = WebRequest.Create(url);
request.BeginGetResponse((args) =>
{
// Download XML.
Stream stream = request.EndGetResponse(args).GetResponseStream();
StreamReader reader = new StreamReader(stream);
string xml = reader.ReadToEnd();
// Parse XML to extract data from RSS feed.
XDocument doc = XDocument.Parse(xml);
XElement rss = doc.Element(XName.Get("rss"));
XElement channel = rss.Element(XName.Get("channel"));
// Set Title property.
Title = channel.Element(XName.Get("title")).Value;
// Set Items property.
List<RssItemViewModel> list =
channel.Elements(XName.Get("item")).Select((XElement element) =>
{
// Instantiate RssItemViewModel for each item.
return new RssItemViewModel(element);
}).ToList();
Items = list;
// Set IsRefreshing to false to stop the 'wait' icon.
IsRefreshing = false;
}, null);
}
}
LoadRssFeed方法使用System.Xml.Linq命名空間中的LINQ-to-XML接口來解析XML檔案,并設定類的Title屬性和Items屬性。 Items屬性是RssItemViewModel對象的集合,它定義與RSS提要中的每個項目關聯的五個屬性。 對于XML檔案中的每個item元素,LoadRssFeed方法執行個體化一個RssItemViewModel對象:
public class RssItemViewModel
{
public RssItemViewModel(XElement element)
{
// Although this code might appear to be generalized, it is
// actually based on desired elements from the particular
// RSS feed set in the RssFeedPage.xaml file.
Title = element.Element(XName.Get("title")).Value;
Description = element.Element(XName.Get("description")).Value;
Link = element.Element(XName.Get("link")).Value;
PubDate = element.Element(XName.Get("pubDate")).Value;
// Sometimes there's no thumbnail, so check for its presence.
XElement thumbnailElement = element.Element(
XName.Get("thumbnail", "http://search.yahoo.com/mrss/"));
if (thumbnailElement != null)
{
Thumbnail = thumbnailElement.Attribute(XName.Get("url")).Value;
}
}
public string Title { protected set; get; }
public string Description { protected set; get; }
public string Link { protected set; get; }
public string PubDate { protected set; get; }
public string Thumbnail { protected set; get; }
}
RssFeedViewModel的構造函數還将其RefreshCommand屬性設定為等于具有Execute方法的Command對象,該方法也調用LoadRssFeed,該方法通過将類的IsRefreshing屬性設定為false來完成。為避免Web通路重疊,隻有IsRefreshing為false時,RefreshCommand的CanExecute方法才傳回true。
請注意,RssFeedViewModel中的Items屬性不必是ObservableCollection,因為一旦建立了Items集合,集合中的項就不會更改。當LoadRssFeed方法擷取新資料時,它會建立一個全新的List對象,并将其設定為Items屬性,進而觸發PropertyChanged事件。
下面顯示的RssFeedPage類執行個體化RssFeedViewModel并配置設定Url屬性。此對象成為StackLayout的BindingContext,其中包含用于顯示Title屬性和ListView的Label。 ListView的ItemsSource,RefreshCommand和IsRefreshing屬性都綁定到RssFeedViewModel中的屬性:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:RssFeed"
x:Class="RssFeed.RssFeedPage">
<ContentPage.Padding>
<OnPlatform x:TypeArguments="Thickness"
iOS="10, 20, 10, 0"
Android="10, 0"
WinPhone="10, 0" />
</ContentPage.Padding>
<ContentPage.Resources>
<ResourceDictionary>
<local:RssFeedViewModel x:Key="rssFeed"
Url="http://earthobservatory.nasa.gov/Feeds/rss/eo_iotd.rss" />
</ResourceDictionary>
</ContentPage.Resources>
<Grid>
<StackLayout x:Name="rssLayout"
BindingContext="{StaticResource rssFeed}">
<Label Text="{Binding Title}"
FontAttributes="Bold"
HorizontalTextAlignment="Center" />
<ListView x:Name="listView"
ItemsSource="{Binding Items}"
ItemSelected="OnListViewItemSelected"
IsPullToRefreshEnabled="True"
RefreshCommand="{Binding RefreshCommand}"
IsRefreshing="{Binding IsRefreshing}">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell Text="{Binding Title}"
Detail="{Binding PubDate}"
ImageSource="{Binding Thumbnail}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<StackLayout x:Name="webLayout"
IsVisible="False">
<WebView x:Name="webView"
VerticalOptions="FillAndExpand" />
<Button Text="< Back to List"
HorizontalOptions="Center"
Clicked="OnBackButtonClicked" />
</StackLayout>
</Grid>
</ContentPage>
這些項目非常适合ImageCell,但可能不适用于Windows 10移動裝置:

當您在此清單中向下滑動手指時,ListView将通過調用RefreshCommand對象的Execute方法并顯示訓示其忙碌的動畫進入重新整理模式。 當RssFeedViewModel将IsRefreshing屬性設定回false時,ListView将顯示新資料。 (這不是在Windows運作時平台上實作的。)
此外,該頁面包含另一個StackLayout,它位于XAML檔案的底部,其IsVisible屬性設定為false。 帶有ListView的第一個StackLayout和第二個隐藏的StackLayout共享一個單元格網格,是以它們基本上都占據整個頁面。
當使用者選擇ListView中的項時,代碼隐藏檔案中的ItemSelected事件處理程式使用ListView隐藏StackLayout并使第二個StackLayout可見:
public partial class RssFeedPage : ContentPage
{
public RssFeedPage()
{
InitializeComponent();
}
void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs args)
{
if (args.SelectedItem != null)
{
// Deselect item.
((ListView)sender).SelectedItem = null;
// Set WebView source to RSS item
RssItemViewModel rssItem = (RssItemViewModel)args.SelectedItem;
// For iOS 9, a NSAppTransportSecurity key was added to
// Info.plist to allow accesses to EarthObservatory.nasa.gov sites.
webView.Source = rssItem.Link;
// Hide and make visible.
rssLayout.IsVisible = false;
webLayout.IsVisible = true;
}
}
void OnBackButtonClicked(object sender, EventArgs args)
{
// Hide and make visible.
webLayout.IsVisible = false;
rssLayout.IsVisible = true;
}
}
第二個StackLayout包含一個WebView,用于顯示RSS feed項引用的項和一個傳回ListView的按鈕:
注意ItemSelected事件處理程式如何将ListView的SelectedItem屬性設定為null,進而有效地取消選擇該項。 (但是,所選項仍可在事件參數的SelectedItem屬性中使用。)這是将ListView用于導航目的時的常用技術。 當使用者傳回ListView時,您不希望仍然選擇該項。 當然,将ListView的SelectedItem屬性設定為null會導緻對ItemSelected事件處理程式的另一次調用,但如果處理程式在SelectedItem為null時忽略大小寫,則第二次調用應該不是問題。
更複雜的程式将導航到第二頁或使用MasterDetailPage的細節部分來顯示項目。 這些技術将在以後的章節中展示。