刷新内容
如您所见,如果您使用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的细节部分来显示项目。 这些技术将在以后的章节中展示。