しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で ListView に引っ張って更新を追加したい

ListView に RSS フィードのリスト表示を行ったらつけたくなるのが引っ張り更新

というわけで今度はこれを追加してみます


実は Xamarin.Forms の ListView には IsRefreshing、IsPullToRefreshEnabled、RefreshCommand というプロパティがあり、これらを利用すれば比較的かんたんに対応できてしまいます

まずは XAML を修正

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamarinReader.Views.TopPage"
             x:Name="root"
             BindingContext="{Binding Path=ViewModel, Source={x:Reference Name=root}}">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0,20,0,0"/>
    </ContentPage.Padding>
    <StackLayout Orientation="Vertical"
                 Spacing="10">
        <StackLayout Orientation="Horizontal"
                     Padding="15,5"
                     BackgroundColor="#33ffffff"
                     Spacing="5">
            <Label Text="TechCrunch"
                   TextColor="Lime"
                   FontSize="Medium"/>
            <Label Text="Reader"
                   FontSize="Medium"/>
        </StackLayout>
        <ListView ItemsSource="{Binding Items}"
                  SeparatorColor="Transparent"
                  HasUnevenRows="True"
                  IsRefreshing="{Binding IsRefresing}"
                  IsPullToRefreshEnabled="True"
                  RefreshCommand="{Binding RefreshCommand}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout Orientation="Vertical"
                                     Spacing="0"
                                     Padding="15,10">
                            <Image Source="{Binding Thumbnail}"
                                   HeightRequest="150"
                                   VerticalOptions="StartAndExpand"
                                   Aspect="AspectFill"/>
                            <StackLayout Orientation="Vertical"
                                         Padding="10"
                                         BackgroundColor="#66000000"
                                         Spacing="10">
                                <Label Text="{Binding Title}"
                                       TextColor="Accent"
                                       FontSize="Medium"/>
                                <Label Text="{Binding Description}"
                                       TextColor="Default"
                                       FontSize="Small"/>
                            </StackLayout>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
</ContentPage>

ListView.IsPullToRefreshEnabled = true を指定した上で、IsRefreshing と RefreshCommand をバインディングします

RefreshCommand は ListView が引っ張られたときに呼び出されます

次に ViewModel は割愛してコードビハインド

/// <summary>
/// Top page
/// </summary>
public partial class TopPage : ContentPage
{
    ~ 中略 ~

    /// <summary>
    /// Constructir
    /// </summary>
    public TopPage()
    {
        this.ViewModel = new TopPageViewModel();
        this.ViewModel.RefreshCommand = DelegateCommand.FromAsyncHandler(this.Refresh);
        this.newsFeed = ((App)App.Current).Container.Resolve<INewsFeedService>();

        InitializeComponent();
    }

    /// <summary>
    /// Appearing event handler
    /// </summary>
    protected override async void OnAppearing()
    {
        base.OnAppearing();

        await this.Refresh();
    }

    /// <summary>
    /// Refresh news feed items
    /// </summary>
    /// <returns>Task</returns>
    private async Task Refresh()
    {
        this.ViewModel.IsRefresing = true;

        await this.newsFeed.Update();

        // Add items on UI thread
        foreach (var item in this.newsFeed.Feed.Channel.Items.OrderBy(i => i.PubDate))
        {
            var vm = new NewsItemViewModel
            {
                UniqueId = item.Guid,
                Categories = new ObservableCollection<string>(item.Categories),
                Title = item.Title,
                Link = item.Link,
                LastUpdated = item.PubDate,
                Description = ReadMoreRegex.Replace(
                                WebUtility.HtmlDecode(
                                HtmlTagRegex.Replace(item.Description, string.Empty)),
                                string.Empty),
            };
            var imgMatch = ImgTagRegex.Match(item.Description);
            if (imgMatch.Success)
            {
                vm.Thumbnail = ImageSource.FromUri(new Uri(imgMatch.Groups["uri"].Value));
            }

            var oldItem = this.ViewModel.Items.FirstOrDefault(i => i.UniqueId.Equals(vm.UniqueId));
            if (oldItem != null)
            {
                if (oldItem.LastUpdated.CompareTo(vm.LastUpdated) >= 0)
                {
                    // no update
                    continue;
                }

                // updated
                oldItem.Categories = vm.Categories;
                oldItem.Title = vm.Title;
                oldItem.Link = vm.Link;
                oldItem.LastUpdated = vm.LastUpdated;
                oldItem.Description = vm.Description;
                oldItem.Thumbnail = vm.Thumbnail;
                continue;
            }

            // new item
            this.ViewModel.Items.Insert(0, vm);
        }

        this.ViewModel.IsRefresing = false;
    }
}

リスト情報の更新処理を Refresh() メソッドに分割し、RefreshCommand に設定しています

Refresh() メソッドの中では IsRefresing を更新が終わるまで true にします・・・こうすることで ListView の更新中アニメーションの表示有無を制御できるみたいですね

それ以外の部分はちょっと変わっていますが、基本的に更新のあった記事が新しい順に先頭に来るようにしているだけです

さっそくおためし~

f:id:matatabi_ux:20150816184502g:plain

ほんとにかんたんにできましたね!