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

UWP の画面遷移でいい感じのアニメーションをさせよう

$
0
0

Connected Animation というものを使うと出来ます。

ConnectedAnimationService.GetForCurrentView() で取得した ConnectedAnimationService に対して画面遷移前と画面遷移後で対応するコントロールの紐づけをしてやる感じです。なので、画面遷移前に画面遷移前のページでアニメーションさせたいコントロールを登録して、画面遷移先でもアニメーションさせたいコントロールを指示するという処理が必要になります。

こんなイメージです。

// 画面遷移前
var a = ConnectedAnimationService.GetForCurrentView();
a.PrepareToAnimate("text", textBlock);
a.PrepareToAnimate("button", button);

Frame.Navigate(typeof(NextPage));


// 画面遷移先の OnNavigatedTo メソッドにて
var a = ConnectedAnimationService.GetForCurrentView();
a.GetAnimation("text")?.TryStart(textBlock);
a.GetAnimation("button")?.TryStart(button);

PrepareToAnimate や TryStart に渡してるのがアニメーションさせたいコントロールです。こんな感じで動きます。

f:id:okazuki:20180211233700g:plain

ListView の要素でアニメーション

普通は ListView タップしたら詳細画面に行くってパターンが多いですよね。そのときしゅっとアニメーションしたらかっこいい…!やってみよう。

データの入れ物作ります。

publicclass Person
{
    publicstring Id { get; set; } = Guid.NewGuid().ToString();
    publicstring Name { get; set; }
}

とりあえずお試しなので App クラスあたりにグローバルでリスト持たせておきましょう。

publicstatic Person[] People { get; } = Enumerable.Range(1, 100)
    .Select(x => new Person
    {
        Name = $"tanaka-{x}",
    })
    .ToArray();

MainPage.xaml で以下のようにバインドします。

<Page x:Class="App14.MainPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:App14"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><ListView x:Name="listView"><ListView.ItemTemplate><DataTemplate x:DataType="local:Person"><Grid><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><TextBlock x:Name="textBlockName"Text="{x:Bind Name}"Style="{ThemeResource BodyTextBlockStyle}" /><Border x:Name="border"Background="Red"Width="50"Height="50"Margin="5"Grid.Column="1" /></Grid></DataTemplate></ListView.ItemTemplate></ListView></Grid></Page>

コードビハインドでデータを ListView に追加しておきます。

using Windows.UI.Xaml.Controls;

// 空白ページの項目テンプレートについては、https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x411 を参照してくださいnamespace App14
{
    /// <summary>/// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。/// </summary>publicsealedpartialclass MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            listView.ItemsSource = App.People;
        }
    }
}

ListView を押したら画面遷移するようにします。ItemClick イベントを拾って

<ListView x:Name="listView"IsItemClickEnabled="True"SelectionMode="None"ItemClick="listView_ItemClick">

ListView の PrepareConnectedAnimation で下準備して画面遷移します。

privatevoid listView_ItemClick(object sender, ItemClickEventArgs e)
{
    listView.PrepareConnectedAnimation("text", e.ClickedItem, "textBlockName");
    listView.PrepareConnectedAnimation("border", e.ClickedItem, "border");
    Frame.Navigate(typeof(NextPage), ((Person)e.ClickedItem).Id);
}

遷移先のページを作ります。

<Page x:Class="App14.NextPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:App14"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><StackPanel VerticalAlignment="Center"HorizontalAlignment="Center"><TextBlock x:Name="textBlock"Style="{ThemeResource HeaderTextBlockStyle}" /><Border x:Name="border"Width="100"Height="100"Background="Red" /></StackPanel><Button x:Name="button"Content="Back"Click="Button_Click" /></Grid></Page>

OnNavigatedTo メソッドでアニメーションの紐づけを行います。

private Person _target;
protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)
{
    _target = App.People.First(x => x.Id == (string)e.Parameter);
    textBlock.Text = _target.Name;

    var a = ConnectedAnimationService.GetForCurrentView();
    a.GetAnimation("text")?.TryStart(textBlock);
    a.GetAnimation("border")?.TryStart(border);
}

戻る方向もアニメーションをつけます。遷移先のページから戻る前にアニメーションの下準備をして…

privatevoid Button_Click(object sender, RoutedEventArgs e)
{
    var a = ConnectedAnimationService.GetForCurrentView();
    a.PrepareToAnimate("text", textBlock);
    a.PrepareToAnimate("border", border);
    Frame.Navigate(typeof(MainPage), _target.Id);
}

戻った先でアニメーションを開始します。

<Page x:Class="App14.MainPage"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="using:App14"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d"><Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><ListView x:Name="listView"IsItemClickEnabled="True"SelectionMode="None"ItemClick="listView_ItemClick"Loaded="listView_Loaded"><ListView.ItemTemplate><DataTemplate x:DataType="local:Person"><Grid><Grid.ColumnDefinitions><ColumnDefinition /><ColumnDefinition Width="Auto" /></Grid.ColumnDefinitions><TextBlock x:Name="textBlockName"Text="{x:Bind Name}"Style="{ThemeResource BodyTextBlockStyle}" /><Border x:Name="border"Background="Red"Width="50"Height="50"Margin="5"Grid.Column="1" /></Grid></DataTemplate></ListView.ItemTemplate></ListView></Grid></Page>
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Navigation;

// 空白ページの項目テンプレートについては、https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x411 を参照してくださいnamespace App14
{
    /// <summary>/// それ自体で使用できる空白ページまたはフレーム内に移動できる空白ページ。/// </summary>publicsealedpartialclass MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            listView.ItemsSource = App.People;
        }

        private Person _scrollTarget;

        protectedoverridevoid OnNavigatedTo(NavigationEventArgs e)
        {
            if (e.Parameter isstring id)
            {
                _scrollTarget = App.People.FirstOrDefault(x => x.Id == id);
            }
        }

        privatevoid listView_ItemClick(object sender, ItemClickEventArgs e)
        {
            listView.PrepareConnectedAnimation("text", e.ClickedItem, "textBlockName");
            listView.PrepareConnectedAnimation("border", e.ClickedItem, "border");
            Frame.Navigate(typeof(NextPage), ((Person)e.ClickedItem).Id);
        }

        private async void listView_Loaded(object sender, RoutedEventArgs e)
        {
            if (_scrollTarget == null)
            {
                return;
            }

            listView.ScrollIntoView(_scrollTarget);
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
            {
                var a = ConnectedAnimationService.GetForCurrentView();
                await Task.WhenAll(
                    listView.TryStartConnectedAnimationAsync(a.GetAnimation("text"), _scrollTarget, "textBlockName").AsTask(),
                    listView.TryStartConnectedAnimationAsync(a.GetAnimation("border"), _scrollTarget, "border").AsTask());
            });
        }
    }
}

ポイントは ListView の Loaded イベントでアニメーションをしていることです。 OnNavigatedTo だとタイミングが早すぎるのか例外が出てしまいました。

そして、ListView で表示対象のコントロールが出てくるまでスクロールしてアニメーションをしています。アニメーションは、Dispatcher を使って少し遅らせてやってます。これをしないと自分のところではアニメーションしてくれなかった…。

ドキュメントではそんなことしてないんだけど。謎い。

docs.microsoft.com

実行するとこんな感じです。

f:id:okazuki:20180212001639g:plain


Viewing all articles
Browse latest Browse all 1387

Trending Articles