原理很簡單,利用Path畫一個圖,然後用動畫進行播放,播放時間由依賴屬性輸入賦值與控制元件內部維護的一個計時器進行控制。
控制元件基本是玩具,無法作為真實專案使用。
非專業UI,即使知道怎麼畫圖也是畫的不如意,到底是眼睛會了,手不行啊。
主介面xaml
<local:VoiceAnimeButton Height="40" Width="200" IconMargin="5,0,-8,0" HorizontalContentAlignment="Center" CornerRadius="15" VerticalContentAlignment="Center" BorderBrush="Black" IconFill="Black" BorderThickness="1" Background="Transparent" VoicePlayTime="0:0:1" > <local:VoiceAnimeButton.ContentTemplate> <DataTemplate> <TextBlock FontSize="10" > <Run Text="播放時間"/> <Run Text="{Binding RelativeSource={RelativeSource AncestorLevel=1,AncestorType=local:VoiceAnimeButton,Mode=FindAncestor}, Path=VoicePlayTime}"/> <Run Text=" "/> <Run Text="狀態: "/> <Run Text="{Binding RelativeSource={RelativeSource AncestorLevel=1,AncestorType=local:VoiceAnimeButton,Mode=FindAncestor}, Path=IsVoicePlay}"/> </TextBlock> </DataTemplate> </local:VoiceAnimeButton.ContentTemplate> </local:VoiceAnimeButton>
控制元件設計XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:聲音播放動畫"> <Style TargetType="{x:Type local:VoiceAnimeButton}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:VoiceAnimeButton}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}" Padding="1"> <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Border Margin="{TemplateBinding IconMargin}" > <Viewbox> <Path x:Name="VoicePath" Height="{TemplateBinding IconHieght}" Width="{TemplateBinding IconWidth}" Fill="{TemplateBinding IconFill}" > <Path.Data> <PathGeometry> <PathFigureCollection> M20 20 Q12 45 20 85 l7 -4 Q18 48 27 23 l-7 -3Z M32 29 Q22 45 32 75 l7 -4 Q29 50 38 33 l-6.5 -4 M45 35 Q38 48 45 68 l7 -4 Q45 50 52 39 l-7.5 -4 M58 41 Q55 49 58 61 l17 -11Z </PathFigureCollection> </PathGeometry> </Path.Data> </Path> </Viewbox> </Border> <ContentPresenter Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Grid> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="VoicePlayStart"> <BeginStoryboard x:Name="bs1"> <Storyboard Storyboard.TargetProperty="Data" Storyboard.TargetName="VoicePath" RepeatBehavior="Forever" Duration="0:0:0.4" BeginTime="0"> <ObjectAnimationUsingKeyFrames> <DiscreteObjectKeyFrame KeyTime="0:0:0.1"> <DiscreteObjectKeyFrame.Value> <PathGeometry> <PathFigureCollection> M45 35 Q38 48 45 68 l7 -4 Q45 50 52 39 l-7.5 -4 M58 41 Q55 49 58 61 l17 -11Z </PathFigureCollection> </PathGeometry> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> <DiscreteObjectKeyFrame KeyTime="0:0:0.2"> <DiscreteObjectKeyFrame.Value> <PathGeometry> <PathFigureCollection> M32 29 Q22 45 32 75 l7 -4 Q29 50 38 33 l-6.5 -4 M45 35 Q38 48 45 68 l7 -4 Q45 50 52 39 l-7.5 -4 M58 41 Q55 49 58 61 l17 -11Z </PathFigureCollection> </PathGeometry> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> <DiscreteObjectKeyFrame KeyTime="0:0:0.3"> <DiscreteObjectKeyFrame.Value> <PathGeometry> <PathFigureCollection> M20 20 Q12 45 20 85 l7 -4 Q18 48 27 23 l-7 -3Z M32 29 Q22 45 32 75 l7 -4 Q29 50 38 33 l-6.5 -4 M45 35 Q38 48 45 68 l7 -4 Q45 50 52 39 l-7.5 -4 M58 41 Q55 49 58 61 l17 -11Z </PathFigureCollection> </PathGeometry> </DiscreteObjectKeyFrame.Value> </DiscreteObjectKeyFrame> </ObjectAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="VoicePlayEnd"> <RemoveStoryboard BeginStoryboardName="bs1"/> </EventTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
控制元件CS程式碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; namespace 聲音播放動畫 { public class VoiceAnimeButton : ContentControl { static VoiceAnimeButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(VoiceAnimeButton), new FrameworkPropertyMetadata(typeof(VoiceAnimeButton))); } private DispatcherTimer Timer; public VoiceAnimeButton() { Timer = new DispatcherTimer(); Timer.Tick += Timer_Tick; Timer.Interval = TimeSpan.FromSeconds(1); } private void Timer_Tick(object sender, EventArgs e) { Timer.Stop(); IsVoicePlay = false; this.RaiseEvent(new RoutedEventArgs(VoicePlayEndEvent, this)); } public static readonly RoutedEvent VoicePlayStartEvent = EventManager.RegisterRoutedEvent("VoicePlayStart", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(VoiceAnimeButton)); /// <summary> /// 聲音播放開始事件 /// </summary> public event RoutedEventHandler VoicePlayStart { add { this.AddHandler(VoicePlayStartEvent, value); } remove { RemoveHandler(VoicePlayStartEvent, value); } } public static readonly RoutedEvent VoicePlayEndEvent= EventManager.RegisterRoutedEvent("VoicePlayEnd", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(VoiceAnimeButton)); /// <summary> /// 聲音播放結束事件 /// </summary> public event RoutedEventHandler VoicePlayEnd { add { AddHandler(VoicePlayEndEvent, value); } remove { RemoveHandler(VoicePlayEndEvent, value); } } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { base.OnMouseLeftButtonDown(e); IsMouseLeftClick = true; Timer.Interval = VoicePlayTime; Timer.Start(); IsVoicePlay = true; this.RaiseEvent(new RoutedEventArgs(VoicePlayStartEvent,this)); } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { base.OnMouseLeftButtonUp(e); IsMouseLeftClick = false; } public static readonly DependencyProperty VoicePlayTimeProperty = DependencyProperty.Register("VoicePlayTime", typeof(TimeSpan), typeof(VoiceAnimeButton), new PropertyMetadata(TimeSpan.FromMilliseconds(1000))); public TimeSpan VoicePlayTime { get => (TimeSpan)GetValue(VoicePlayTimeProperty); set => SetValue(VoicePlayTimeProperty, value); } public static readonly DependencyProperty IsMouseLeftClickProperty = DependencyProperty.Register("IsMouseLeftClick", typeof(bool), typeof(VoiceAnimeButton),new PropertyMetadata(false)); public bool IsMouseLeftClick { get => (bool)GetValue(IsMouseLeftClickProperty); set => SetValue(IsMouseLeftClickProperty, value); } public static readonly DependencyProperty IconWidthProperty = DependencyProperty.Register("IconWidth", typeof(double), typeof(VoiceAnimeButton), new PropertyMetadata(100.0)); public double IconWidth { get => Convert.ToDouble(IconWidthProperty); set => SetValue(IconWidthProperty, value); } public static readonly DependencyProperty IconHieghtProperty = DependencyProperty.Register("IconHieght", typeof(double), typeof(VoiceAnimeButton), new PropertyMetadata(100.0)); public double IconHieght { get => Convert.ToDouble(IconHieghtProperty); set => SetValue(IconHieghtProperty, value); } public static readonly DependencyProperty IconFillProperty= DependencyProperty.Register("IconFill", typeof(SolidColorBrush), typeof(VoiceAnimeButton), new PropertyMetadata(new SolidColorBrush(Colors.Black))); public SolidColorBrush IconFill { get => GetValue(IconFillProperty) as SolidColorBrush; set => SetValue(IconFillProperty, value); } public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(VoiceAnimeButton), new PropertyMetadata(new CornerRadius(0))); public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(VoiceAnimeButton), new PropertyMetadata(new Thickness(0.0))); public Thickness IconMargin { get => (Thickness)GetValue(IconMarginProperty); set => SetValue(IconMarginProperty, value); } public static readonly DependencyProperty IsVoicePlayProperty = DependencyProperty.Register("IsVoicePlay", typeof(bool), typeof(VoiceAnimeButton)); public bool IsVoicePlay { get => (bool)GetValue(IsVoicePlayProperty); set => SetValue(IsVoicePlayProperty, value); } } }