【Win10】页面导航的实现

注:本文基于 Windows 10 10240 及其 SDK 编写,若以后有变化,请以新版本为准。

页面导航我们是再熟悉不过了,浏览器、手机 App 大多都使用这种方式来展示内容。在 Windows 10 应用商店应用当中,也是使用这种方式来展示内容。具体是通过 Frame 这个控件来进行导航展示。

在 App.xaml.cs 文件中,我们可以看到创建了一个 Frame:

并且在下面,使用 Navigate 方法导航到 App 的主页 MainPage。

导航到某个页面使用的就是 Navigate 方法,有三个重载,其实这个也没什么好说的,看最复杂的,有三个参数的那个重载:

第一个参数是指需要导航到哪个页面,第二个是需要传递的参数,第三个是指使用哪个过渡效果来导航到目标页面。

一般我们用得最多的还是第二个和第一个重载。第三个是很少用的。

关于导航到某个页面,就简单说到这里,因为这并不是本文的重点。

接下来开始说本文的重点,导航的后退与前进,特别是后退。

如果说怎样执行后退与前进的话,那代码很简单:

if (Frame.CanGoBack)
{
    Frame.GoBack();
}

if (Frame.CanGoForward)
{
    Frame.GoForward();
}

判断好是否能后退/前进之后执行就可以了。另外也可以再加多一些条件判断,例如是否正在登录之类的,这些需要看具体的业务逻辑判断。

但是,关键的问题来了,App 中你总得提供相应的 UI 入口来执行相应的功能吧。这就好比你写了一个框架、类库,总得有公开的方法给别人来调用。那么,在 Windows 10 应用商店应用当中,应该如何实现这个后退/前进的入口呢?接下来将探讨一下。

一、设备上的后退键

一直做 Windows Phone Runtime App 开发的都会再熟悉不过了。我们是可以在 App 中捕捉到设备上的后退键被按下的。在 UWP 中,后退键被定义了在 Mobile Extensions 里,因此使用之前必须先添加引用:

但是,由于不是每台 Windows 10 设备上都存在后退键,因此必须先使用 ApiInformation 类做功能检查。

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    if (ApiInformation.IsEventPresent("Windows.Phone.UI.Input.HardwareButtons", "BackPressed"))
    {
        HardwareButtons.BackPressed += HardwareButtons_BackPressed;
    }
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    if (ApiInformation.IsEventPresent("Windows.Phone.UI.Input.HardwareButtons", "BackPressed"))
    {
        HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
    }
}

private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
    // 标记已处理该事件。
    e.Handled = true;
    if (Frame.CanGoBack)
    {
        Frame.GoBack();
    }
}

这个是普遍最适用的写法,在 HardwareButtons_BackPressed 方法中,我把 e.Handled 设置为 true,这是因为 BackPressed 是一个路由事件,如果不设置的话,其它注册了事件处理方法也会收到通知,这是我们不希望的。然后我们可以判断是否能够后退,这里可以结合该页面的特征来追加判断逻辑,因此我说这是普遍最适用的写法。页面离开的话我们就注销掉,因为每个页面后退的逻辑并不一定相同。到新的页面就再重新注册。这种方式麻烦就在于每个页面都要写这么一段代码,但是这是最通用的写法。

如果我们的页面后退总是不需要考虑当前页面逻辑的话,我们可以在 App.xaml.cs 中,创建 Frame 时就立刻注册一个按下逻辑:

那么如果当前页面能够后退的话,就后退,如果不能的话,那么什么也不做,让后退键按下事件继续传递。这种写法代码量比上面的通用的少很多,不需要在每个页面导航进入和离开时订阅/注销后退键事件,但是没办法根据页面逻辑来判断是否能够后退。因此各有优劣,大家可以视乎 App 的需求来进行选择。

二、窗口左上角的后退键

但是在 Desktop 下,是没有物理设备的后退键的,那么难道在 App 里面单独做一个后退按钮吗?这是一个相当不优雅的解决方案。因此微软在 Windows 10 中提供了一个新的 API,可以在窗口的左上角显示一个后退按钮。

SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;

调用这么一行代码之后,我们的 App 的左上角就会看见一个后退按钮。当然,这个按钮只有在窗口模式下才显示,mobile 端是看不见的。

那么我们的页面代码可以这么写:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    SystemNavigationManager navigationManager = SystemNavigationManager.GetForCurrentView();
    navigationManager.AppViewBackButtonVisibility = Frame.CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
    navigationManager.BackRequested += NavigationManager_BackRequested;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    SystemNavigationManager.GetForCurrentView().BackRequested -= NavigationManager_BackRequested;
}

private void NavigationManager_BackRequested(object sender, BackRequestedEventArgs e)
{
    e.Handled = true;
    if (Frame.CanGoBack)
    {
        Frame.GoBack();
    }
}

在进入到页面的时候,我们先判断当前页面是否能后退,如果可以的话,就显示,否则就不显示。接下来订阅事件,与设备后退键按下事件类似,我们需要将 e.Handled 设置为 true,以阻止事件继续传递。然后后退之前,确保确实能后退,我们再后退。

如果类似设备后退键不需要考虑当前页面逻辑的话,可以在 App.xaml.cs 中这么写:

只要发生了导航就判断是否能够后退,从而是否显示后退键。接下来订阅事件和上面后退键的类似,不再细说。

三、鼠标侧键前进和后退

如果你用的是 5 键鼠标的话,那么你会发现,使用浏览器的时候是能够按那两个侧键来前进和后退的。那么,在 UWP 里又应该如何实现这种功能呢?

在 UWP 中,每个窗口都对应着一个 CoreWindow 对象,这个对象上能够监听许多输入事件,包括鼠标按下和释放。那么我们就可以编写了:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    CoreWindow.GetForCurrentThread().PointerReleased += Page_PointerReleased;
}

protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    base.OnNavigatedFrom(e);

    CoreWindow.GetForCurrentThread().PointerReleased -= Page_PointerReleased;
}

private void Page_PointerReleased(CoreWindow sender, PointerEventArgs args)
{
    args.Handled = true;
    switch (args.CurrentPoint.Properties.PointerUpdateKind)
    {
        case PointerUpdateKind.XButton1Released:// 鼠标后退键。
            if (Frame.CanGoBack)
            {
                Frame.GoBack();
            }
            break;

        case PointerUpdateKind.XButton2Released:// 鼠标前进键。
            if (Frame.CanGoForward)
            {
                Frame.GoForward();
            }
            break;

在页面进入和页面离开分别订阅/注销指针释放事件。在 UWP 中,鼠标、手指触摸、触控笔这类输入设备都归类为 Pointer。

接下来,在事件订阅方法中,从事件参数中先获取当前点击的点,然后再获取相关信息,最后判断是怎样触发的。

鼠标侧键后退键叫 XButton1,前进键叫 XButton2。当然有的鼠标可能这两个键是反转的,但是由于我们这里获取的是虚拟键,而不是实际键,因此并不需要进行特别处理。

在 App.xaml.cs 全局处理的代码类似,这里就不再多说。

不过这种方法目前有一个缺点,就是在 WebView 控件中,这个事件是不会触发的,因为 WebView 自身吞掉了这个事件,事件没法冒泡到 CoreWindow 上。而且 JavaScript 没有方法捕捉鼠标侧键事件,因此不得不说这是一个遗憾。

四、触摸手势前进和后退

不知道还在阅读本文的你是否用过 WP 8.1 上的 IT 之家客户端。它那个手势滑动前进后退我觉得是做得相当不错的。

要做这个效果,我们必须使用到手势识别,但是这个逻辑如何来写呢?难道得我们自己来写判断逻辑?答案是不需要的,在 UWP 中,我们使用 GestureRecognizer 类来做手势识别。

所谓的手势识别,无非就是计算一堆点,GestureRecognizer 这个类也是同理,我们需要调用方法来提供数据,也就是把触摸的点给它。

如果水平滑动发生的话,那么将会触发 CrossSliding 事件。

但是,GestureRecognizer 仅仅是告诉你滑动发生了,而从左往右滑还是从右往左滑这点是不会告诉你的。因此我们还需要记录开始点和结束点的坐标来做判断。改写下代码:

public sealed partial class MainPage : Page
    {
        private readonly GestureRecognizer _recognizer;

        private PointerPoint _startPoint;

        private PointerPoint _endPoint;

        public MainPage()
        {
            this.InitializeComponent();
            _recognizer = new GestureRecognizer()
            {
                // 识别滑动手势。
                GestureSettings = GestureSettings.CrossSlide,
                // 识别水平滑动手势。
                CrossSlideHorizontally = true
            };
            _recognizer.CrossSliding += GestureRecognizer_CrossSliding;
        }

        protected override void OnPointerPressed(PointerRoutedEventArgs e)
        {
            base.OnPointerPressed(e);

            PointerPoint point = e.GetCurrentPoint(null);
            _startPoint = point;
            _recognizer.ProcessDownEvent(point);
        }

        protected override void OnPointerMoved(PointerRoutedEventArgs e)
        {
            base.OnPointerMoved(e);

            _recognizer.ProcessMoveEvents(e.GetIntermediatePoints(null));
        }

        protected override void OnPointerReleased(PointerRoutedEventArgs e)
        {
            base.OnPointerReleased(e);

            PointerPoint point = e.GetCurrentPoint(null);
            _endPoint = point;
            _recognizer.ProcessUpEvent(point);
        }

        private void GestureRecognizer_CrossSliding(GestureRecognizer sender, CrossSlidingEventArgs args)
        {
            if (args.CrossSlidingState == CrossSlidingState.Completed && (args.PointerDeviceType == PointerDeviceType.Pen || args.PointerDeviceType == PointerDeviceType.Touch))// 鼠标就不处理了。
            {
                if (_startPoint != null && _endPoint != null)// 确保访问 Position 不会异常。
                {
                    double startX = _startPoint.Position.X;
                    double endX = _endPoint.Position.X;
                    if (startX < endX)
                    {
                        // 从左往右,后退。
                        if (Frame.CanGoBack)
                        {
                            Frame.GoBack();
                        }
                    }
                    else if (startX > endX)
                    {
                        // 从右往左,前进。
                        if (Frame.CanGoForward)
                        {
                            Frame.GoForward();
                        }
                    }
                }
            }
        }

那么这样的话就能够实现类似 IT 之家那种滑动前进和后退了。

如果是打算写到 App.xaml.cs 的话,那么获取点的方式需要稍微修改一下:

/// <summary>
    /// 提供特定于应用程序的行为,以补充默认的应用程序类。
    /// </summary>
    sealed partial class App : Application
    {
        private readonly GestureRecognizer _recognizer;

        private PointerPoint _startPoint;

        private PointerPoint _endPoint;

        private CoreWindow _window;

        /// <summary>
        /// 初始化单一实例应用程序对象。这是执行的创作代码的第一行,
        /// 已执行,逻辑上等同于 main() 或 WinMain()。
        /// </summary>
        public App()
        {
            this.InitializeComponent();
            this.Suspending += OnSuspending;

            _recognizer = new GestureRecognizer()
            {
                GestureSettings = GestureSettings.CrossSlide,
                CrossSlideHorizontally = true
            };
            _recognizer.CrossSliding += GestureRecognizer_CrossSliding;
        }

        /// <summary>
        /// 在应用程序由最终用户正常启动时进行调用。
        /// 将在启动应用程序以打开特定文件等情况下使用。
        /// </summary>
        /// <param name="e">有关启动请求和过程的详细信息。</param>
        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
#if DEBUG
            if (System.Diagnostics.Debugger.IsAttached)
            {
                this.DebugSettings.EnableFrameRateCounter = true;
            }
#endif

            Frame rootFrame = Window.Current.Content as Frame;

            // 不要在窗口已包含内容时重复应用程序初始化,
            // 只需确保窗口处于活动状态
            if (rootFrame == null)
            {
                // 创建要充当导航上下文的框架,并导航到第一页
                rootFrame = new Frame();

                rootFrame.NavigationFailed += OnNavigationFailed;

                if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
                {
                    //TODO: 从之前挂起的应用程序加载状态
                }

                // 将框架放在当前窗口中
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                // 当导航堆栈尚未还原时,导航到第一页,
                // 并通过将所需信息作为导航参数传入来配置
                // 参数
                rootFrame.Navigate(typeof(MainPage), e.Arguments);
            }
            // 确保当前窗口处于活动状态
            Window.Current.Activate();

            if (_window == null)
            {
                _window = CoreWindow.GetForCurrentThread();
                _window.PointerPressed += Window_PointerPressed;
                _window.PointerMoved += Window_PointerMoved;
                _window.PointerReleased += Window_PointerReleased;
            }
        }

        private void Window_PointerPressed(CoreWindow sender, PointerEventArgs args)
        {
            PointerPoint point = args.CurrentPoint;
            _startPoint = point;
            _recognizer.ProcessDownEvent(point);
        }

        private void Window_PointerMoved(CoreWindow sender, PointerEventArgs args)
        {
            _recognizer.ProcessMoveEvents(args.GetIntermediatePoints());
        }

        private void Window_PointerReleased(CoreWindow sender, PointerEventArgs args)
        {
            PointerPoint point = args.CurrentPoint;
            _endPoint = point;
            _recognizer.ProcessUpEvent(point);
        }

        private void GestureRecognizer_CrossSliding(GestureRecognizer sender, CrossSlidingEventArgs args)
        {
            Frame rootFrame = Window.Current.Content as Frame;
            if (rootFrame == null)
            {
                return;
            }

            if (args.CrossSlidingState == CrossSlidingState.Completed && (args.PointerDeviceType == PointerDeviceType.Pen || args.PointerDeviceType == PointerDeviceType.Touch))
            {
                if (_startPoint != null && _endPoint != null)
                {
                    double startX = _startPoint.Position.X;
                    double endX = _endPoint.Position.X;
                    if (startX < endX)
                    {
                        if (rootFrame.CanGoBack)
                        {
                            rootFrame.GoBack();
                        }
                    }
                    else if (startX > endX)
                    {
                        if (rootFrame.CanGoForward)
                        {
                            rootFrame.GoForward();
                        }
                    }
                }
            }
        }

改成从 CoreWindow 上获取。而 CoreWindow 则在 Launched 方法最后初始化,因为 GetForCurrentThread 方法在 App 构造函数中调用是会返回 null 的。接下来剩下的代码就差不多一样了。

与第三点同样的是,由于 WebView 吞掉事件的原因,这种方法在 WebView 里也是没法触发的。但是与鼠标侧键不同的是,JavaScript 是能够捕捉到主要输入设备按下、移动、释放这三个关键的事件的,因此我们可以使用 JavaScript 来捕捉 WebView 里面的手势,然后通知 App,再执行前进或者后退。

这里我使用的 JS 手势库是 Hammer.js,官网是:http://hammerjs.github.io/

编写 Html 模板:

<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
    <title></title>
    <!--引用 HammerJS-->
    <script src="../Scripts/hammer.min.js" type="text/javascript"></script>
</head>
<body>
    这里放内容。
    <script type="text/javascript">
        // 初始化监听对象,并监听 html。
        var hammertime = new Hammer(document.getElementsByTagName("html")[0]);
        hammertime.on("swipeleft", function (e) {
            if (e.pointerType !== "mouse") {
                // 左滑,前进。
                window.external.notify("goforward");
            }
        });
        hammertime.on("swiperight", function (e) {
            if (e.pointerType !== "mouse") {
                // 右滑,后退。
                window.external.notify("goback");
            }
        });
    </script>
</body>
</html>

放内容这里可以通过 WebView.InvokeScriptAsync 之类的方法注入内容。而 JavaScript 通知 WebView 则需要使用 window.external.notify 方法,参数是一个字符串,调用后 WebView 的 ScriptNotify 事件将会触发,并且会得到 JavaScript 传递过来的参数。在上面我们使用 e.PointerType 来做判断输入设备,同样的,鼠标的话,不管它。

然后 cs 文件中获取到 JavaScript 传递过来的参数就执行相应的前进后退。

private void WebView_ScriptNotify(object sender, NotifyEventArgs e)
        {
            string msg = e.Value;
            Debug.WriteLine(msg);
            switch (msg)
            {
                case "goforward":
                    if (Frame.CanGoForward)
                    {
                        Frame.GoForward();
                    }
                    break;

                case "goback":
                    if (Frame.CanGoBack)
                    {
                        Frame.GoBack();
                    }
                    break;
            }
        }

那么对于手势前进、后退的话,WebView 也能够得到支持了。

关于手势前进、后退,具体可以参照这个渣渣的 Demo:GestureRecognizerNavigate.zip

鼠标用户记得请使用模拟器来测试!!!

结语

以上四种方式算是目前比较常见的导航前进、后退的交互实现方式,当然在 App 中你可以四种一起来用,也可以只用其中的一两种,这个需要结合具体的业务需求来考虑。当然了,你也可以脱离这四种方式,例如利用加速度传感器,向左甩机器就后退,向右甩就前进,至于实用性嘛,见仁见智了。最后,希望大家能够有所得益,编写出令用户满意的 App。

时间: 2024-10-10 09:10:18

【Win10】页面导航的实现的相关文章

Win10系列:JavaScript页面导航

页面导航是在开发应用的过程中使用频率较高的技术,其中比较常用的导航方式有多页导航和页内导航,采用多页导航方式的应用程序包含一系列的页面,在一个页面中加入另一个页面的链接地址后,单击链接将跳转到指定页面,从而实现页面之间的导航.而页内导航方式是在一个页面内根据需要加载其他页面,使用页内导航方式的应用程序仍然包含一系列的页面,不同的是,这些页面是顺序被加载到一个选定的页面区域中,而不是从一个页面跳转到另一个页面.在传统的网站开发中,通常使用多页导航的方式,在开发基于JavaScript的Window

【REACT NATIVE 系列教程之五】NAVIGATOR(页面导航)的基本使用与传参

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2248.html 今天介绍一种应用开发中常用的负责页面切换及导航功能的组件:Navigator 一:Navigator 对于页面导航其实主要功能就是:每个页面都知道本身应该切换到哪个页面,并且切到的页面会记录从哪里来,如果要返回的话,知道返回到哪个页面.这一切都不需要再用逻辑管理!而且每个页面之间也可以进行参数传递,

响应式页面导航设计解析

有人说,2013将是响应式网页设计之年.自用户体验设计师Ethan Marcotte在2010年提出Responsive Web Design(RWD)的名词,即响应式网页设计,这个概念从Responsive Architecture延伸到web设计领域,让所有的交互设计.视觉.前端开发都开始投入到这个趋势,或者说新的设计解决方案中. 当自己和身边的朋友越来越多的用上了iPhone.iPad.android手机或平板,当越来越多的人都在谈论这个老意识新概念的话题,当我们一直秉承的用户体验第一与网

WinPhone学习笔记(一)——页面导航与页面相关

最近学一下Windows Phone(接下来简称“WinPhone”)的开发,在很久很久前稍探究一下WinPhone中对一些传感器的开发,那么现在就从头来学学WinPhone的开发.先从WinPhone的页面入手,在我印象中比较深刻的那番话:一台WinPhone设备就好比一个Web的浏览器,应用上每个界面就是一个网页,可以点击“后退”来返回之前的页面.这个类比我觉得相当的形象.这番话能引出WinPhone开发中一个比较常见的操作——页面导航,由这个页面导航还引出了别的方面的内容,如下面所示 那下

微信卡券功能商户后台卡券货架、最新页面导航等五项优化

微信卡券最近又更新了,汲取苹果产品的精髓,势必将简单化进行到底.微信卡券商户后台近日发布卡券货架.最新页面导航等五项新优化.具体调整如下: 一. 类目及资质调整: 1. 取消[其它]类目 申请时,若无对应类目可选,即你所属的行业暂未开放,请留意平台后续通知. 2. 新增/修改类目: 新增:车票.船票.水电煤缴费.有线电视缴费.货运.综合电商 修改:话费——话费/流量/宽带 3. 部分类目资质调整: 保健食品.酒类.母婴用品.足疗按摩: 另外,家政服务.机票.钟表眼镜.普通食品.药房/药店.化妆品

小程序配置单个页面导航栏的属性(微信小程序交流群:604788754)

配置单个页面导航栏的属性: 就在所要配置页面相对应的json文件中写入以下想要设置的属性: { "navigationBarBackgroundColor": "#ffffff",    "navigationBarTextStyle": "black",    "navigationBarTitleText": "小程序" } 属性只需要放在大括号里面即可,每个属性用逗号隔开. 如果不

WPF的页面导航

工作中之前接触过的WPF程序一直是使用TabControl作不同页面间的切换,每个Tab负责独立的功能,清晰简捷,所以一直就没有动力研究WPF自带的页面导航.(虽然接触过使用页面导航的WPF项目,也并没有去了解,而是似懂非懂地过去了.) 直到最近做的一个项目,用的还是TabControl,但在某个Tab里面,做的任务有些复杂,导致UI在操作前后会有很大的变化.很自然的想法就是在这个Tab中使用两个view(我并没有指明是UserControl还是Page),来回切换.然而粗略地调查了一下之后觉得

wp8.1 页面返回 页面导航

public The_second() public second() { this.InitializeComponent(); Frame frame = Window.Current.Content as Frame; frame.Navigating += Frame_Navigating;//请求新的页面导航是发生 } private void Frame_Navigating(objectsender,NavigatingCancelEventArgs e) { if (xxx参数)

wp8.1 Study1: 页面导航&amp;页面间值传递

摘要:wp8.1与wp8中很多API是不一样了,wp8.1把以前wp7.x时的api去掉了,更多与win8.1的API相似.比如以下的页面导航和页面之间的值传递 1.页面导航 利用Frame.Navigate() 方法,C#语句如下: 1 Frame.Navigate(typeof(Page2));//Page2为一个页面的名称 这里需要注意的是此方法是可以重载的,即后面介绍的页面间值传递方法. 2.页面间值传递 这里利用Frame.Navigate()重载方法,进行单个值传递和多个值传递 1)