Skip to content

Commit ef69677

Browse files
authored
Enhance animation handling and transitions for Android (#13)
* Enhance animation handling and transitions for Android Updated `AnimationHelpers` to support custom animations and added methods for computing animation info. Modified `TransitionType` to include a `Custom` value and introduced `AndroidCustomAnimation` and `BaseCustomAnimation` records. Implemented new methods in `NavigationTrans` for managing custom transitions and added a `PropertyManager` for animation properties. Refactored `ShellTransItemRenderer` and `ShellTransRenderer` to improve fragment management and utilize new animation methods. Updated XAML in `AppShell.xaml` to use `SetAndroidTransitions` for custom animations and modified `NewPage2.xaml.cs` to apply the new transition setup. * Refactor NavigationTrans and PropertyManager classes - Removed unused `using System.Diagnostics.CodeAnalysis;` directive. - Changed `PropertyManager` from a static class to a static file class. - Fixed initialization of `properties` dictionary to use `new Dictionary<BindableObject, BaseCustomAnimation>()`. - Updated `Add` method to correctly store values in the `properties` dictionary. - `Get` method remains unchanged in functionality.
1 parent e61c53c commit ef69677

File tree

8 files changed

+168
-103
lines changed

8 files changed

+168
-103
lines changed

PJ.NavigationTrans.Maui/AnimationHelpers.android.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,50 @@ static partial class AnimationHelpers
2626
public static AnimationInfo ToPlatform(this TransitionType transition, double duration)
2727
{
2828
var animation = transition.ToPlatform();
29+
return CreateAnimation(duration, animation);
30+
}
31+
32+
public static AnimationInfo CreateAnimation(double duration, int animation)
33+
{
2934
var context = Platform.AppContext ?? throw new NullReferenceException();
35+
3036
var loadedAnimation = Android.Views.Animations.AnimationUtils.LoadAnimation(context, animation);
31-
Debug.Assert(loadedAnimation is not null);
37+
Assert(loadedAnimation is not null);
3238
loadedAnimation.Duration = (long)duration;
3339
return new(animation, loadedAnimation);
3440
}
41+
42+
public static (AnimationInfo animationIn, AnimationInfo animationOut) ComputeAnimationInfo(this BindableObject page, TransInfo? transinfo = null)
43+
{
44+
transinfo ??= GetInfo(page);
45+
46+
var info = transinfo.Value;
47+
48+
var duration = info.Duration;
49+
50+
var animationIn = GetAnimation(info.AnimationIn, duration, page);
51+
var animationOut = GetAnimation(info.AnimationOut, duration, page, false);
52+
53+
return (animationIn, animationOut);
54+
55+
56+
static AnimationInfo GetAnimation(TransitionType transition, double duration, BindableObject page, bool isIn = true)
57+
{
58+
if (transition != TransitionType.Custom)
59+
{
60+
return transition.ToPlatform(duration);
61+
}
62+
63+
var result = (AndroidCustomAnimation?)NavigationTrans.GetAndroidTransitions(page);
64+
65+
if (result is null)
66+
{
67+
return TransitionType.Default.ToPlatform(duration);
68+
}
69+
70+
return CreateAnimation(result.Duration, isIn ? result.AnimationIn : result.AnimationOut);
71+
}
72+
}
3573
}
3674

3775
// From .NET MAUI

PJ.NavigationTrans.Maui/Models.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ public enum TransitionType
1212
TopOut,
1313
BottomIn,
1414
BottomOut,
15+
Custom
1516
}
1617

1718
#if ANDROID
1819
readonly record struct AnimationInfo(int AnimationId, AAnimation Animation);
20+
record AndroidCustomAnimation(double Duration, int AnimationIn, int AnimationOut) : BaseCustomAnimation(Duration);
1921
#endif
2022

21-
record struct TransInfo(double Duration, TransitionType AnimationIn, TransitionType AnimationOut);
23+
record struct TransInfo(double Duration, TransitionType AnimationIn, TransitionType AnimationOut);
24+
25+
26+
public abstract record BaseCustomAnimation(double Duration);

PJ.NavigationTrans.Maui/NavigationTrans.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace PJ.NavigationTrans.Maui;
1+
using System.Runtime.CompilerServices;
2+
3+
namespace PJ.NavigationTrans.Maui;
24
public static class NavigationTrans
35
{
46
public static readonly BindableProperty DurationProperty =
@@ -20,4 +22,35 @@ public static class NavigationTrans
2022

2123
public static TransitionType GetTransitionOut(BindableObject view) => (TransitionType)view.GetValue(TransitionOutProperty);
2224
public static void SetTransitionOut(BindableObject view, TransitionType value) => view.SetValue(TransitionOutProperty, value);
25+
26+
#if ANDROID
27+
public static void SetAndroidTransitions(BindableObject view, int transitionIn, int transitionOut, double duration)
28+
{
29+
RegisterCustomTransitions(view);
30+
var value = new AndroidCustomAnimation(duration, transitionIn, transitionOut);
31+
PropertyManager.Add(view, value);
32+
}
33+
#endif
34+
35+
static void RegisterCustomTransitions(BindableObject view)
36+
{
37+
SetTransitionIn(view, TransitionType.Custom);
38+
SetTransitionOut(view, TransitionType.Custom);
39+
}
40+
41+
public static BaseCustomAnimation? GetAndroidTransitions(BindableObject view) => PropertyManager.Get(view);
42+
43+
}
44+
45+
static file class PropertyManager
46+
{
47+
static readonly Dictionary<BindableObject, BaseCustomAnimation> properties = [];
48+
49+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
50+
public static void Add(BindableObject key, BaseCustomAnimation value) =>
51+
properties[key] = value;
52+
53+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
54+
public static BaseCustomAnimation? Get(BindableObject key) =>
55+
properties.TryGetValue(key, out var value) ? value : null;
2356
}

PJ.NavigationTrans.Maui/Platforms/Android/Shell/ShellTransItemRenderer.android.cs

Lines changed: 80 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -25,64 +25,64 @@ protected override Task<bool> HandleFragmentUpdate(ShellNavigationSource navSour
2525
switch (navSource)
2626
{
2727
case ShellNavigationSource.Push:
28+
{
29+
fragmentMap.TryAdd(page, CreateFragmentForPage(page));
30+
if (!isForCurrentTab)
2831
{
29-
fragmentMap.TryAdd(page, CreateFragmentForPage(page));
30-
if (!isForCurrentTab)
31-
{
32-
return Task.FromResult(true);
33-
}
34-
break;
32+
return Task.FromResult(true);
3533
}
34+
break;
35+
}
3636
case ShellNavigationSource.Pop:
37+
{
38+
if (fragmentMap.TryGetValue(page, out var frag))
3739
{
38-
if (fragmentMap.TryGetValue(page, out var frag))
40+
if (!isForCurrentTab && ChildFragmentManager.Contains(frag.Fragment))
3941
{
40-
if (!isForCurrentTab && ChildFragmentManager.Contains(frag.Fragment))
41-
{
42-
RemoveFragment(frag.Fragment);
43-
}
44-
fragmentMap.Remove(page);
42+
RemoveFragment(frag.Fragment);
4543
}
44+
fragmentMap.Remove(page);
45+
}
4646

47-
if (!isForCurrentTab)
48-
{
49-
return Task.FromResult(true);
50-
}
51-
break;
47+
if (!isForCurrentTab)
48+
{
49+
return Task.FromResult(true);
5250
}
51+
break;
52+
}
5353
case ShellNavigationSource.PopToRoot:
54+
{
55+
RemoveAllPushedPages(shellSection, isForCurrentTab);
56+
if (!isForCurrentTab)
5457
{
55-
RemoveAllPushedPages(shellSection, isForCurrentTab);
56-
if (!isForCurrentTab)
57-
{
58-
return Task.FromResult(true);
59-
}
60-
break;
58+
return Task.FromResult(true);
6159
}
60+
break;
61+
}
6262
case ShellNavigationSource.Insert:
63+
{
64+
if (!isForCurrentTab)
6365
{
64-
if (!isForCurrentTab)
65-
{
66-
return Task.FromResult(true);
67-
}
68-
break;
66+
return Task.FromResult(true);
6967
}
68+
break;
69+
}
7070
case ShellNavigationSource.Remove:
71+
{
72+
if (fragmentMap.TryGetValue(page, out var frag))
7173
{
72-
if (fragmentMap.TryGetValue(page, out var frag))
73-
{
74-
if (!isForCurrentTab && frag != currentFragment && ChildFragmentManager.Contains(frag.Fragment))
75-
{
76-
RemoveFragment(frag.Fragment);
77-
}
78-
fragmentMap.Remove(page);
79-
}
80-
if (!isForCurrentTab)
74+
if (!isForCurrentTab && frag != currentFragment && ChildFragmentManager.Contains(frag.Fragment))
8175
{
82-
return Task.FromResult(true);
76+
RemoveFragment(frag.Fragment);
8377
}
84-
break;
78+
fragmentMap.Remove(page);
8579
}
80+
if (!isForCurrentTab)
81+
{
82+
return Task.FromResult(true);
83+
}
84+
break;
85+
}
8686
case ShellNavigationSource.ShellSectionChanged:
8787
break;
8888
default:
@@ -130,63 +130,63 @@ protected override Task<bool> HandleFragmentUpdate(ShellNavigationSource navSour
130130
switch (navSource)
131131
{
132132
case ShellNavigationSource.Push:
133-
{
134-
trackFragment = target;
133+
{
134+
trackFragment = target;
135135

136-
if (currentFragment is not null)
137-
{
138-
t.HideEx(currentFragment.Fragment);
139-
}
140-
141-
if (!ChildFragmentManager.Contains(target.Fragment))
142-
{
143-
t.AddEx(GetNavigationTarget().Id, target.Fragment);
144-
}
136+
if (currentFragment is not null)
137+
{
138+
t.HideEx(currentFragment.Fragment);
139+
}
145140

146-
t.ShowEx(target.Fragment);
147-
break;
141+
if (!ChildFragmentManager.Contains(target.Fragment))
142+
{
143+
t.AddEx(GetNavigationTarget().Id, target.Fragment);
148144
}
149145

146+
t.ShowEx(target.Fragment);
147+
break;
148+
}
149+
150150
case ShellNavigationSource.ShellSectionChanged:
151+
{
152+
if (currentFragment is not null)
151153
{
152-
if (currentFragment is not null)
153-
{
154-
t.HideEx(currentFragment.Fragment);
155-
}
154+
t.HideEx(currentFragment.Fragment);
155+
}
156156

157-
if (!ChildFragmentManager.Contains(target.Fragment))
158-
{
159-
t.AddEx(GetNavigationTarget().Id, target.Fragment);
160-
}
157+
if (!ChildFragmentManager.Contains(target.Fragment))
158+
{
159+
t.AddEx(GetNavigationTarget().Id, target.Fragment);
160+
}
161161

162-
t.ShowEx(target.Fragment);
162+
t.ShowEx(target.Fragment);
163163

164-
break;
165-
}
164+
break;
165+
}
166166

167167
case ShellNavigationSource.Pop:
168168
case ShellNavigationSource.PopToRoot:
169169
case ShellNavigationSource.Remove:
170-
{
171-
isNavBack = true;
172-
trackFragment = currentFragment;
170+
{
171+
isNavBack = true;
172+
trackFragment = currentFragment;
173173

174-
if (currentFragment is not null)
175-
{
176-
// Do not use Remove here,
177-
// or the Fragment will be destroyed before the animation completes
178-
t.HideEx(currentFragment.Fragment);
179-
}
174+
if (currentFragment is not null)
175+
{
176+
// Do not use Remove here,
177+
// or the Fragment will be destroyed before the animation completes
178+
t.HideEx(currentFragment.Fragment);
179+
}
180180

181-
if (!ChildFragmentManager.Contains(target.Fragment))
182-
{
183-
t.AddEx(GetNavigationTarget().Id, target.Fragment);
184-
}
181+
if (!ChildFragmentManager.Contains(target.Fragment))
182+
{
183+
t.AddEx(GetNavigationTarget().Id, target.Fragment);
184+
}
185185

186-
t.ShowEx(target.Fragment);
186+
t.ShowEx(target.Fragment);
187187

188-
break;
189-
}
188+
break;
189+
}
190190
}
191191
t.CommitAllowingStateLossEx();
192192

@@ -227,12 +227,7 @@ void CallBack(object? s, EventArgs e)
227227

228228
static void SetupAnimationImpl(FragmentTransaction t, Page page, Fragment? destination, Fragment? originFragment)
229229
{
230-
var info = AnimationHelpers.GetInfo(page);
231-
232-
var duration = info.Duration;
233-
234-
var animationIn = info.AnimationIn.ToPlatform(duration);
235-
var animationOut = info.AnimationOut.ToPlatform(duration);
230+
var (animationIn, animationOut) = page.ComputeAnimationInfo();
236231

237232
if (destination is not null)
238233
{

PJ.NavigationTrans.Maui/Platforms/Android/Shell/ShellTransRenderer.android.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ namespace PJ.NavigationTrans.Maui;
77
public partial class ShellTransRenderer : ShellRenderer
88
{
99
IShellItemRenderer? __currentView;
10-
public ShellTransRenderer()
11-
{
12-
}
1310

1411
protected override IShellItemRenderer CreateShellItemRenderer(ShellItem shellItem)
1512
{
@@ -33,10 +30,7 @@ protected override void SwitchFragment(FragmentManager manager, Android.Views.Vi
3330
return;
3431
}
3532

36-
var duration = info.Duration;
37-
38-
var animationIn = info.AnimationIn.ToPlatform(duration);
39-
var animationOut = info.AnimationOut.ToPlatform(duration);
33+
var (animationIn, animationOut) = shellContent.ComputeAnimationInfo(info);
4034

4135
var fragmentTransaction = manager.BeginTransaction();
4236

PJ.NavigationTrans.Sample/AppShell.xaml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@
2222
Route="NewPage1" />
2323

2424
<ShellContent
25+
x:Name="content"
2526
Title="Home"
26-
trans:NavigationTrans.Duration="1000"
27-
trans:NavigationTrans.TransitionIn="BottomIn"
28-
trans:NavigationTrans.TransitionOut="TopOut"
2927
ContentTemplate="{DataTemplate local:NewPage2}"
3028
Route="NewPage2" />
3129

PJ.NavigationTrans.Sample/AppShell.xaml.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,9 @@ public AppShell()
88

99
Routing.RegisterRoute(nameof(NewPage1), typeof(NewPage1));
1010
Routing.RegisterRoute(nameof(NewPage2), typeof(NewPage2));
11+
12+
#if ANDROID
13+
Maui.NavigationTrans.SetAndroidTransitions(this.content, Resource.Animation.flip_in, Resource.Animation.scale_out, 1500);
14+
#endif
1115
}
1216
}

PJ.NavigationTrans.Sample/NewPage2.xaml.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using PJ.NavigationTrans.Maui;
2-
31
namespace PJ.NavigationTrans.Sample;
42

53
public partial class NewPage2 : ContentPage
@@ -8,9 +6,9 @@ public NewPage2()
86
{
97
InitializeComponent();
108

11-
//ShellTrans.SetTransitionIn(this, TransitionType.ScaleIn);
12-
//ShellTrans.SetTransitionOut(this, TransitionType.FlipOut);
13-
//ShellTrans.SetDuration(this, 2000);
9+
#if ANDROID
10+
Maui.NavigationTrans.SetAndroidTransitions(this, Resource.Animation.scale_in, Resource.Animation.flip_out, 1500);
11+
#endif
1412

1513
var tap = new TapGestureRecognizer();
1614
tap.Tapped += (_, __) =>

0 commit comments

Comments
 (0)