Xamarin.Forms で Activity Transitions もどきを作ってみた
Android L から Activity Transitions という概念が導入されました.
単純なスライドやフェードではない、よりコンテキストを反映したリッチな画面遷移を実現できます.
@konifarさんのMaterial Catに Activity Transitions の実例がありますので、ぜひインストールしてご覧ください.
Xamarin.Forms には Activity Transitions のような機能は API レベルでは提供されていません.私が探した限りではサードパーティー製のライブラリも見つかりませんでした.
そこで今回は Xamarin.Forms で Activity Transitions もどきの表現を実装してみます.ページ間で View を共有する Shared Elements のパターンを実装します.
実装結果がしょっぱい感じになっていますがご容赦ください.
絶対座標を取得する Effect
画面遷移の起点となる View のスクリーン上での絶対座標を取得する必要があリます.
しかしながら Xamarin.Forms.ViewElement.Bounds で取得できるものは親 View との相対座標なので不十分です.
そこでスクリーン上の絶対座標を取得する Effect を作成します.
using System;
using Xamarin.Forms;
namespace ActivityTransitionSample
{
public static class AbsoluteBoundsEffect
{
public static readonly BindableProperty AbsoluteBoundsProperty =
BindableProperty.CreateAttached("AbsoluteBounds", typeof(Rectangle), typeof(AbsoluteBoundsEffect), new Rectangle(0, 0, 0, 0));
public static Rectangle GetAbsoluteBounds(BindableObject view)
{
return (Rectangle)view.GetValue(AbsoluteBoundsProperty);
}
public static void SetAbsoluteBounds(BindableObject view, Rectangle value)
{
view.SetValue(AbsoluteBoundsProperty, value);
}
}
class ViewAbsoluteBoundsEffect : RoutingEffect
{
public ViewAbsoluteBoundsEffect() : base("Santea.ViewAbsoluteBoundsEffect")
{
}
}
}
PCLプロジェクトに AbsoluteBoundsEffect を作ります.
using System;
using ActivityTransitionSample.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ResolutionGroupName("Santea")]
[assembly: ExportEffect(typeof(ViewAbsoluteBoundsEffect), "ViewAbsoluteBoundsEffect")]
namespace ActivityTransitionSample.Droid
{
public class ViewAbsoluteBoundsEffect : PlatformEffect
{
protected override void OnAttached()
{
try
{
Control.LayoutChange += (sender, e) =>
{
UpdateAbsoluteBounds();
};
UpdateAbsoluteBounds();
}
catch (Exception ex)
{
Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
}
}
protected override void OnDetached()
{
}
private void UpdateAbsoluteBounds()
{
int[] position = new int[2];
Control.GetLocationInWindow(position);
AbsoluteBoundsEffect.SetAbsoluteBounds(Element,
new Rectangle(position[0], position[1], Control.Width, Control.Height));
}
}
}
レイアウトの変更に合わせてプロパティを変更するために LayoutChange にイベントを付与しています.
絶対座標は Android.Views.View.GetLocationInWindow で取得しています.
遷移元ページ
まずは遷移元のページを作ります.
今回はGrid状の View として FlowListView を用いました.
2016年12月22日現在、iOS 10.1 では正常に動作しないようです.
GridPage.xaml
GridPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Windows.Input;
using Xamarin.Forms;
namespace ActivityTransitionSample
{
public partial class GridPage : ContentPage
{
public IList ImageModels { get; set; }
public GridPage()
{
InitializeComponent();
ImageModels = ImageModel.GenerateList();
BindingContext = this;
}
public void OnImageClicked(object sender, EventArgs e)
{
var image = sender as Image;
Navigation.PushAsync(new DetailPage(image));
}
}
}
Grid 中の Image をクリックしたとき、Image の参照を遷移先の Page に渡します.
遷移先ページ
次に遷移先ページです.
DetailPage.xaml
アニメーションの対象は Image と StackLayout になります.それぞれフェードアニメーション用の Opacity=”0.3″ の初期値を与えています.
DetailPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace ActivityTransitionSample
{
public partial class DetailPage : ContentPage
{
private Image _image;
public DetailPage(Image image)
{
InitializeComponent();
_image = image;
Image.Source = _image.Source;
}
protected async override void OnAppearing()
{
base.OnAppearing();
await Task.WhenAll(
Image.LayoutTo((Rectangle)_image.GetValue(AbsoluteBoundsEffect.AbsoluteBoundsProperty), 0),
StackLayout.LayoutTo(new Rectangle(0, Height, 0, 0), 0)
);
Image.FadeTo(1.0, 250);
Image.LayoutTo(new Rectangle(0, 0, Width, 290), 250);
StackLayout.FadeTo(1.0, 300);
StackLayout.LayoutTo(new Rectangle(0, 290, Width, Height - 290), 300);
}
}
}
ここからが本エントリーの中心です.Page.OnAppearing にアニメーションを実装します.
await Task.WhenAll(
Image.LayoutTo((Rectangle)_image.GetValue(AbsoluteBoundsEffect.AbsoluteBoundsProperty), 0),
StackLayout.LayoutTo(new Rectangle(0, Height, 0, 0), 0)
);
まず、アニメーション前の View の座標を指定します.Image には遷移元ページの Image の絶対座標を指定します.StackLayout にはページの画面下端の座標を指定します.
Image.FadeTo(1.0, 250);
Image.LayoutTo(new Rectangle(0, 0, Width, 290), 250);
StackLayout.FadeTo(1.0, 300);
StackLayout.LayoutTo(new Rectangle(0, 290, Width, Height - 290), 300);
フェードとレイアウトのアニメーションを非同期に実行します.StackLayout を Image より50msだけ遅くすることで、それぞれのアニメーションが際立つようにしています.
実装結果
画面遷移前のページの Image を起点にして 画面遷移後のページの Image がアニメーションしている様子がご覧いただけると思います.
以上です.
参考
ディスカッション
コメント一覧
まだ、コメントがありません