Xamarin navigation without animation
I have an application where I want to show page A from which the user can navigate to page B or C, from B to A or C, and from C only to A, even if the user when B to get to C
Currently when I do a transition B-> C, first PopAsync
to go back to A and then I do PushAsync
to go to C, so '
The question is, is there a civilized way to customize this navigation scheme while still relying on inline navigation to keep track of the navigation stack - I don't want to do that myself and use it PushModalAsync
.
Note that (as shown in the image) A and C are not the endpoints of the entire navigation stack, there are pages before A and after C, so the stack needs to be kept.
source to share
IOS NavigationRenderer
has virtual methods OnPopViewAsync
and OnPushAsync
(similar to Android ):
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
return base.OnPopViewAsync(page, animated);
}
protected override Task<bool> OnPushAsync(Page page, bool animated)
{
return base.OnPushAsync(page, animated);
}
They call the corresponding base method two arguments, a page, and animate the transition. Thus, you can enable or disable animations using the following approach:
- Display a custom navigation page.
- Add the property "Animated".
- Output a custom navigation renderer for your custom navigation page.
- Override the pop and push methods, naming their base methods with the Animated property.
Note that I have not tried this approach yet as it is a pretty definite job. But disabling animation on all navigation pages did work that way.
Edit: It took me a few hours to actually implement my solution for my own project. Therefore, I will share more details. (I developed and tested on Xamarin.Forms 1.2.3-pre4.)
Custom navigation page
Apart from the above property, Animated
my nav page reimplements the two navigation functions and adds an optional argument Animated
which defaults to true
. This way we can keep all of the existing code and only add false
where needed.
Also, both methods will sleep for a very short time (10ms) after clicking / popping up the page. Without this delay, we had problems with sequential calls.
public class CustomNavigationPage: NavigationPage
{
public bool Animated { get; private set; }
public CustomNavigationPage(Page page) : base(page)
{
}
// Analysis disable once MethodOverloadWithOptionalParameter
public async Task PushAsync(Page page, bool animated = true)
{
Animated = animated;
await base.PushAsync(page);
await Task.Run(delegate {
Thread.Sleep(10);
});
}
// Analysis disable once MethodOverloadWithOptionalParameter
public async Task<Page> PopAsync(bool animated = true)
{
Animated = animated;
var task = await base.PopAsync();
await Task.Run(delegate {
Thread.Sleep(10);
});
return task;
}
}
Custom navigation rendering
The renderer for my custom navigation page overrides both navigation methods and passes the property to Animated
their base methods. (It's kind of ugly to introduce a flag this way, but I couldn't find a better solution.)
public class CustomNavigationRenderer: NavigationRenderer
{
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
return base.OnPopViewAsync(page, (Element as CustomNavigationPage).Animated);
}
protected override Task<bool> OnPushAsync(Page page, bool animated)
{
return base.OnPushAsync(page, (Element as CustomNavigationPage).Animated);
}
}
This is for iOS. But on Android it is almost identical.
Sample application
To demonstrate the capabilities of successive clicking and page appearing, I wrote the following application.
The class App
just creates a new DemoPage
one wrapped in CustomNavigationPage
. Please note that this instance must be publicly available for this example.
public static class App
{
public static CustomNavigationPage NavigationPage;
public static Page GetMainPage()
{
return NavigationPage = new CustomNavigationPage(new DemoPage("Root"));
}
}
The demo page contains multiple buttons that click and pop pages in different orders. You can add or remove a parameter false
for each call to PushAsync
or PopAsync
.
public class DemoPage: ContentPage
{
public DemoPage(string title)
{
Title = title;
Content = new StackLayout {
Children = {
new Button {
Text = "Push",
Command = new Command(o => App.NavigationPage.PushAsync(new DemoPage("Pushed"))),
},
new Button {
Text = "Pop",
Command = new Command(o => App.NavigationPage.PopAsync()),
},
new Button {
Text = "Push + Pop",
Command = new Command(async o => {
await App.NavigationPage.PushAsync(new DemoPage("Pushed (will pop immediately)"));
await App.NavigationPage.PopAsync();
}),
},
new Button {
Text = "Pop + Push",
Command = new Command(async o => {
await App.NavigationPage.PopAsync(false);
await App.NavigationPage.PushAsync(new DemoPage("Popped and pushed immediately"));
}),
},
new Button {
Text = "Push twice",
Command = new Command(async o => {
await App.NavigationPage.PushAsync(new DemoPage("Pushed (1/2)"), false);
await App.NavigationPage.PushAsync(new DemoPage("Pushed (2/2)"));
}),
},
new Button {
Text = "Pop twice",
Command = new Command(async o => {
await App.NavigationPage.PopAsync(false);
await App.NavigationPage.PopAsync();
}),
},
},
};
}
}
Important hint: . It took me hours of debugging to find out that you need to use an instance NavigationPage
(or derivative) and not ContentPage
Navigation
! Otherwise, immediately invoking two or more taps or taps results in strange behavior and crashes.
source to share
Collected here are the snippets I whipped up along with some other niceties to improve the NaviagationPage for iOS. Link to comment and code on the xamarin forums.
source to share
What I would do if I did this, press the "C" button on your navigation stack, and then pop page B from the stack. This way, when you leave page C, you will go to page A.
// Push the page you want to go to on top of the stack.
await NavigationPage.PushAsync(new CPage()));
// Remove page B from the stack, so when you want to go back next time
//you will go to page A.
Navigation.RemovePage(Navigation.NavigationStack[Navigation.NavigationStack.Count - 2] );
Alternatively, when you even leave page C, you can remove all instances of type B from the stack and then discard 1. In this case, page B will remain on the stack until you navigate back from page C to page A.
source to share