• c#,使用WPF的Adorner实现iPhone上新邮件或消息提示效果实现(一)


    一、背景介绍

    首先,让我们看一下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

  • 相关阅读:
    System.BadImageFormatException: Could not load file or assembly
    MSSQL数据库索引的应用
    快递api网接口快递调用方法
    winform的扩展的带有截图功能picturebox
    免费api大全
    C#使用百度API通过IP获取地理位置和坐标
    用淘宝ip地址库查ip
    开源相关社区/项目一览(备查,欢迎补充)(转)
    .NET系列文章——近一年文章分类整理,方便各位博友们查询学习(转)
    设计模式--状态模式C++实现
  • 原文地址:https://www.cnblogs.com/kongxianghai/p/2590613.html
Copyright © 2020-2023  润新知