原理很简单,利用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);
        }
    }
}