しっぽを追いかけて

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

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

Xamarin.Forms で ListView のコンテキストメニューを追加したい

前回 はリストの選択時に読み上げを行うようにしました

今回はさらにコンテキストアクション(AndroidWindows Phone は長押し、iOS は水平スワイプ)で追加のメニュー表示(詳細記事のリンクを開く)を加えてみたいと思います


まずは 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"
                  ItemSelected="OnListItemSelected"
                  RefreshCommand="{Binding RefreshCommand}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <ViewCell.ContextActions>
                            <MenuItem Text="Read more"
                                      Command="{Binding LaunchLinkUriCommand}"
                                      CommandParameter="{Binding}"/>
                        </ViewCell.ContextActions>
                        
                        <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.ItemTemplate の ViewCell に下記の記述を追加しました

                        <ViewCell.ContextActions>
                            <MenuItem Text="Read more"
                                      Command="{Binding LaunchLinkUriCommand}"
                                      CommandParameter="{Binding}"/>
                        </ViewCell.ContextActions>

この修正を受けてコードビハインドも修正します

/// <summary>
/// Top page
/// </summary>
public partial class TopPage : ContentPage
{
    ~ 中略 ~
    
    /// <summary>
    /// Appearing event handler
    /// </summary>
    protected override async void OnAppearing()
    {
        base.OnAppearing();

        this.speech = ((App)App.Current).Container.Resolve<ITextSpeechService>();
        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),
                LaunchLinkUriCommand = new DelegateCommand<NewsItemViewModel>(this.LaunchLinkUri),
            };
            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;
    }

    /// <summary>
    /// ListView item selected event handler
    /// </summary>
    /// <param name="sender">Event publisher</param>
    /// <param name="e">Event arguments</param>
    private void OnListItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
        var item = e.SelectedItem as NewsItemViewModel;
        if (item == null)
        {
            return;
        }

        this.speech.SetLanguage("English");
        this.speech.Speak(string.Format("{0}: {1}", item.Title, item.Description));
    }

    /// <summary>
    /// Launch a selected item link uri
    /// </summary>
    /// <param name="item">Select item</param>
    private void LaunchLinkUri(NewsItemViewModel item)
    {
        Device.OpenUri(new Uri(item.Link));
    }
}

リンクを開くための LaunchLinkUri というメソッドを追加して、リストアイテムの ViewModel である NewsItemViewModel.LaunchLinkUriCommand に設定しています

最終的に先ほどの XAML の Read More の MenuItem にバインドされます

やることはこれだけ!実にかんたん!そしてさっそくおためし

f:id:matatabi_ux:20150822031012g:plain

Windows Phone はにょきっと生えます・・・プラットフォームごとに全然インタラクションが違いますがそれは省略