在Avalonia应用开发中,默认的导航机制往往只能满足简单的页面跳转需求,当业务场景需要携带复杂参数、控制页面缓存、实现嵌套路由或者自定义跳转拦截逻辑时,就需要开发者自行封装自定义路由和导航体系。

自定义导航的核心思路
自定义路由和导航逻辑的核心是解耦页面跳转逻辑与具体页面实例,通过统一的导航服务管理路由映射和跳转行为。整体可以分为三个部分:路由表定义、导航服务封装、页面生命周期管理。
1. 定义路由表结构
首先需要定义一个路由表,用来存储路由路径和对应页面类型的映射关系,同时可以扩展路由的元数据,比如是否需要登录、是否缓存等。
// 路由元数据类,可扩展更多属性
public class RouteMetadata
{
// 路由对应的页面类型
public Type PageType { get; set; }
// 是否需要登录才能访问
public bool RequireAuth { get; set; }
// 是否缓存页面实例
public bool CachePage { get; set; }
}
// 路由表管理类
public class RouteTable
{
private readonly Dictionary<string, RouteMetadata> _routes = new();
// 注册路由
public void Register(string path, RouteMetadata metadata)
{
if (_routes.ContainsKey(path))
{
throw new InvalidOperationException($"路由 {path} 已存在");
}
_routes[path] = metadata;
}
// 根据路径获取路由元数据
public RouteMetadata GetRoute(string path)
{
if (_routes.TryGetValue(path, out var metadata))
{
return metadata;
}
throw new KeyNotFoundException($"未找到路径为 {path} 的路由");
}
}
2. 封装导航服务
导航服务负责处理跳转请求,包括参数传递、页面实例化、缓存管理以及跳转拦截逻辑。我们可以将导航服务设计为单例,方便全局调用。
public class NavigationService
{
private static NavigationService _instance;
public static NavigationService Instance => _instance ??= new NavigationService();
private readonly RouteTable _routeTable = new();
// 页面缓存字典
private readonly Dictionary<string, Control> _pageCache = new();
// 当前显示的页面容器,通常是Window的内容区域或者ContentControl
public ContentControl? Host { get; set; }
// 初始化路由表,注册所有页面路由
public void InitRoutes()
{
// 注册首页路由,不缓存
_routeTable.Register("/", new RouteMetadata
{
PageType = typeof(HomePage),
RequireAuth = false,
CachePage = false
});
// 注册详情页路由,缓存页面
_routeTable.Register("/detail", new RouteMetadata
{
PageType = typeof(DetailPage),
RequireAuth = true,
CachePage = true
});
}
// 导航方法,支持传递参数
public void NavigateTo(string path, object? parameter = null)
{
if (Host == null)
{
throw new InvalidOperationException("未设置导航宿主容器");
}
var route = _routeTable.GetRoute(path);
// 如果需要登录,这里可以添加登录检查逻辑
if (route.RequireAuth)
{
// 模拟登录检查,实际项目替换为真实逻辑
bool isLogin = CheckLogin();
if (!isLogin)
{
NavigateTo("/login");
return;
}
}
Control page;
// 处理页面缓存
if (route.CachePage && _pageCache.ContainsKey(path))
{
page = _pageCache[path];
}
else
{
// 实例化页面,传递参数
page = (Control)Activator.CreateInstance(route.PageType)!;
if (page is INavigable navigable)
{
navigable.OnNavigatedTo(parameter);
}
if (route.CachePage)
{
_pageCache[path] = page;
}
}
// 触发旧页面的离开事件
if (Host.Content is INavigable oldNavigable)
{
oldNavigable.OnNavigatedFrom();
}
Host.Content = page;
}
// 模拟登录检查方法
private bool CheckLogin()
{
// 实际项目中替换为真实的登录状态判断逻辑
return true;
}
}
// 可导航页面接口,定义生命周期方法
public interface INavigable
{
// 进入页面时调用,可接收参数
void OnNavigatedTo(object? parameter);
// 离开页面时调用
void OnNavigatedFrom();
}
3. 页面实现导航接口
需要支持自定义导航的页面实现INavigable接口,就可以接收导航参数和处理生命周期事件。
public partial class DetailPage : UserControl, INavigable
{
public DetailPage()
{
InitializeComponent();
}
// 接收导航传递的参数
public void OnNavigatedTo(object? parameter)
{
if (parameter is int id)
{
// 根据id加载详情数据
LoadDetailData(id);
}
}
// 离开页面时处理资源释放等逻辑
public void OnNavigatedFrom()
{
// 停止数据加载、取消订阅事件等
}
private void LoadDetailData(int id)
{
// 加载数据的具体逻辑
}
}
使用示例
在应用启动时初始化导航服务,绑定宿主容器,之后就可以在任意位置调用导航方法实现跳转。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 设置导航宿主为窗口的内容区域
NavigationService.Instance.Host = this.MainContent;
// 初始化路由表
NavigationService.Instance.InitRoutes();
// 默认导航到首页
NavigationService.Instance.NavigateTo("/");
}
}
// 在某个按钮的点击事件中触发跳转,传递参数
private void OnDetailButtonClick(object sender, RoutedEventArgs e)
{
// 跳转到详情页,传递商品id 123
NavigationService.Instance.NavigateTo("/detail", 123);
}
注意事项
- 页面缓存需要合理管理,避免占用过多内存,可以在合适的时机清除不需要的缓存页面。
- 路由路径的命名建议保持统一规范,比如采用类似RESTful的风格,方便维护。
- 如果应用有多个嵌套的导航区域,可以扩展导航服务支持多个宿主容器的管理。
- 跳转拦截逻辑可以根据业务需求扩展,比如添加权限校验、数据未保存提醒等功能。