我有一个Xamarin shell应用程序,在AppShell.xaml中定义了以下内容:
<TabBar Route="tabs" x:Name="tbTabs">
<Tab Title="Home"
Route="home"
Style="{StaticResource BaseStyle}"
Icon="home.png" >
<ShellContent>
<views:LandingPage />
</ShellContent>
</Tab>
<Tab Title="Services"
Route="service"
Style="{StaticResource BaseStyle}"
Icon="service.png">
<ShellContent>
<views:ServicesPage/>
</ShellContent>
</Tab>
</TabBar>
我的目标是通过单击主页上的图标转到列出应用程序接收到的通知历史记录的页面,从主页导航到外壳结构之外定义的页面。单击列表中的通知,将进入该通知的详细信息页面。
主页<->;通知历史记录<->;通知详细信息
未在AppShell.xaml中定义为shell一部分的页面的路由在AppShell_xaml.cs中定义为:
Routing.RegisterRoute("notificationhistorypage", typeof(NotificationHistoryPage));
Routing.RegisterRoute("notificationdetailspage", typeof(NotificationDetailsPage));
问题是,当我在通知历史页面上单击通知时,导航动画会在导航到详细信息页面之前显示导航回主页。当我从详细信息页面导航回来,而不是回到通知列表时,它会直接转到主页。
我已经尝试了很多方法来解决这个问题。所有这些似乎都表现出相同的行为,或者导致异常。
根据我在https://github.com/xamarin/Xamarin.Forms/issues/5790将路线定义为似乎更合乎逻辑
Routing.RegisterRoute("//tabs/home/notificationhistorypage", typeof(NotificationHistoryPage));
Routing.RegisterRoute("//tabs/home/notificationhistorypage/notificationdetailspage", typeof(NotificationDetailsPage));
不幸的是,这在运行时会产生一个异常:
"无法计算:/notificationhistorypage的路由\n参数名称:uri">
为了从主页导航到通知历史页面,我尝试了以下方法:
await Shell.Current.GoToAsync("/notificationhistorypage");
await Shell.Current.GoToAsync("//tabs/home/notificationhistorypage");
为了从通知历史页面导航到通知详细信息页面,我尝试了以下操作:
await Shell.Current.GoToAsync($"/notificationdetailspage?itemid={notification.ID}", true);
await Shell.Current.GoToAsync($"///tabs/home/notificationhistorypage/notificationdetailspage?itemid={notification.ID}", true);
await Shell.Current.Navigation.PushAsync(new NotificationDetailsPage(notification.ID.ToString()));
结果总是一样的。出于某种原因,在推送通知详细信息页面之前,导航历史记录页面总是从导航堆栈中弹出。我想做的事情是可能的吗?还是应该去掉shell,用旧的导航来完成这一切?
问题是,当我在通知历史页面上单击通知时,导航动画会显示导航回主页,然后再导航到详细信息页面。当我从详细信息页面导航回来,而不是回到通知列表时,它会直接转到主页。
AppShell.xaml:中的弹出项
<FlyoutItem FlyoutDisplayOptions="AsMultipleItems">
<ShellContent
Title="About"
ContentTemplate="{DataTemplate local:AboutPage}"
Icon="tab_about.png"
Route="AboutPage" />
<ShellContent
Title="Browse"
ContentTemplate="{DataTemplate local:ItemsPage}"
Icon="tab_feed.png" />
<ShellContent
Title="Main"
ContentTemplate="{DataTemplate local:MainPage}"
Icon="tab_feed.png" />
</FlyoutItem>
in the Shell子类构造函数中的Routing.RegisterRoute方法:
public AppShell()
{
InitializeComponent();
Routing.RegisterRoute("NotificationHistory", typeof(NotificationHistory));
Routing.RegisterRoute("NotificationDetails", typeof(NotificationDetails));
}
从MainPage导航到NotificationHistory页面。
private async void btn1_Clicked(object sender, EventArgs e)
{
await Shell.Current.GoToAsync("NotificationHistory");
}
从NotificationHistory导航到NotificationDetailspage。
async void OnItemSelected(Item item)
{
if (item == null)
return;
// This will push the ItemDetailPage onto the navigation stack
await Shell.Current.GoToAsync($"{nameof(ItemDetailPage)}?{nameof(ItemDetailViewModel.ItemId)}={item.Id}");
}
NotificationHistory页面:
<StackLayout>
<CollectionView
x:Name="ItemsListView"
ItemsSource="{Binding Items}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Padding="10" x:DataType="model:Item">
<Label
FontSize="16"
LineBreakMode="NoWrap"
Text="{Binding Text}" />
<Label
FontSize="13"
LineBreakMode="NoWrap"
Text="{Binding Description}" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type local:HistoryViewModel}}, Path=ItemTapped}"
CommandParameter="{Binding .}"
NumberOfTapsRequired="1" />
</StackLayout.GestureRecognizers>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
public partial class NotificationHistory : ContentPage
{
HistoryViewModel _viewModel;
public NotificationHistory()
{
InitializeComponent();
BindingContext = _viewModel = new HistoryViewModel();
}
}
public class HistoryViewModel : BaseViewModel
{
public ObservableCollection<Item> Items { get; set; }
public Command<Item> ItemTapped { get; }
public HistoryViewModel()
{
Title = "history";
Items = new ObservableCollection<Item>()
{
new Item { Id ="1" , Text = "First item", Description="This is an item description." },
new Item { Id = "2", Text = "Second item", Description="This is an item description." },
new Item { Id = "3", Text = "Third item", Description="This is an item description." },
new Item { Id = "4", Text = "Fourth item", Description="This is an item description." },
new Item { Id = "5", Text = "Fifth item", Description="This is an item description." },
new Item { Id = "6", Text = "Sixth item", Description="This is an item description." }
};
ItemTapped = new Command<Item>(OnItemSelected);
}
async void OnItemSelected(Item item)
{
if (item == null)
return;
// This will push the ItemDetailPage onto the navigation stack
await Shell.Current.GoToAsync($"{nameof(NotificationDetails)}?{nameof(DetailViewModel.ItemId)}={item.Id}");
}
}
通知详细信息页面:
<StackLayout Padding="15" Spacing="20">
<Label FontSize="Medium" Text="Text:" />
<Label FontSize="Small" Text="{Binding Text}" />
<Label FontSize="Medium" Text="Description:" />
<Label FontSize="Small" Text="{Binding Description}" />
</StackLayout>
public partial class NotificationDetails : ContentPage
{
public NotificationDetails()
{
InitializeComponent();
this.BindingContext =new DetailViewModel();
}
}
[QueryProperty(nameof(ItemId), nameof(ItemId))]
public class DetailViewModel : BaseViewModel
{
private string itemId;
private string text;
private string description;
public string Id { get; set; }
public string Text
{
get => text;
set => SetProperty(ref text, value);
}
public string Description
{
get => description;
set => SetProperty(ref description, value);
}
public string ItemId
{
get
{
return itemId;
}
set
{
itemId = value;
LoadItemId(value);
}
}
public async void LoadItemId(string itemId)
{
HistoryViewModel items = new HistoryViewModel();
try
{
var item = items.Items.Where(x => x.Id == itemId).FirstOrDefault();
if(item!=null)
{
Id = item.Id;
Text = item.Text;
Description = item.Description;
}
}
catch (Exception)
{
Debug.WriteLine("Failed to Load Item");
}
}
}
BaseViewModel是实现INotifyPropertyChanged 的类
public class BaseViewModel : INotifyPropertyChanged
{
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
不幸的是,这个问题是由我在AppShell.xaml.cs中的OnNavigation((中的一些代码引起的,当按下某些选项卡时,这些代码会弹出导航堆栈的所有内容。当args时,我没有意识到我需要阻止代码运行。Source==ShellNavigationSource.Push.我还应该提到,为了使我的代码修复工作,我不得不从4.8更新到Xamarin.Forms 5.0。