[Xamarin.Forms] シンプルなアニメーション(回転、拡大・縮小、移動、フェード)を実行する方法

はじめに

こんにちは。加藤です。
今回は、Xamarin.Formsで回転、拡大・縮小、移動、フェードなどのシンプルなアニメーションを実行する方法について紹介します。

検証環境

以下の環境で動作を確認しています。

  • Xamarin Studio Community バージョン 6.2(build 1829)
  • Xamarin.Forms バージョン 2.3.3.193
  • iPhone 7 iOS 10.2(シミュレーター)
  • Nexus 5 Androidバージョン6.0.1(実機)

画面レイアウト

以下のように、一通りのアニメーションを試せるようにボタンを配置しました。アニメーション対象は青いBoxViewとしました。

simple_animation_screen

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" xmlns:local="clr-namespace:AnimationSample" x:Class="AnimationSample.AnimationSamplePage">
    <StackLayout Padding="10, 60, 10, 10" Spacing="100">
        <BoxView x:Name="boxView" BackgroundColor="Blue" WidthRequest="100" HeightRequest="100" HorizontalOptions="Center" />
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="44" />
                <RowDefinition Height="44" />
                <RowDefinition Height="44" />
                <RowDefinition Height="44" />
                <RowDefinition Height="44" />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Button x:Name="rotationButton" TextColor="White" Text="Rotation" Grid.Row="0" Grid.Column="0" BackgroundColor="Gray" Clicked="RotationButton_Clicked" />
            <Button x:Name="relativeRotationButton" TextColor="White" Text="RelativeRotation" Grid.Row="0" Grid.Column="1" BackgroundColor="Gray" Clicked="RelativeRotationButton_Clicked" />
            <Button x:Name="scalingButton" TextColor="White" Text="Scaling" Grid.Row="1" Grid.Column="0" BackgroundColor="Gray" Clicked="ScalingButton_Clicked" />
            <Button x:Name="relativeScalingButton" TextColor="White" Text="RelativeScaling" Grid.Row="1" Grid.Column="1" BackgroundColor="Gray" Clicked="RelativeScalingButton_Clicked" />
            <Button x:Name="translationButton" TextColor="White" Text="Translation" Grid.Row="2" Grid.Column="0" BackgroundColor="Gray" Clicked="TranslationButton_Clicked" />
            <Button x:Name="fadingButton" TextColor="White" Text="Fading" Grid.Row="2" Grid.Column="1" BackgroundColor="Gray" Clicked="FadingButton_Clicked" />
            <Button x:Name="compoundButton" TextColor="White" Text="Compound" Grid.Row="3" Grid.Column="0" BackgroundColor="Gray" Clicked="CompoundButton_Clicked" />
            <Button x:Name="compositeButton" TextColor="White" Text="Composite" Grid.Row="3" Grid.Column="1" BackgroundColor="Gray" Clicked="CompositeButton_Clicked" />
            <Button x:Name="resetButton" TextColor="White" Text="Reset" Grid.Row="4" Grid.ColumnSpan="2" BackgroundColor="Gray" Clicked="ResetButton_Clicked" />
        </Grid>
    </StackLayout>
</ContentPage>

アニメーションの実装

回転(絶対指定)

回転はRotateToメソッドで行います。第1引数に回転の角度(度)、第2引数にアニメーションの時間(ミリ秒)を指定します。

private async void RotationButton_Clicked(object sender, System.EventArgs e)
{
    // 1秒かけて360度になるように回転する。
    await boxView.RotateTo(360, 1000);
    // アニメーション後にRotationを0に戻す。これによって再度ボタンをタップした時にアニメーションが行えるようにする。
    boxView.Rotation = 0;
}
 iOS  Android
ios_RotateTo android_RotateTo

RotateToはZ方向を軸に回転しますが、その他にもX方向を軸に回転するRotateXToメソッドや、Y方向を軸に回転するRotateYToメソッドも用意されています。

回転(相対指定)

上で述べている「絶対指定」とは、「指定した値になるようにアニメーションする」という意味です。 それに対して「相対指定」もあります。これは「現在の値に指定した増減値を加えるアニメーション」です。 相対指定の回転はRelRotateToメソッドで行います。引数はRotateToメソッドと同じです。

private async void RelativeRotationButton_Clicked(object sender, System.EventArgs e)
{
    // 1秒かけて現在の角度に30度プラスする。
    await boxView.RelRotateTo(30, 1000);
}

ボタンをタップする度に30度回転しています。

 iOS  Android
ios_RelRotateTo android_RelRotateTo

拡大・縮小(絶対指定)

拡大縮小はScaleToメソッドで行います。第1引数に拡大・縮小の倍率、第2引数にアニメーションの時間(ミリ秒)を指定します。
以下は2倍に拡大する例です。

private async void ScalingButton_Clicked(object sender, System.EventArgs e)
{
    // 1秒かけて2倍に拡大
    await boxView.ScaleTo(2, 1000);
}
 iOS  Android
ios_ScaleTo android_ScaleTo

拡大・縮小(相対指定)

拡大・縮小にも相対指定のメソッド、RelScaleToが用意されています。

private async void RelativeScalingButton_Clicked(object sender, System.EventArgs e)
{
    // 現在のScaleに0.5プラスする。
    await boxView.RelScaleTo(0.5, 1000);
}

1回のボタンタップでScaleを+0.5しているので、2回ボタンをタップすると元の大きさの2倍になります。

 iOS  Android
ios_RelScaleTo android_RelScaleTo

移動

移動はTranslateToメソッドで行います。第1引数にX方向の移動量、第2引数にY方向の移動量、第3引数にアニメーションの時間(ミリ秒)を指定します。

private async void TranslationButton_Clicked(object sender, System.EventArgs e)
{
    // 1秒かけて右と下に50ずつ移動
    await boxView.TranslateTo(50, 50, 1000);
}

右と下に同時に移動するので斜めに移動します。

 iOS  Android
ios_TranslateTo android_TranslateTo

フェード

フェードはFadeToメソッドで行います。 以下の例では、1秒かけてBoxViewが透明な状態から徐々に表示されていきます。

private async void FadingButton_Clicked(object sender, System.EventArgs e)
{
    // 不透明度の初期値は0(透明)
    boxView.Opacity = 0;
    // 1秒かけて不透明度を1にする
    await boxView.FadeTo(1, 1000);
}
 iOS  Android
ios_FadeTo android_FadeTo

拡大・縮小や回転の中心を変更する

デフォルトでは拡大・縮小や回転の中心はビューの真ん中なのですが、AnchorXAnchorXプロパティで中心を変更することができます。

以下は、BoxViewの左上を中心に360度回転する例です。

private async void RotationButton_Clicked(object sender, System.EventArgs e)
{
    // ビューの左上を中心にする。
    boxView.AnchorX = 0;
    boxView.AnchorY = 0;
    // 1秒かけて360度になるように回転する。
    await boxView.RotateTo(360, 1000);
}
 iOS  Android
ios_Anchor_zero android_Anchor_zero

複数のアニメーションを組み合わせる

上述したアニメーションを組み合わせることも可能です。

アニメーションをシーケンシャルに1つずつ実行したい

アニメーションを1つずつ順番に実行したい場合はawaitを使用します。これにより前のアニメーションが終わってから次のアニメーションが行われます。

private async void CompoundButton_Clicked(object sender, System.EventArgs e)
{
    await boxView.TranslateTo(50, 50, 500); // 500ミリ秒かけて右下に50移動
    await boxView.RelRotateTo(30, 1000);    // 1000ミリ秒かけて30度回転
    await boxView.ScaleTo(0.5, 2000);       // 2000ミリ秒かけて0.5倍の大きさに
}
 iOS  Android
ios_Compound android_Compound

単純にアニメーションを同時実行したい

単純にアニメーションを同時実行したい場合はawaitしなければOKです。ただし、この場合アニメーションの完了やキャンセルを感知できないので通常は後述の方法を使った方がよいと思います。

private void CompositeButton_Clicked(object sender, System.EventArgs e)
{
    boxView.TranslateTo(50, 50, 500);
    boxView.RelRotateTo(30, 1000);
    boxView.ScaleTo(0.5, 2000);
}

同時に開始したアニメーションが1つでも終わったら後続の処理をしたい

この場合はTask.WhenAnyを使います。
Task.WhenAnyは引数にアニメーションのTaskを取ります。

以下の例では3つアニメーションを同時実行していますが、一番時間の短いTranslateToのアニメーションが終わるとすぐにWriteLineが実行されます。

private async void CompositeButton_Clicked(object sender, System.EventArgs e)
{
    // WhenAnyは同時に開始したアニメーションが1つでも終われば完了となる
    await Task.WhenAny(
        boxView.TranslateTo(50, 50, 500),
        boxView.RelRotateTo(30, 1000),
        boxView.ScaleTo(0.5, 2000)
    );
    WriteLine("finish animation");
}

同時に開始したアニメーションが全て終わったら後続の処理をしたい

この場合はTask.WhenAllを使います。Task.WhenAnyと同様、引数にアニメーションのTaskを取ります。  

private async void CompositeButton_Clicked(object sender, System.EventArgs e)
{
    // WhenAllは同時に開始したアニメーションが全て終われば完了となる
    await Task.WhenAll(
        boxView.TranslateTo(50, 50, 500),
        boxView.RelRotateTo(30, 1000),
        boxView.ScaleTo(0.5, 2000)
    );
    WriteLine("finish animations.");
}
 iOS  Android
ios_Composite android_Composite

アニメーションをキャンセルするには

ViewExtensions.CancelAnimations(対象のビュー)でアニメーションをキャンセルすることができます。
今回のサンプルだとResetボタンの処理で使っていました。

private void ResetButton_Clicked(object sender, System.EventArgs e)
{
    // アニメーションをキャンセル
    ViewExtensions.CancelAnimations(boxView);

    // アニメーションで変更したプロパティをリセット
    boxView.Rotation = 0;
    boxView.Scale = 1;
    boxView.TranslationX = 0;
    boxView.TranslationY = 0;
    boxView.Opacity = 1;
    boxView.AnchorX = 0.5;
    boxView.AnchorY = 0.5;
}

おわりに

今回は、Xamarin.Formsで回転、拡大・縮小、移動、フェードなどのシンプルなアニメーションを実行する方法をご紹介しました。
非常に分かりやすくて使い易いAPIだと思います。

参考