Windows Phone自定義主題


我們知道Windows Phone默認的主題系統是由黑白兩色為背景和一些強調色組成的,用戶可以隨意切換。通常來說,應用開發者無需關心這一部分,系統會去更新相關的資源,然后再體現在應用中。

但有一些時候,我們基於品牌等因素的考量,可能不想使用Windows Phone的默認主題。比如我開發的“豆芽”是豆瓣網的一個客戶端,我希望盡可能貼近豆瓣網本身清新的風格,而不是給用戶呈現一個和豆瓣網風格大相徑庭的黑色背景的界面;再比如我想讓應用使用Windows Phone的默認字體(等線),而不是SDK的默認字體(雅黑)。

這些都需要我們去自定義應用的主題。

在介紹如何創建自定義主題之前,先來簡單的描述一下Windows Phone主題的原理。

在Windows Phone中,系統預定義了許多資源,這些資源包括了畫筆、顏色、字體、粗細、字號、文本樣式等等最基本的元素(詳細的資源名稱可以查看這里)。此外,Windows Phone中的所有控件都會有自己的樣式,樣式中還包括了定義控件布局的模板,而模板又利用系統內置的資源定義了控件在各種狀態下的外觀(所以我們在XAML中隨處可以見類似{StaticResource PhoneBackgroundBrush}這樣的對內置資源的引用)。

所以我們可以想到,修改內置資源或者修改控件的樣式都可以達到自定義主題的效果。

在早期的Windows Phone v7.0),我們可以使用前一種方式,只需要在應用中增加一個ResourceDictionary的XAML文件,里邊添加若干和系統資源相同鍵名的資源,即可實現對系統資源的覆蓋。

但這種方法在Mango (v7.1)中無效了,它被當作一個Bug修復了,所以我們只能另尋方法。代價最小的一種方法是在App初始化的時候動態的讀取我們定義的ResourceDictionary,並替換系統內置資源。具體的步驟可以參考這里 ,我就不贅述了。

此外,還可以利用Mango帶來的另外一個變化,新的Silverlight 4帶來的“隱式樣式”(Implicit Style)。隱式樣式是指只有TargetType卻沒有指定Key的Style,在Silverlight 4中,會將這個Style應用到所有匹配的TargetType對象上。

我們可以利用“隱式樣式”來更改內置控件的樣式,只需要將需要修改的控件的樣式添加到應用的ResourceDictionary中,將其Key值去掉即可(當然不去掉也可以,這就需要手工設置所有匹配控件的Style屬性)。

但一般情況下,既然我們想要更改應用級別的主題,基本上我們會修改整套配色方案,如果單純用“隱式樣式”來實現的話,我們就需要實現所有控件的隱式樣式,看起來似乎也不是一件簡單的事情。

本文來介紹另外一種方法,這種方法不僅實現了所有控件的樣式,還一並接管了所有的內置資源,但它的實現過程卻一點兒也不復雜。

首先

我們在項目中添加一個XAML文件,用來存放新的主題,這里將文件名定為“CustomTheme.xaml”,下面是它的內容:

<ResourceDictionary
 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
 
xmlns:System
="clr-namespace:System;assembly=mscorlib">
 
</ResourceDictionary>

然后編輯App.xaml,添加相應的ResourceDictionary:

<Application.Resources>
 
<ResourceDictionary>
 
<ResourceDictionary.MergedDictionaries>
 
<ResourceDictionary Source="CustomTheme.xaml"/>
 
</ResourceDictionary.MergedDictionaries>
 
</ResourceDictionary>
 
</Application.Resources>

接着打開Windows Phone SDK附帶的設計資源文件夾:C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Design。我們會看到若干文件夾,這些文件夾都對應於Windows Phone中的一個主題搭配,譬如LightGreen表示淺色背景色和綠色強調色的搭配。

挑選一個最貼近我們想要的主題配色的文件夾打開,我們會看到兩個XAML文件,其中ThemeResources.xaml中定義了系統資源,System.Windows.xaml中定義了大多數控件的樣式。

將ThemeResources.xaml和System.Windows.xaml添加到項目中,由於System.Windows.xaml引用了ThemeResources.xaml中定義的資源,所以在System.Windows.xaml的根元素下添加一個ResourceDictionary來引用ThemeResources.xaml(否則在運行時會拋出找不到資源的異常):

<ResourceDictionary.MergedDictionaries>

<ResourceDictionary Source="ThemeResources.xaml"/>

</ResourceDictionary.MergedDictionaries>

然后在我們的CustomTheme.xaml中也添加一個對System.Windows.xaml引用的ResourceDictionary:

<ResourceDictionary.MergedDictionaries>

<ResourceDictionary Source="System.Windows.xaml"/>

</ResourceDictionary.MergedDictionaries>

現在這幾個文件的關系是:App.xaml引用了CustomTheme.xaml,CustomTheme.xaml引用了System.Windows.xaml,System.Windows.xaml引用了ThemeResources.xaml。也就是說,在應用運行時,這幾個XAML會被全部加載。

至此,我們的應用中已經包含了Windows Phone的幾乎所有資源和樣式,但無論我們怎么修改它們的值,都不會影響應用在運行時呈現的外觀。因為我們所有的模版和頁面元素依然引用了系統內置的資源,而這些資源是不可覆蓋的。

這里有個奇怪的現象,如果我們修改了這些資源的值,是可以在設計器中看到效果的,但絲毫不會影響運行時的效果,我相信這也是一個遲早要修復的Bug。

回到正題,既然我們已經包含了Windows Phone的幾乎所有的資源和樣式,那還何必去覆蓋系統預置的資源和模版呢?直接使用我們自己的不就好了嗎?

沒錯,就是這樣。

我們觀察到Windows Phone內置的資源和樣式的名稱都以“Phone”開頭,非常有規律,也就非常容易替換。所以接下來我們在整個項目(或解決方案,取決於你的實際情況)里搜索x:Key=”Phone這個字符串,將其替換為x:Key=”Custom,這樣會將所有資源的名稱修改為CustomXXX;然后在搜索{StaticResource Phone開頭的字符串,將其替換為{StaticResource Custom,這樣會把所有對系統資源的引用修改為對應用內資源的引用。

如下圖所示:

完成這步操作之后,我們的應用就已經基本和Windows Phone內置主題說再見了,這時我們可以修改一些資源的值,運行一下看看效果:

不僅僅是顏色,因為我們已經將系統內置的絕大多數樣式都包含了進來了,所以還可以修改控件的布局。譬如我們想更改CheckBox的對鈎的樣式,沒有問題,它在模版中使用一個名為CheckMark的Path來表示的,直接修改就好:

前面我一直在強調我們只包含了“絕大多數模版”,是因為還有一些控件的模版並未在System.Windows.xaml中定義,譬如Panorama和Pivot,對於這兩個控件的樣式我們該如何自定義呢?

需要一些小手段,我們用.NET Reflector這個工具打開C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Libraries\Silverlight\Microsoft.Phone.Controls.dll,查看它的Resources,將MicrosoftPhone.Controls.g.resources中的themes/generic.xaml保存下來。

這個文件定義了Panorama和Pivot控件的樣式,同樣將其添加到項目中。

然后在項目中添加對Microsoft.Phone.Controls.dll的引用並將generic.xaml文件根元素中定義的兩個命名空間(local和localPrimitives)的值修改一下:

xmlns:local="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"

xmlns:localPrimitives="clr-namespace:Microsoft.Phone.Controls.Primitives;assembly=Microsoft.Phone.Controls"

現在我們需要把generic.xaml插入到之前創建好的“資源引用鏈”中,先在generic.xaml文件的根元素下增加對System.Windows.xaml的引用:

<ResourceDictionary.MergedDictionaries>

<ResourceDictionary Source="System.Windows.xaml"/>

</ResourceDictionary.MergedDictionaries>

再將CustomTheme.xaml中對System.Windows.xaml的引用改為對generic.xaml的引用。

現在這幾個文件的關系變成了:App.xaml應用了CustomTheme.xaml,CustomTheme.xaml引用了generic.xaml,generic.xaml引用了System.Windows.xaml,System.Windows.xaml引用了ThemeResources.xaml。

OK,現在再添加一個Panorama頁面,修改一下布局(Title的尺寸),試試效果:

方法介紹完了,簡單歸納一下,就是找出系統內置的資源和樣式,添加到應用的項目中,修改所有資源和樣式的Key值,修改所有對系統內置資源和樣式的引用,聽起來似乎工程浩大,其實只是幾步簡單的復制粘貼和查找替換。

唯一比較麻煩的是要維護幾個xaml文件之間的互相引用,還要保持一定的順序,其實你也可以不必這么做,如果你願意的話,完全可以把ThemeResources.xaml、System.Windows.xaml和generic.xaml文件根元素中的內容依次復制到CustomTheme.xaml文件中,這樣只需要CustomTheme.xaml一個文件就可以了,但我個人認為把這幾個文件混在一塊並不利於維護。

當然,這種方法也並不是十分完美的,在我看來,它存在如下一些缺陷:

  • 每次添加一個新的頁面,都要檢查其中對系統內置資源的引用,將其修改為引用我們接管的資源
  • 假如微軟在將來的版本中修改了內置樣式的模板,我們也得做對應的修改,這就會比較棘手,我的方法是,在修改這些資源的值的時候,在其旁邊加一個注釋來表明這個值被修改過(譬如<!–Changed–>),將來微軟升級了SDK,我們可以先備份一下現有XAML文件,然后重復之前的步驟接管系統內置資源和樣式,再根據備份文件中的特定注釋來逐一修改。

此外,你可能會擔心最后在接管Panorama和Pivot樣式的時候使用了一點Hack的手段,會不會被市場拒絕,對此我也不能給出確切的答案,我只能說,“豆芽”也使用了這種方法,目前還沒有遇到這方面的問題。

輕觸這里下載源碼


免責聲明!

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



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