前言
重置過Windows Phone 7手機的朋友,一定記得,重置之後第一件要做的事是設置語言,時區,時間,Live ID等信息。我們也一定記得那是一種水平切換的動畫。當我們點擊下一步時,頁面從右向左滑動。有點類似iphone主頁菜單的左右滑動效果。
前段時間QQ羣有人在問這種效果。這樣的效果有時候會用到,比如街旁WP7客戶端開始的引導用戶使用的幾個頁面,類似教程,就可以做成這種效果。還有,如果需要在客戶端註冊,也可以把幾個註冊過程做成這樣的效果,還有比如翻書的效果,也可以這樣平移……
我們可以看下一下效果,它是從右向左連貫的水平滑動,本篇就分析這樣的思路以及實現原理,並給出源代碼下載:
1.你的思路
如果我猜的沒錯,你已經有了大概的思路:設置一個Canvas,按每個頁面實際的位置佈局,然後每次修改每個元素的Canvas.Left……
確實,以前我也這麼幹過!
這樣雖然可行,但是有點顯得我們不夠專業,而且所有頁面需要一次實例化,如果頁面過多,那會影響第一次加載時間。
那怎麼辦呢?不急,我們一步一步來慢慢分析。
2.Silverlight中的簡單導航
切換,切換,切換,該怎麼切換呢?
想想,在Silverlight中,我們要由一個UI頁面切換到另一個UI頁面,在不借助Frame的時候,會怎麼辦呢。
最簡單的,就是將容器的Content給替換成另一個對象,沒錯就是這樣。
所以,頁面切換的最簡單模型就是,最外層放置一個ContentControl,第一次顯示的時候給他的Content賦值一個頁面實例,在用戶點擊導航按鈕之後,用另一個頁面實例替換。如下的模型:
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要複雜得多,我將在下一篇分析。
本實例實際上有很多缺陷:
- 所有頁面第一次全部實例化,不能像Frame可以在導航的時候加載頁面
- 不能結合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的技術沙龍,將有一些大家熟悉的一線開發人員分享實戰經驗。