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 の更新中アニメーションの表示有無を制御できるみたいですね
それ以外の部分はちょっと変わっていますが、基本的に更新のあった記事が新しい順に先頭に来るようにしているだけです
さっそくおためし~
ほんとにかんたんにできましたね!