博客園客戶端UAP開發隨筆 -- 奔跑吧,頁面!


前言

頁面導航,是App中的基本功,一般的App,一來一去,只需要簡單的Navigate + Back就行了,一個復雜的App可能需要很多導航模式的混合才能實現最佳用戶體驗。

SplashScreen 啟動屏幕

我們先從最開始的SplashScreen說起吧。如果你把啟動屏幕做成一個Page,啟動時先顯示一下,然后假裝忙乎兩秒,跳到下一個主頁面開始進入正題,這個好像看上去也很美好。但是當用戶玩命兒按Back鍵時,哦,露出馬腳了,啟動頁面被喚出了。不過這個bug倒是不妨作為一個新奇的體驗。

MSDN里有一節專門講了如何添加一個SplashScreen,但是說實話,我試了兩次,都沒成功,人太笨!

算了,自己想辦法吧!如果在MainPage里加一個開關會不會簡單一些呢?於是這樣改裝MainPage.xaml:

<Page x:Name="page"
 >
    <Grid>
        <Grid x:Name="grid_Splash">
            <Image Height="100" Source="ms-appx:///Assets/Logo.100.White.png" />
        </Grid>
        <Grid x:Name="grid_Main" Visibility="Collapsed">
            ……content here……
        </Grid>
    </Grid>
</Page>

這里把不重要的代碼都刪除了,只看干貨:<Grid x:Name=”grid_Splash”>,這一項定義了一個Grid, 蓋在了主要內容的前面,因為下面的<Grid x:Name=grid_Main Visibility=”Collapsed”>在初始狀態被搞成隱藏了,如此一來,grid_Splash中的Image就會在應用啟動后,首先映入眼簾。

什么時候把它從用戶眼中摳走呢?有兩種方法可供選擇。

1)在MainPage的構造函數里開始裝載你的data,比如是從遠程,考慮到網絡狀況,可能需要幾秒鍾。那么你就在Splash里放一個ProgressRing,讓它轉啊轉,轉啊轉……差不多等用戶煩了,你的遠程數據也拿回來了,然后寫一句:

this.grid_Splash.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
this.grid_Main.Visibility = Windows.UI.Xaml.Visibility.Visible;

如此一來Splash被隱藏,你的主要內容在數據來到后也化妝完畢(綁定好了),可以出來見公婆了。

2)如果你不需要從遠程調用數據,而是從本地取數據,那么上面的過程就會一閃而過,晃瞎用戶的K金G眼,體驗很糟糕。這時你可以采用第二種辦法:在啟動應用時啟動一個2秒的計時器:

ThreadPoolTimer.CreateTimer(SplashTimeOut, new TimeSpan(0, 0, 2));

其中定義了回調函數(應該叫做delegate),當兩秒時間到時,在函數SplashTimeOut里面:

void SplashTimeOut(......)
{
    this.grid_Splash.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
    this.grid_Main.Visibility = Windows.UI.Xaml.Visibility.Visible;
}

這樣會和第一種方法的體驗一樣,只不過是假裝忙活了一下,干等2秒而已,目的是讓用戶有一種有人替他干活的虛榮感,而且讓你漂亮的啟動頁面給用戶流下深刻印象。

Basic頁面

除了MainPage以外,如果你還有其它二級頁面需要添加的話,請在VS中選擇這里:

image

你如果偏愛Blank Page我也不攔着你,但是Basic Page這個選擇能幫你省老鼻子事兒了,它在你的項目中自動添加了這些幫助文件:

image

其中,NavigationHelper.cs里面實現了在頁面上處理Back按鍵的事件,即,當用戶在底層頁面按Back鍵時,會回到上級頁面。但是在主頁面中不建議處理Back鍵,而是讓系統自動處理,把App放到后台。

基本頁面導航

基本頁面導航MSDN里有,和Silverlight有很大不同。記得SL理面有個什么NavigationService,聽上去蠻不錯的,到了WinRT里,一律用這個:

this.Frame.Navigate(typeof(NewsReadingPage), obj);

如果在Control里面做頁面導航,比如按了個自定義Control,而上層代碼又不太容易能得到具體是按了哪個Control,就只好在Control里觸發導航動作了,盡管我們不建議這樣做:

Frame frame = Window.Current.Content as Frame;
frame.Navigate(typeof(....),....);

這里的Frame很模糊,看上去像個全局的,但是在前面那個例子里,又是用this.Frame,就是說在Page對象里還有個Frame。有個文檔專門說這事兒,但是繞來繞去的我沒看懂,人太笨!如果有搞明白了這事兒的園友們可以給大家一個說明,謝謝先!

參數傳遞

基本頁面導航中的obj,就可以當作參數傳遞給下級頁面。但是有時候一些下級頁面需要返回一些信息回來給調用者,怎么做呢?因為在頁面的GoBack()方法中沒有參數。有三種方法可以解決這個問題:

1)利用好那個obj,把它即作為[in],又作為[out],下級頁面給obj里面的字段賦值,上級頁面通過解析obj里的約定字段來得到參數。

2)在下級頁面中用static字段,返回之前給它賦值,然后上級頁面可以使用。

3)弄個外部類,比如一個靜態類,或者一個單例的類,弄個變量在里面當參數。注意用完之后帶上手套把指紋擦掉,把該變量“歸零”就可以了,隱藏你的作案痕跡,避免下次使用時搞不清當前狀態。

前跳式頁面導航

z博客園UAP里沒有這個例子,用我們做的另一個App--豆瓣一刻來舉例說明吧(順便做一廣告,豆瓣一刻 for WP已經上線了,名字叫做“一刻”,link is here:一刻)。

咱們看圖說話(注意,以下邏輯很繞,沒有耐心的可能看不懂):

image

按正常邏輯,閱讀文章時,可以查看評論;如果想發表評論,點擊下方按鈕,進入“寫評論”頁面。但是此時用戶可能還沒有登錄,不能匿名發表評論,所以需要自動跳到登錄頁面,登錄成功后,自動進入寫評論頁面。

每當PM說起“自動”這個詞時,我就頭大!什么所謂自動,都是我們程序員手動搞出來的!有時候自動能實現,有時候實現不了,這個要和PM講清楚。

針對這個具體例子,有幾種方式備選,我們先看第一種:

1)看評論頁->點擊發表評論->發現沒登錄->進入登錄頁->登錄成功->進入發表評論頁

這個流程是最朴素的想法了,但是先別動手,仔細想想:在用戶提交評論后,頁面該回到哪里?從目前的情況看,是回到登錄成功的頁面了,會讓用戶感到困惑。而且,在點擊發表評論按鈕后,還需要做一個分支判斷:如果用戶登錄了,直接到寫評論頁;如果用戶沒登錄,要進入登錄頁面。

看看第二種:

2)看評論頁->點擊發表評論->進入寫評論頁->發現沒登錄->進入登錄頁->登錄成功->“自動”返回寫評論

這個看上去好一些,沒有第一種方式的兩個缺點。用戶寫完評論后,從stack看,是能直接回到最開始的看評論頁的。但是需要解決的問題是如何“自動“返回寫評論頁?我的刁民小計是:

a) 在CommentWritingPage.Page_Loaded事件中,判斷用戶是否登錄:

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            if (!Settings.Current.IsLogin)
            {
                if (this.backFromLogonPage)
                {

                }
                else
                {
                    this.Frame.Navigate(typeof(SettingsPage2), new DataModel.SettingNavigationParameter() { targetPivot = TargetPivotItemName.LogonAndBack, targetCss = 0 });
                }
            }
        }

如果登錄了,啥也不做,留在當前寫評論頁面即可;如果沒登錄,跳到SettingsPage.Logon頁面,並且帶上一個參數: LogonAndBack。

b) 在SettingsPage.Logon頁面,當有登錄成功的事件返回后(因為登錄是一個異步過程),判斷參數是不是LogonAndBack:

void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (Settings.Current.IsLogin && this.logonAndBack)
            {
                Settings.Current.PropertyChanged -= Current_PropertyChanged;
                this.Frame.GoBack();
            }
        }

如果是LogonAndBack,就用this.Frame.GoBack()返回調用者頁面,也就是發表評論頁面。

如此一來,當用戶發表完評論后,自動回閱讀評論頁面了,行雲流水(本來想說飛檐走壁,但是想了想,覺得還沒那么離譜)!

需要特別說明的是,在CommentWritingPage中,要在Page_Loaded事件中才能跳轉到其它頁,如果在OnNavigatedTo()事件中調用this.Frame.Navigate(),nothing happened,沒用,什么也不會發生。

后跳式頁面導航

讓我們來看一個更復雜的例子。下圖是我們開發的另一個App,還沒有弄完,功能類似個瀏覽器。

image

第一張圖是在一個WebViewPage里,已經加載了一個頁面,點擊右上角的窗口管理,進入第二張窗口管理頁面(比較丑陋,因為designer休假了,還沒完工)。可以看到一共6個窗口,只有第一個窗口被使用了。此時我們點擊第二個窗口,想啟動一個新窗口來瀏覽其它網站。按理說應用程序應該把第二個窗口激活,但是窗口中啥都沒有,大白板一個,用戶體驗很差,應該自行慚愧地立刻返回到主控頁讓用戶有更多的選擇項(就是目前所顯示的第三張藍色頁面)。這個如何做?

一個很自然的想法就是,從窗口管理頁Navigate到主控頁。不行滴!如此一來,當用戶在主控頁按Back時,會回到窗口管理頁,而窗口管理頁只是一個輔助頁面,類似彈出式菜單,不應該在stack里存留。主控頁是程序的根,按Back時必須要退出應用。

解決辦法是在窗口管理頁里收到點擊事件后,返回到WebViewPage(第一張圖):

// 窗口管理頁的點擊事件
private void lv_ItemClick(object sender, ItemClickEventArgs e)
        {
            WebViewHelper wvh = e.ClickedItem as WebViewHelper;
            this.pool.SetActiveWindow(wvh);
            if (this.Frame.CanGoBack)
            {
                this.pool.BackFromStatusPage = true;
                this.Frame.GoBack();
            }
        }

在此頁面中,判斷當前活動窗口是否為空,注意,也是要在Page_Loaded事件中處理:

private void Page_Loaded(object sender, RoutedEventArgs e)
        {
            Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;

            if (this.pool.BackFromStatusPage)
            {
                this.pool.BackFromStatusPage = false;
                if (this.pool.GetActiveWindow().IsEmptyView)
                {
                    // back to main page to wait for input
                    if (this.Frame.CanGoBack)
                    {
                        this.Frame.GoBack();
                    }
                }
                else
                {
                    // stay at webview page to show current web content
                }
            }
            else
            {
                this.ctrl_Input.Url = this.wvActive.Url;
            }
        }

如果IsEmptyView == true,再調用GoBack返回到主控頁面(第三張圖),而不是跳轉(前進)到主控頁面。

這個solution的基本思路,或者說是設計理念,就是窗口控制頁面(第二張圖)一定是個葉子節點,不能讓它作為中間導航節點。

小結

我和一些橋牌的初學者講過很多次:打每一張牌都要有你的思路,不能說”紅桃花色沒打過,我試試看“,而是說”從叫牌過程分析,我的同伴有紅桃大牌,我要幫助他一下,穿過明手的紅桃Q”。寫程序做設計也一樣,當有多種選擇時,一定要首先確定一個設計理念,然后再確定解決方法。

 

分享代碼,改變世界!

Windows Phone Store App link:

http://www.windowsphone.com/zh-cn/store/app/博客園-uap/500f08f0-5be8-4723-aff9-a397beee52fc

Windows Store App link:

http://apps.microsoft.com/windows/zh-cn/app/c76b99a0-9abd-4a4e-86f0-b29bfcc51059

GitHub open source link:

https://github.com/MS-UAP/cnblogs-UAP

MSDN Sample Code:

https://code.msdn.microsoft.com/CNBlogs-Client-Universal-477943ab

 

MS-UAP

2015/2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM