一、背景介紹
首先,讓我們看一下iPhone上的新郵件提示效果。
在郵件圖標的右上角會出現未讀的新郵件數量,蘋果的這種設計即簡潔又精致,而且相當的實用。
那么經典的效果當然要用我們的實際行動來膜拜!^_^
二、最終效果預覽
在該篇文章的最后分享了代碼,^_^。
三、實現分解
結構采用自定義按鈕+自定義裝飾件(Adorner)。
裝飾件顧名思義就是用來作裝飾用的,好處就是:我們以前都是自己寫個控件然后在控件上繪制所有的效果,
而現在有了它,我們可以將一些效果獨立出來做成一種裝飾件,重用在其他想使用該效果的控件上,增強了
效果的解耦和重用。
1、自定義按鈕(PromptableButton)
接下來我們編寫一個繼承自Button類的PromptableButton。
internal class PromptableButton : Button { //省略... }
該按鈕需要一個提示數量(PromptCount)屬性用於最終的右上角提示顯示用,做成依賴屬性是為了綁定給用作顯示的XAML代碼。
CoercePromptCountCallback用於限制PromptCount不能小於0。
public int PromptCount { get { return (int)GetValue(PromptCountProperty); } set { SetValue(PromptCountProperty, value); } } public static readonly DependencyProperty PromptCountProperty = DependencyProperty.Register("PromptCount", typeof(int), typeof(PromptableButton), new FrameworkPropertyMetadata(0, new PropertyChangedCallback(PromptCountChangedCallBack),
new CoerceValueCallback(CoercePromptCountCallback))); private static object CoercePromptCountCallback(DependencyObject d, object value) { int promptCount = (int)value; promptCount = Math.Max(0, promptCount); return promptCount; }
該按鈕還需要一個封面圖片,同樣做成依賴屬性綁定給界面。
public ImageSource CoverImageSource { get { return (ImageSource)GetValue(CoverImageSourceProperty); } set { SetValue(CoverImageSourceProperty, value); } } public static readonly DependencyProperty CoverImageSourceProperty = DependencyProperty.Register("CoverImageSource", typeof(ImageSource), typeof(PromptableButton), new UIPropertyMetadata(null));
因為是自定義按鈕,我們需要為該按鈕提供一個皮膚,皮膚寫在Themes/Generic.xaml資源文件中。
為了應用該皮膚,我們如此做:
static PromptableButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PromptableButton), new FrameworkPropertyMetadata(typeof(PromptableButton))); }
接下來,看看皮膚的實現。
其中的PART_CoverImage的Source綁定PromptableButton的CoverImageSource屬性,用來顯示封面圖片。
下面的觸發器的作用是當按下按鈕后對按鈕作模糊效果。
<Style TargetType="{x:Type local:PromptableButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:PromptableButton}"> <Grid> <Image Name="PART_CoverImage" Stretch="Fill" Source="{Binding RelativeSource={RelativeSource TemplatedParent},Path=CoverImageSource}"> <Image.Effect> <BlurEffect x:Name="effect" Radius="0"/> </Image.Effect> </Image> </Grid> <ControlTemplate.Triggers> <Trigger Property="IsPressed" Value="True"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="effect" Storyboard.TargetProperty="Radius" From="0" To="5" Duration="0:0:0.2" /> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="effect" Storyboard.TargetProperty="Radius" From="5" To="0" Duration="0:0:0.2" /> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
自定義按鈕(PromptableButton)完成。
2、自定義裝飾件(PromptAdorner)
我們一般都會在Adorner的OnRender方法里實現裝飾件的繪制,如果繪制的話需要監聽PromptableButton的PromptCount
屬性的變化,這樣作就顯得比較麻煩,依賴屬性本身就是用作綁定的最好的對象,為什們我們不能使用XAML來作繪制,並且綁定
PromptCount!
為此,專門做一個負責繪制PromptCount的自定義控件(PromptChrome),為這個控件做一個皮膚,在此皮膚內綁定
PromptCount,並且將這個控件作為裝飾件(PromptAdorner)的子控件,說白了就是PromptAdorner負責裝飾,裝飾些什么東
西則交給PromptChrome來完成。
首先,讓我們看看PromptAdorner的代碼。
其目的就是管理子控件PromptChrome,並通過ArrangeOverride方法進行布局。
注意其中的_chrome.DataContext = adornedElement;這句話使PromptableButton成為PromptChrome的上下文,
使PromptCount被PromptChrome界面上的元素綁定。
internal class PromptAdorner : Adorner { protected override int VisualChildrenCount { get { return 1; } } public PromptAdorner(UIElement adornedElement) : base(adornedElement) { _chrome = new PromptChrome(); _chrome.DataContext = adornedElement; this.AddVisualChild(_chrome); } protected override Visual GetVisualChild(int index) { return _chrome; } protected override Size ArrangeOverride(Size arrangeBounds) { _chrome.Arrange(new Rect(arrangeBounds)); return arrangeBounds; } PromptChrome _chrome; }
接下來,我們看一下PromptChrome的代碼。
在靜態構造中應用皮膚。
在ArrangeOverride布局方法中,將自己放置到右上角。
internal class PromptChrome : Control { static PromptChrome() { DefaultStyleKeyProperty.OverrideMetadata(typeof(PromptChrome), new FrameworkPropertyMetadata(typeof(PromptChrome))); } protected override Size ArrangeOverride(Size arrangeBounds) { this.Width = 34; this.Height = 34; this.HorizontalAlignment = System.Windows.HorizontalAlignment.Right; this.VerticalAlignment = System.Windows.VerticalAlignment.Top; TranslateTransform tt = new TranslateTransform(); tt.X = 10; tt.Y = -10; this.RenderTransform = tt; return base.ArrangeOverride(arrangeBounds); } }
接下來是PromptChrome的皮膚。
提示效果的繪制,綁定PromptCount都在內,可參考注釋。
<Style TargetType="{x:Type local:PromptChrome}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:PromptChrome}"> <Grid x:Name="container"> <!--最外圈的白色圓框,並對其作陰影效果--> <Ellipse Fill="White"> <Ellipse.Effect> <DropShadowEffect BlurRadius="6" ShadowDepth="6" Opacity="0.8" Direction="270" RenderingBias="Performance"/> </Ellipse.Effect> </Ellipse> <!--內部的上半圓--> <Ellipse Margin="3"> <Ellipse.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0" Color="#FFF4AEB1"/> <GradientStop Offset="0.5" Color="#FFE3313A"/> <GradientStop Offset="1" Color="#FFE3313A"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <!--內部的下半圓,通過采用Exclude模式合並上下兩個圓來完成--> <Path HorizontalAlignment="Center" VerticalAlignment="Center"> <Path.Data> <CombinedGeometry GeometryCombineMode="Exclude" > <CombinedGeometry.Geometry1> <EllipseGeometry Center="14 14" RadiusX="14" RadiusY="14" /> </CombinedGeometry.Geometry1> <CombinedGeometry.Geometry2> <EllipseGeometry Center="14 0" RadiusX="18" RadiusY="14"/> </CombinedGeometry.Geometry2> </CombinedGeometry> </Path.Data> <Path.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0" Color="#FFDF151F"/> <GradientStop Offset="1" Color="#FFBA0004"/> </LinearGradientBrush> </Path.Fill> </Path> <Viewbox Stretch="Uniform" > <!--綁定上文中的PromptCount屬性--> <Label Content="{Binding Path=PromptCount}" x:Name="label" Foreground="White" FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Viewbox> </Grid> <ControlTemplate.Triggers> <!--使用數據觸發器,當PromptCount為0時,隱藏提示--> <DataTrigger Binding="{Binding Path=PromptCount}" Value="0"> <Setter TargetName="container" Property="Visibility" Value="Hidden"/> </DataTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
四、代碼
http://download.csdn.net/download/kongxh_1981/9161573