第十九章:集合视图(十六)

简介:

刷新内容
如您所见,如果您使用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="&lt; Back to List"
                    HorizontalOptions="Center"
                    Clicked="OnBackButtonClicked" />
        </StackLayout>
    </Grid>
</ContentPage>

这些项目非常适合ImageCell,但可能不适用于Windows 10移动设备:
2018_11_06_093009
当您在此列表中向下滑动手指时,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的按钮:
2018_11_06_093310
注意ItemSelected事件处理程序如何将ListView的SelectedItem属性设置为null,从而有效地取消选择该项。 (但是,所选项仍可在事件参数的SelectedItem属性中使用。)这是将ListView用于导航目的时的常用技术。 当用户返回ListView时,您不希望仍然选择该项。 当然,将ListView的SelectedItem属性设置为null会导致对ItemSelected事件处理程序的另一次调用,但如果处理程序在SelectedItem为null时忽略大小写,则第二次调用应该不是问题。
更复杂的程序将导航到第二页或使用MasterDetailPage的细节部分来显示项目。 这些技术将在以后的章节中展示。

目录
相关文章
|
JavaScript Android开发 iOS开发