使用 Xamarin.Plugin.SharedTransitions 製作出具有跨頁面之共用項目動畫效果
最近觀看完 .NET Conf: Focus on Xamarin 的 Building Beautiful Apps with Xamarin.Forms 影片,頓時間覺得 Xamarin.Forms 似乎又進化了許多,在這個影片中我看到許多驚豔內容,對於 Xamarin.Plugin.SharedTransitions 這個套件所產生出來的效果,更讓我想要能夠親自來嘗試看看,因此,我先從 Xamarin.Forms-Monkeys 這裡取得許多猴子介紹的紀錄內容,並且將這些內容包裝到練習使用的 NuGet 套件內,接著,實做出如下面影片中的動畫效果。
這個說明專案的原始碼為 xfSharedTransitions
範例專案說明
在這個測試範例專案中,將會使用 Prism Template Pack 建立一個 Xamarin.Forms 的專案,接著要安裝 Xamarin.FFImageLoading.Forms & Xamarin.Plugin.SharedTransitions 這兩個 NuGet 套件;這裡會用到 Xamarin.FFImageLoading.Forms 這個套件,這是因為一開始建立這個測試專案的時候,使用的是 Xamarin.Forms 內的 Image 元件,不過,由於要顯示的猴子圖片是來自於 Internet 上,因此,當要進行頁面切換的時候,對於共用圖片元件的動畫動作,變得不自然與動畫路徑怪怪的,可是,若使用 GiampaoloGabba / Xamarin.Plugin.SharedTransitions 內提供的範例專案,卻可以正常運作,經過了解,發現到該範例專案內使用的貓狗圖片,都是專案內的圖片檔案,不是透過 Internet 方式取得的,所以,就根據該專案的說明內容,嘗試使用 Xamarin.FFImageLoading.Forms 這個元件,讓圖片從 Internet 下載下來,使用本機上快取的圖片檔案,發現到就可以順利運作。
首先,在 [MainPage.xaml] 檔案中,建立底下的 XAML 宣告
在這裡因為會使用到 amarin.FFImageLoading.Forms & Xamarin.Plugin.SharedTransitions 功能,因此,加入了兩個命名空間 sharedTransitions & ffimageloading
而在 ContentPage 內,也使用到兩個附加屬性,用來宣告動畫需要多久的時間,這裡使用
sharedTransitions:SharedTransitionNavigationPage.TransitionDuration="500"
來宣告需要花費 500ms,並且使用 sharedTransitions:SharedTransitionNavigationPage.TransitionSelectedGroup="{Binding SelectedMonkeyId}"
用於宣告點選了哪個集合項目中的紀錄,若僅是做兩個頁面之間的切換,並沒有牽扯到集合項目,例如 ListView,這裡是可以不用使用這個附加屬性哦。<?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="xfSharedTransitions.Views.MainPage"
xmlns:behavior="http://prismlibrary.com"
xmlns:sharedTransitions="clr-namespace:Plugin.SharedTransitions;assembly=Plugin.SharedTransitions"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
sharedTransitions:SharedTransitionNavigationPage.TransitionDuration="500"
sharedTransitions:SharedTransitionNavigationPage.TransitionSelectedGroup="{Binding SelectedMonkeyId}"
Title="猴仔們">
<Grid>
<ListView
HasUnevenRows="True"
ItemsSource="{Binding AllMonkey}"
SelectionMode="None"
Footer="">
<ListView.Behaviors>
<behavior:EventToCommandBehavior
EventName="ItemTapped"
Command="{Binding MonkeyCommand}"
EventArgsParameterPath="Item"/>
</ListView.Behaviors>
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label
Grid.Row="0" Grid.Column="1"
HorizontalOptions="Start" VerticalOptions="End"
FontSize="24"
Text="{Binding Name}"/>
<Label
Grid.Row="1" Grid.Column="1"
HorizontalOptions="Start" VerticalOptions="Start"
FontSize="16"
Text="{Binding Location}"/>
<ffimageloading:CachedImage HeightRequest="80"
Grid.Row="0" Grid.RowSpan="2"
Aspect="AspectFit"
Source="{Binding ImageUrl}"
sharedTransitions:Transition.Name="MonkeyImage"
sharedTransitions:Transition.Group="{Binding Id}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</ContentPage>
在這裡因為會使用到 amarin.FFImageLoading.Forms & Xamarin.Plugin.SharedTransitions 功能,因此,加入了兩個命名空間 sharedTransitions & ffimageloading
而在 ContentPage 內,也使用到兩個附加屬性,用來宣告動畫需要多久的時間,這裡使用
sharedTransitions:SharedTransitionNavigationPage.TransitionDuration="500"
來宣告需要花費 500ms,並且使用 sharedTransitions:SharedTransitionNavigationPage.TransitionSelectedGroup="{Binding SelectedMonkeyId}"
用於宣告點選了哪個集合項目中的紀錄,若僅是做兩個頁面之間的切換,並沒有牽扯到集合項目,例如 ListView,這裡是可以不用使用這個附加屬性哦。
在 ItemsTemplate 內,需要使用
sharedTransitions:Transition.Name="MonkeyImage"
這樣的附加屬性宣告,指定兩個頁面都有的共同視覺項目,宣告這裡需要進行動畫的變化動作,而 sharedTransitions:Transition.Group="{Binding Id}"
則是同樣用於集合檢視內才會用到的宣告,這裡將會指定這筆紀錄的 ID 到 Group 屬性內,而這個屬性可以是整數或者是字串,不過,必須具有唯一值特性。
對 MainPage 的 ViewModel,將會使用底下的 C# 程式碼來設計
public class MainPageViewModel : INotifyPropertyChanged, INavigationAware
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly INavigationService navigationService;
public string Title { get; set; }
public ObservableCollection<Monkey> AllMonkey { get; set; } = new ObservableCollection<Monkey>();
public DelegateCommand<Monkey> MonkeyCommand { get; set; }
public int SelectedMonkeyId { get; set; }
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
Title = MonkeyData.Monkeys.Count.ToString();
MonkeyCommand = new DelegateCommand<Monkey>(async x =>
{
SelectedMonkeyId = x.Id;
NavigationParameters para = new NavigationParameters();
para.Add(nameof(Monkey), x);
await Task.Delay(100);
await navigationService.NavigateAsync(nameof(DetailPage), para);
});
}
public void OnNavigatedFrom(INavigationParameters parameters)
{
}
public void OnNavigatedTo(INavigationParameters parameters)
{
int idx = 1;
foreach (var item in MonkeyData.Monkeys)
{
AllMonkey.Add(item);
idx++;
}
}
public void OnNavigatingTo(INavigationParameters parameters)
{
}
}
對於點選 ListView 的某個紀錄,將會觸發 MonkeyCommand 這個命令,從這個命令參數中,可以取得使用者所點選的物件值,在這裡,將會把所點選的紀錄物件值,透過導航參數的鍵值 Monkey 傳送到下個頁面去
對於要切換過去的頁面,這裡新增一個 DetailPage.xaml 頁面與 DetailPageViewModel.cs 這兩個檔案,其中,對於頁面部分,將會設計底下的 XAML 宣告內容
從這裡可以很清楚的看到,僅有在圖片檢視的地方,使用到
sharedTransitions:Transition.Name="MonkeyImage"
宣告,這是用來串接兩個頁面共用檢視的宣告方式。<?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:prism="http://prismlibrary.com"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:sharedTransitions="clr-namespace:Plugin.SharedTransitions;assembly=Plugin.SharedTransitions"
x:Class="xfSharedTransitions.Views.DetailPage"
Title="{Binding Monkey.Name, StringFormat='猴子:{0}'}"
>
<Grid>
<ScrollView>
<StackLayout Orientation="Vertical">
<ffimageloading:CachedImage HeightRequest="300" WidthRequest="300"
Aspect="AspectFit"
Source="{Binding Monkey.ImageUrl}"
sharedTransitions:Transition.Name="MonkeyImage"
/>
<Label
HorizontalOptions="Center" VerticalOptions="End"
FontSize="24"
Text="{Binding Monkey.Name}"/>
<Label
HorizontalOptions="Center" VerticalOptions="Start"
FontSize="16"
Text="{Binding Monkey.Location}"/>
<Label
HorizontalOptions="Start" VerticalOptions="Start"
FontSize="14"
Text="{Binding Monkey.Details}"/>
</StackLayout>
</ScrollView>
</Grid>
</ContentPage>