《Windows Fun 7》 二:Windows Phone怎樣實現一個水平切換Page的Transition

前言

重置過Windows Phone 7手機的朋友,一定記得,重置之後第一件要做的事是設置語言,時區,時間,Live ID等信息。我們也一定記得那是一種水平切換的動畫。當我們點擊下一步時,頁面從右向左滑動。有點類似iphone主頁菜單的左右滑動效果。

前段時間QQ羣有人在問這種效果。這樣的效果有時候會用到,比如街旁WP7客戶端開始的引導用戶使用的幾個頁面,類似教程,就可以做成這種效果。還有,如果需要在客戶端註冊,也可以把幾個註冊過程做成這樣的效果,還有比如翻書的效果,也可以這樣平移……

我們可以看下一下效果,它是從右向左連貫的水平滑動,本篇就分析這樣的思路以及實現原理,並給出源代碼下載:

page

 

1.你的思路

如果我猜的沒錯,你已經有了大概的思路:設置一個Canvas,按每個頁面實際的位置佈局,然後每次修改每個元素的Canvas.Left……

確實,以前我也這麼幹過!

這樣雖然可行,但是有點顯得我們不夠專業,而且所有頁面需要一次實例化,如果頁面過多,那會影響第一次加載時間。

那怎麼辦呢?不急,我們一步一步來慢慢分析。

 

2.Silverlight中的簡單導航

切換,切換,切換,該怎麼切換呢?

想想,在Silverlight中,我們要由一個UI頁面切換到另一個UI頁面,在不借助Frame的時候,會怎麼辦呢。

最簡單的,就是將容器的Content給替換成另一個對象,沒錯就是這樣。

所以,頁面切換的最簡單模型就是,最外層放置一個ContentControl,第一次顯示的時候給他的Content賦值一個頁面實例,在用戶點擊導航按鈕之後,用另一個頁面實例替換。如下的模型:

aaa

 

3.Frame+Page

其實,沒錯啦,頁面切換的基本模型就是這麼簡單。

Silverlight提供了一個Frame+Page的導航模型,它其實仍然是這樣的原理。我們首先看下Frame的定義:

   1:  public class Frame : ContentControl, INavigate
   2:  {
   3:     public bool CanGoBack { get; internal set; }
   4:     public bool CanGoForward { get; internal set; }
   5:     public Uri CurrentSource { get; internal set; }
   6:     public Uri Source { get; set; }
   7:     public UriMapperBase UriMapper { get; set; }
   8:     public event FragmentNavigationEventHandler FragmentNavigation;
   9:     public event NavigatedEventHandler Navigated;
  10:     public event NavigatingCancelEventHandler Navigating;
  11:     public event NavigationFailedEventHandler NavigationFailed;
  12:     public event NavigationStoppedEventHandler NavigationStopped;
  13:     public void GoBack();
  14:     public void GoForward();
  15:     public bool Navigate(Uri source);
  16:     public void StopLoading();
  17:  }

Frame首先是一個ContentControl,那麼每個Page就是一個頁面了。不同的是不需要我們去替換Content,我們只需要指定一個XAML文件的地址,Frame會根據路徑找到這個頁面,然後實例化,然後自己替換它的Content。

相比我們前面得出的最簡單的模型,它僅僅是提供了一些導航事件,使得我們可以做出一些處理。

我們看到,Frame模型簡化了我們開發中的頁面導航。還是很有用的。

 

4.Windows Phone 7中的Transitions

Nice!但是頁面切換太突兀了,一下子就換過來,一點都不符合Silverlight的風格,Silverlight可是做動畫的啊,Silverlight可是替換Flash的啊,爲什麼要像HTML一樣導航呢。

Windows Phone 7操作系統出來後,Metro橫空出世,Metro強調流暢,強調平滑,強調每戶的每個操作和響應都能通過一些視覺變換反饋給用戶。因此,給Page之間的過度加入了Transition。

不過默認的PhoneApplicationFrame並沒有加入Transition的功能,我們需要去分析Toolkit裏面的TransitionFrame代碼。

對TransitionFrame的分析以及將本實例轉化爲Frame的方式將會在下一篇分析。

 

5.本示例代碼分析

本實例實際上來源於PDC大會上介紹Expression Blend 4時官方演示的源代碼示例。其中我把導航的部分抽取出來。

和Frame+Page的模型類似,本實例也由兩部分組成:TransitionContentControl和SlideShow。其中TransitionContentControl類似於Frame的功能,而SlideShow類似於Page,不同的是SlideShow是繼承於ItemsControl,它首先就會取得所有頁面並實例化,這在頁面多得時候不是很可取,後面我會將它進行改造。

   1:  public class TransitionContentControl : Control 
   2:  { 
   3:     private ContentControl currentWrappedContent = null; 
   4:     private Grid grid = null; 
   5:   
   6:     public Style TransitionStyle { get; set;} 
   7:     public object Content{ get; set;} 
   8:   
   9:     static void ContentPropertyChanged(object o, DependencyPropertyChangedEventArgs args); 
  10:     static void TransitionStylePropertyChanged(object o, DependencyPropertyChangedEventArgs args); }
 

和前面的Frame沒有太大區別,神奇之處在於多了一個TransitionStyle依賴屬性,這個Style也沒什麼區別,神奇之處在於多了一個LayoutStates

   1:  <Style x:Key="SlideTemplate" TargetType="ContentControl">
   2:      <Setter Property="Template">
   3:          <Setter.Value>
   4:             <ControlTemplate TargetType="ContentControl">
   5:                <Grid x:Name="grid" Background="{TemplateBinding Background}">
   6:                   <VisualStateManager.VisualStateGroups>
   7:                      <VisualStateGroup x:Name="LayoutStates">
   8:                         <VisualState x:Name="BeforeLoaded">
   9:                         <VisualState x:Name="AfterLoaded"/>
  10:                         <VisualState x:Name="BeforeUnloaded">
  11:                      </VisualStateManager.VisualStateGroups>
  12:                      <Grid.RenderTransform>
  13:                         <CompositeTransform/>
  14:                      </Grid.RenderTransform>
  15:                      <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding                                    ContentTemplate}" Content="{TemplateBinding Content}"}"/>
  16:               </Grid>
  17:            </ControlTemplate>
  18:         </Setter.Value>
  19:     </Setter>
  20:  </Style>
 

關於LayoutStates,我之前的一篇文章有介紹:http://www.cnblogs.com/hielvis/archive/2010/10/09/1846046.html

其實也很簡單,它定義了三個狀態BeforeLoaded,AfterLoaded,BeforeUnloaded。

BeforeLoaded就是一個頁面加載之前的狀態,如果頁面是從右向左滑動,那麼它應該是在屏幕右邊看不見:

   1:  <VisualState x:Name="BeforeLoaded"> 
   2:     <Storyboard> 
   3:        <DoubleAnimation Duration="0" To="1000" 
   4:               Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" 
   5:               Storyboard.TargetName="grid"/> 
   6:        <DoubleAnimation Duration="0" To="0" 
   7:               Storyboard.TargetProperty="(UIElement.Opacity)" 
   8:               Storyboard.TargetName="grid"/> 
   9:     </Storyboard> 
  10:  </VisualState> 
 

我們看到這個狀態,其CompositeTransform.TranslateX被設置到屏幕後邊的位置,並且透明度爲0,不可見。

與BeforeLoaded狀態相反,BeforeUnloaded如下:

   1:  <VisualState x:Name="BeforeUnloaded">   
   2:     <Storyboard>   
   3:       <DoubleAnimation Duration="0" To="-1000"   
   4:                   Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)"                       
   5:                   Storyboard.TargetName="grid"/>    
   6:        <DoubleAnimation Duration="0" To="0"                         
   7:                  Storyboard.TargetProperty="(UIElement.Opacity)"   
   8:                  Storyboard.TargetName="grid"/>   
   9:     </Storyboard>  
  10:  </VisualState> 

而AfterLoaded就是一個頁面正常顯示的狀態,那爲什麼它是空的沒有設置呢,那是因爲它和正常狀態一樣,沒有修改任何元位置,下面就是正常狀態:
   1:  <Grid.RenderTransform> 
   2:      <CompositeTransform/> 
   3:  </Grid.RenderTransform> 
 

好了,由此我們不難明白,由BeforeLoaded狀態過度到AfterLoaded狀態,就是新頁面由右向左滑入的動畫;而由AfterLoaded狀態過度到BeforeUnloaded狀態,就實現了頁面向左淡出的動畫效果。

 

那麼你會問,這兩個狀態過度如何同時進行的。實際上,這兩個動畫不是同時作用在一個元素上,通過前面簡單的模型,我們知道Frame實際上控制着兩個頁面實例,在切換的時候,它將兩個不同的狀態過度分別作用的兩個頁面上,具體的代碼在OnContentChanged方法中:

   1:  protected virtual void OnContentChanged(object oldContent, object newContent)
   2:  {
   3:      ContentControl oldWrapper = this.currentWrappedContent;   if (oldWrapper != null)
   4:      {
   5:          VisualStateGroup layoutStatesGroup = FindNameInWrapper(oldWrapper, "LayoutStates") as VisualStateGroup;
   6:          if (layoutStatesGroup == null)
   7:          {
   8:               this.grid.Children.Remove(oldWrapper);
   9:               SetContent(oldWrapper, null);
  10:          }
  11:          else
  12:          {
  13:             layoutStatesGroup.CurrentStateChanged += delegate(object sender, VisualStateChangedEventArgs args)
  14:              {
  15:                   this.grid.Children.Remove(oldWrapper);
  16:                   SetContent(oldWrapper, null);
  17:               };
  18:             VisualStateManager.GoToState(oldWrapper, this.Reversed ? "BeforeLoaded" : "BeforeUnloaded", true);
  19:           }
  20:        }
  21:   
  22:        ContentControl newWrapper = new ContentControl();
  23:        newWrapper.Style = this.TransitionStyle;
  24:        newWrapper.HorizontalContentAlignment = HorizontalAlignment.Stretch;
  25:        newWrapper.VerticalContentAlignment = VerticalAlignment.Stretch;
  26:   
  27:        this.grid.Children.Add(newWrapper);
  28:        newWrapper.ApplyTemplate();
  29:        if (this.TransitionStyle != null)
  30:        {
  31:           SetContent(newWrapper, newContent);
  32:           if (oldWrapper != null)
  33:           {
  34:               VisualStateManager.GoToState(newWrapper, this.Reversed ? "BeforeUnloaded" : "BeforeLoaded", false);
  35:               VisualStateManager.GoToState(newWrapper, "AfterLoaded", true);
  36:           }
  37:       }
  38:       this.currentWrappedContent = newWrapper;}
 
這個方法實際上是本示例最核心的部分,它分別將兩種狀態切換作用在兩個頁面實例上,然後兩個動畫同時作用,就形成了頁面由右向左滑入的效果。
這裏也提前指出TransitionFrame的一個缺點,它不能像這樣兩個動畫同時作用。我後面將要改造的Frame會加上LayoutStates的支持。
 
此示例其實代碼非常精簡,思路也很清晰。以上我分析了核心部分。
 

6.下一篇內容

在此基礎上,我相信你已經大概知道TransitionFrame的實現思路了,是的,至少思想是類似的。

但是TransitionFrame要複雜得多,我將在下一篇分析。

本實例實際上有很多缺陷:

  1. 所有頁面第一次全部實例化,不能像Frame可以在導航的時候加載頁面
  2. 不能結合PhoneApplicationPage響應回退鍵,需要響應後退事件還需要手工做一些事情

後續,我將把次功能整合進TransitionFrame或者自定義一個Frame,那是就會分析一下Frame,這樣就可以把這種效果與Back事件集成。

 

7.源代碼下載

源代碼:http://files.cnblogs.com/hielvis/TecHappy.WindowsPhone.Toolkit.rar

Xap包:http://files.cnblogs.com/hielvis/TecHappy.WindowsPhone.Sample.xap

8.Windows Phone 7技術沙龍

9月17號,我們將在微軟亞太研發集團舉辦Windows Phone 7的技術沙龍,將有一些大家熟悉的一線開發人員分享實戰經驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章