Quantcast
Channel: かずきのBlog@hatena
Viewing all articles
Browse latest Browse all 1387

ストアアプリでListViewに左右のスワイプ処理を追加するという記事が面白かったのでReactivePropertyを使って書いてみた

$
0
0

元ネタ

めとべや東京 #7で登壇してきましたその1 ストアアプリでListViewに左右のスワイプ処理を追加する - shinji-japanのブログ

面白い内容です。読んでて記事内容で疑問点がいくつか出てきました。

  • なぜItemTemplate内のBorderの横幅と縦幅を明示してるのか
  • スワイプされた要素はどうやって特定するのか
  • MVVMちっくに書きたい

なぜItemTemplate内のBorderの横幅と縦幅を明示してるのか

これはおそらくイベントを拾うため。ItemContainerStyleでHorizontalContentAlignmentとVerticalContentAlignmentをStretchにしてやることで、ItemTemplateの中身がいっぱいにひろがって明示的にサイズを指定しなくても、イベントが拾えるようになりました。

具体的にはItemContainerStyleプロパティのコピーを以下のように編集します。

<Style x:Key="ListViewItemStyle1"TargetType="ListViewItem"><Setter Property="FontFamily"Value="{ThemeResource ContentControlThemeFontFamily}"/><Setter Property="FontSize"Value="{ThemeResource ControlContentThemeFontSize}"/><Setter Property="Background"Value="Transparent"/><Setter Property="TabNavigation"Value="Local"/><Setter Property="IsHoldingEnabled"Value="True"/><Setter Property="Margin"Value="0,0,18,2"/><Setter Property="HorizontalContentAlignment"Value="Left"/><Setter Property="VerticalContentAlignment"Value="Top"/><!-- この2個を追加 --><Setter Property="HorizontalContentAlignment"Value="Stretch" /><Setter Property="VerticalContentAlignment"Value="Stretch" /><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="ListViewItem"><-- 省略 --></ControlTemplate></Setter.Value></Setter></Style>

スワイプされた要素はどうやって特定するのか

イベント引数のOriginalSourceのDataContextからかっさらってやればよかったです。

MVVMチックに書きたい

ReactivePropertyのEventToReactiveCommandを使ってみました。

こんなConverterを作って、スワイプされた要素とスワイプの方向のTupleを返すようにしました。

publicclass ManipulationDeltaRoutedEventArgsToStringConverter : ReactiveConverter<ManipulationDeltaRoutedEventArgs, Tuple<string, string>>
{
    protectedoverride IObservable<Tuple<string, string>> OnConvert(IObservable<ManipulationDeltaRoutedEventArgs> source)
    {
        var ox = source
            .Select(x => Tuple.Create(x.OriginalSource as FrameworkElement, x.Delta.Translation.X));
        return Observable.Merge(
            ox
                .Where(x => x.Item2 > 10)
                .Throttle(TimeSpan.FromSeconds(0.5))
                .ObserveOnUIDispatcher()
                .Select(x => Tuple.Create(x.Item1.DataContext asstring, "Right")),
            ox
                .Where(x => x.Item2 < -10)
                .Throttle(TimeSpan.FromSeconds(0.5))
                .ObserveOnUIDispatcher()
                .Select(x => Tuple.Create(x.Item1.DataContext asstring, "Left")));
    }
}

Throttleを入れてるのはログを吐くようにしてみたら10以上の移動が連続して発生していたので、フィルタリングするためです。これで無駄なイベントの発生を排除しています。

これをViewModelのCommandにBindingします。

<Pagexmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:App3"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"xmlns:Core="using:Microsoft.Xaml.Interactions.Core"xmlns:Interactivity1="using:Reactive.Bindings.Interactivity"x:Class="App3.MainPage"mc:Ignorable="d"><Page.Resources><!-- ITemTemplate内からViewModelに触るためにリソースにVMを登録しておく --><local:MainPageViewModel x:Key="viewModel"/><DataTemplate x:Key="DataTemplate1"><Grid ManipulationMode="TranslateX,System"Background="Pink"><Interactivity:Interaction.Behaviors><!-- ManipulationDeltaのイベント引数を変換してコマンドに渡す --><Core:EventTriggerBehavior EventName="ManipulationDelta"><Interactivity1:EventToReactiveCommand Command="{Binding SwipeCommand, Mode=OneWay, Source={StaticResource viewModel}}"><local:ManipulationDeltaRoutedEventArgsToStringConverter /></Interactivity1:EventToReactiveCommand></Core:EventTriggerBehavior></Interactivity:Interaction.Behaviors><TextBlock Text="{Binding}"Style="{ThemeResource BodyTextBlockStyle}" /></Grid></DataTemplate><Style x:Key="ListViewItemStyle1"TargetType="ListViewItem"><!-- さっきの内容なので省略 --></Style></Page.Resources><Page.DataContext><StaticResource ResourceKey="viewModel"/></Page.DataContext><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition/></Grid.RowDefinitions><TextBlock TextWrapping="Wrap"Text="{Binding SwipeMessage.Value}"Style="{StaticResource BodyTextBlockStyle}"HorizontalAlignment="Center"/><ListView Grid.Row="1"ItemsSource="{Binding Items}"ItemTemplate="{StaticResource DataTemplate1}"ItemContainerStyle="{StaticResource ListViewItemStyle1}" /></Grid></Page>

ViewModelはこんな感じ。

using Reactive.Bindings;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;

namespace App3
{
    publicclass MainPageViewModel
    {
        public ReadOnlyReactiveCollection<string> Items { get; private set; }
        public ReactiveCommand<Tuple<string, string>> SwipeCommand { get; private set; }

        public ReactiveProperty<string> SwipeMessage { get; private set; }

        public MainPageViewModel()
        {
            var source = new ObservableCollection<string>(Enumerable.Range(1, 1000).Select(x => "item " + x));
            this.Items = source.ToReadOnlyReactiveCollection(x => x);

            this.SwipeCommand = new ReactiveCommand<Tuple<string, string>>();
            this.SwipeMessage = this.SwipeCommand
                .Select(x => x + " swiped.")
                .ToReactiveProperty();
        }
    }
}

実行してスワイプしてみた。ちゃんととれてる。

f:id:okazuki:20150222234807p:plain


Viewing all articles
Browse latest Browse all 1387

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>