WPF实例用户控件库之表盘和骨架屏动画设计

1、文件架构
在这里插入图片描述
2、Instrument.xaml

<UserControl x:Class="Controls.Instrument"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Controls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="450">
    <Grid>
        <Ellipse Fill="{Binding PlateBackground,RelativeSource={RelativeSource AncestorType=UserControl,Mode=FindAncestor}}" Name="BackEllipse"/>
        <Canvas Name="mainCanvas" Width="{Binding Width,ElementName=BackEllipse}" Height="{Binding Height,ElementName=BackEllipse}"/>
        <Path Stroke="White" StrokeThickness="4" Name="circle" RenderTransformOrigin="0.5 0.5" 
              StrokeStartLineCap="Round" StrokeEndLineCap="Round"
              Width="{Binding Width,ElementName=BackEllipse}" Height="{Binding Height,ElementName=BackEllipse}">
            <Path.RenderTransform>
                <RotateTransform Angle="-45"/>
            </Path.RenderTransform>
        </Path>

        <Path Fill="White" Name="pointer" RenderTransformOrigin="0.5 0.5" 
              Width="{Binding Width,ElementName=BackEllipse}" Height="{Binding Height,ElementName=BackEllipse}">
            <Path.RenderTransform>
                <RotateTransform Angle="-45" x:Name="rtPointer"/>
            </Path.RenderTransform>
        </Path>

        <Border Width="20" Height="20" CornerRadius="10">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="White" Offset="0.583"/>
                    <GradientStop Color="#FF97B5B9" Offset="1"/>
                </LinearGradientBrush>
            </Border.Background>

        </Border>
    </Grid>
</UserControl>

3、Instrument.xaml.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
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.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Controls
{
    /// <summary>
    /// Instrument.xaml 的交互逻辑
    /// </summary>
    public partial class Instrument : UserControl
    {
        //依赖属性
        public Brush PlateBackground
        {
            get { return (Brush)this.GetValue(PlateBackgroundProperty); }
            set { this.SetValue(PlateBackgroundProperty, value); }
        }
        public static readonly DependencyProperty PlateBackgroundProperty = DependencyProperty.Register("PlateBackground", typeof(Brush), typeof(Instrument),
            new PropertyMetadata(default(Brush)));

        public double Value
        {
            get { return (double)this.GetValue(ValueProperty); }
            set { this.SetValue(ValueProperty, value); }
         }
        public static readonly DependencyProperty ValueProperty=DependencyProperty.Register("Value",typeof(double),typeof(Instrument),
            new PropertyMetadata(double.NaN, new PropertyChangedCallback(OnPropertyChanged)));

        public int Minimum
        {
            get { return (int)this.GetValue(MinimumProperty); }
            set { this.SetValue(MinimumProperty, value); }
        }
        public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(int), typeof(Instrument),
            new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropertyChanged)));

        public int Maximum
        {
            get { return (int)this.GetValue(MaximumProperty); }
            set { this.SetValue(MaximumProperty, value); }
        }
        public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(int), typeof(Instrument),
            new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropertyChanged)));

        public int Interval
        {
            get { return (int)this.GetValue(IntervalProperty); }
            set { this.SetValue(IntervalProperty, value); }
        }
        public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register("Interval", typeof(int), typeof(Instrument),
            new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropertyChanged)));

        public int ScaleTextSize
        {
            get { return (int)this.GetValue(ScaleTextSizeProperty); }
            set { this.SetValue(ScaleTextSizeProperty, value); }
        }
        public static readonly DependencyProperty ScaleTextSizeProperty = DependencyProperty.Register("ScaleTextSize", typeof(int), typeof(Instrument),
            new PropertyMetadata(default(int), new PropertyChangedCallback(OnPropertyChanged)));

        public static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as Instrument).Refresh();
        }
        public Instrument()
        {
            InitializeComponent();
            this.SizeChanged += Instrument_SizeChanged;
        }

        private void Instrument_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            double minSIze=Math.Min(this.RenderSize.Width, this.RenderSize.Height);
            this.BackEllipse.Width = minSIze;
            this.BackEllipse.Height = minSIze;
        }
        private void Refresh()
        {
            if (double.IsNaN(this.BackEllipse.Width)) return;
            double radius = this.BackEllipse.Width / 2;
            this.mainCanvas.Children.Clear();
            double min = this.Minimum, max = this.Maximum;
            int scaleAreaCount = this.Interval;
            double step=270.0/(max-min);
            for(int i = 0; i < max-min; i++)
            {
                Line lineScale = new Line();
                double angle = (i * step-45) * Math.PI / 180;
                lineScale.X1 = radius - (radius - 10) * Math.Cos(angle);    
                lineScale.Y1 = radius - (radius - 10) * Math.Sin(angle);
                if(i% scaleAreaCount == 0 || i==(max-min-1))
                {
                    lineScale.X2 = radius - (radius - 30) * Math.Cos(angle);
                    lineScale.Y2 = radius - (radius - 30) * Math.Sin(angle);
                }
                else
                {
                    lineScale.X2 = radius - (radius - 20) * Math.Cos(angle);
                    lineScale.Y2 = radius - (radius - 20) * Math.Sin(angle);
                }
                lineScale.Stroke = Brushes.White;
                lineScale.StrokeThickness = 1;
                this.mainCanvas.Children.Add(lineScale);
            }
            step = 270.0 / scaleAreaCount;
            int scaleText = (int)min;
            for(int i = 0; i <= scaleAreaCount; i++)
            {
                TextBlock textScale = new TextBlock();
                textScale.Text=(scaleText+scaleAreaCount  * i).ToString();
                textScale.Foreground = Brushes.White;
                textScale.Width = 34;
                textScale.TextAlignment = TextAlignment.Center;
                textScale.FontSize = this.ScaleTextSize;
                //textScale.Background = Brushes.Green;
                double angle = (i * step - 45) * Math.PI / 180;
                Canvas.SetLeft(textScale, radius - (radius - 40) * Math.Cos(angle)-17);
                Canvas.SetTop(textScale, radius - (radius - 40) * Math.Sin(angle)-10);
                this.mainCanvas.Children.Add(textScale);
            }

            string sData = "M{0},{1} A{0} {0} 0 1 1 {1} {2}";
            sData = string.Format(sData, radius / 2, radius, radius * 1.5);
            var converter=TypeDescriptor.GetConverter(typeof(Geometry));
            this.circle.Data= (Geometry)converter.ConvertFrom(sData);

            step = 270.0 / (max - min);
            //this.rtPointer.Angle = this.Value * step-45;
            double value=double.IsNaN(this.Value) ? 0 : this.Value;
            DoubleAnimation da=new DoubleAnimation(value * step - 45,new Duration(TimeSpan.FromMilliseconds(200)));
            this.rtPointer.BeginAnimation(RotateTransform.AngleProperty, da);

            sData = "M{0},{1} {1},{2},{1} {3}";
            sData = string.Format(sData, radius*0.3, radius, radius-8, radius + 8);
            this.pointer.Data = (Geometry)converter.ConvertFrom(sData);
        }
    }
}

4、SkeletonScreen.xaml

<UserControl x:Class="Controls.SkeletonScreen"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Controls"
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="900">
    <UserControl.Triggers>
        <EventTrigger RoutedEvent="Window.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Duration="0:0:1" To="#F7F9FA" Storyboard.TargetName="imgBlock" Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                    RepeatBehavior="Forever" AutoReverse="True"/>
                    <DoubleAnimation Duration="0:0:1.5" To="1" Storyboard.TargetName="border1" Storyboard.TargetProperty="Background.GradientStops[1].Offset"
                                    RepeatBehavior="Forever" AutoReverse="True"/>
                    <DoubleAnimation Duration="0:0:1.5" To="1" Storyboard.TargetName="border2" Storyboard.TargetProperty="Background.GradientStops[1].Offset"
                                    RepeatBehavior="Forever" AutoReverse="True"/>
                    <DoubleAnimation Duration="0:0:1.5" To="1" Storyboard.TargetName="border3" Storyboard.TargetProperty="Background.GradientStops[1].Offset"
                                    RepeatBehavior="Forever" AutoReverse="True"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </UserControl.Triggers>
    <Grid Margin="0 6">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="160"/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <!--1列图片骨架-->
        <Border Background="#DDD" Width="80" Height="65" CornerRadius="10" Name="imgBlock"/>
        <!--2列文字骨架-->
        <StackPanel Grid.Column="1" VerticalAlignment="Center">
            <!--1-->
            <Border ClipToBounds="True">
                <Border Height="16" Width="450" HorizontalAlignment="Left" Name="border1" Margin="-200 0">
                    <Border.Background>
                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                            <GradientStop Color="#DDD" Offset="0"/>
                            <GradientStop Color="#F7F9FA" Offset="0.5"/>
                            <GradientStop Color="#DDD" Offset="1"/>
                        </LinearGradientBrush>
                    </Border.Background>
                </Border>
            </Border>
            <!--2-->
            <Border ClipToBounds="True">
                <Border Height="16" Margin="-580 10" Name="border2">
                    <Border.Background>
                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                            <GradientStop Color="#DDD" Offset="0"/>
                            <GradientStop Color="#F7F9FA" Offset="0.5"/>
                            <GradientStop Color="#DDD" Offset="1"/>
                        </LinearGradientBrush>
                    </Border.Background>
                </Border>
            </Border>
            <!--3-->
            <Border ClipToBounds="True">
                <Border Height="16" Width="580" HorizontalAlignment="Left" Name="border3" Margin="-200 0">
                    <Border.Background>
                        <LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
                            <GradientStop Color="#DDD" Offset="0"/>
                            <GradientStop Color="#F7F9FA" Offset="0.5"/>
                            <GradientStop Color="#DDD" Offset="1"/>
                        </LinearGradientBrush>
                    </Border.Background>
                </Border>
            </Border>
        </StackPanel>
    </Grid>
</UserControl>

5、SkeletonScreen.xaml.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;

namespace Controls
{
    /// <summary>
    /// SkeletonScreen.xaml 的交互逻辑
    /// </summary>
    public partial class SkeletonScreen : UserControl
    {
        public SkeletonScreen()
        {
            InitializeComponent();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: WPF(Windows Presentation Foundation)是一种用于创建功能丰富的用户界面的技术,它提供了许多预定义的控件,但也支持自定义控件来满足更特定的需求。 在WPF中,我们可以通过扩展现有的控件或创建全新的控件来自定义控件。具体步骤如下: 1. 创建一个新的类,继承自WPF中的控件类(如Button、TextBox等),这个新类将成为我们自定义控件的基类。 2. 在新类中添加需要的属性、方法和事件,以满足我们的特定需求。我们可以根据需要自定义控件的外观、行为和逻辑。 3. 使用WPF的外观系统来定义自定义控件的视觉效果。可以使用XAML来定义控件的样式、模板和触发器,也可以通过代码动态创建控件。 4. 在控件的构造函数中初始化控件的默认属性和事件。 5. 可选地,可以实现控件的依赖属性,这样就可以通过绑定的方式将数据与控件关联起来。 6. 在需要使用自定义控件的地方,将控件添加到XAML布局或通过代码创建并添加到视觉树中。 通过自定义控件,我们可以实现更灵活、更具个性化的用户界面,同时提供更好的用户体验。自定义控件可以适应各种复杂的场景和需求,从而提供更具创意和创新性的用户界面设计。 总结来说,使用WPF自定义控件的过程包括创建扩展自控件基类、添加属性和事件、定义外观效果、初始化属性和事件、实现依赖属性等步骤。自定义控件能够满足特定需求,提供更灵活、个性化的用户界面。 ### 回答2: WPF(Windows Presentation Foundation)是微软推出的一种优秀的用户界面开发技术,允许开发者创建高度可定制的界面。而自定义控件则是WPF中的一种重要功能,它允许开发者根据自己的需求创建全新的用户界面元素。 自定义控件实例如下: 我们假设要创建一个自定义按钮控件,该按钮有独特的外观和交互行为。首先,我们需要在WPF应用程序中定义一个新的自定义控件类,该类继承自Button类。然后,我们可以在控件类中添加新的依赖属性以实现更多的定制化选项。 在控件类中,我们可以重写OnApplyTemplate方法以定义在控件模板中使用的可视化元素。比如,我们可以定义一个Grid作为按钮的视觉部分,并在Grid中添加一个Border作为按钮的背景。还可以添加鼠标事件处理程序以响应用户的交互动作。 接下来,我们需要在XAML文件中使用我们自定义的按钮控件。我们可以在Window或者其他容器中引用自定义按钮,并设置其属性和事件处理程序。比如,我们可以设置按钮的背景颜色为蓝色,文本为“点击我”,并为按钮的Click事件添加一个处理方法。 通过这种方式,我们可以实现一个具有独特外观和交互行为的自定义按钮控件开发者可以根据自己的需求定义更多的自定义控件,从而为用户提供更灵活和丰富的界面体验。 总而言之,WPF中的自定义控件是一种强大的功能,它允许开发者创建全新的用户界面元素,实现更高度的定制化和交互行为。通过合理地使用自定义控件开发者可以为用户提供更好的用户界面体验。 ### 回答3: WPF(Windows Presentation Foundation)是一个用于创建 Windows 桌面应用程序的开发框架,它提供了丰富的图形化用户界面(GUI)设计工具和功能。在 WPF 中,我们可以创建自定义控件来满足特定的需求。 自定义控件是指在原有的 WPF 控件基础上进行扩展或重写,以满足特定场景的需求。通过自定义控件,我们可以实现更加灵活的界面设计和交互方式。 创建自定义控件的过程包括以下几个步骤: 1. 继承现有的 WPF 控件:可以选择一个现有的 WPF 控件作为基类,然后通过扩展或重写其功能来实现自定义控件。例如,我们可以继承 Button 控件,并添加一些额外的属性和事件来实现一个特定的按钮效果。 2. 添加依赖属性:依赖属性是 WPF 中一种特殊的属性,它可以提供数据的绑定和通知机制。通过添加依赖属性,我们可以在自定义控件中定义可以被外部代码修改和引用的属性。 3. 创建控件模板:控件模板定义了控件的外观和布局方式。可以通过 XAML 或代码方式创建控件模板,并将其应用到自定义控件中。 4. 添加样式和模板绑定:可以为自定义控件添加样式,定义其在不同状态下的外观效果。同时,可以通过模板绑定将控件属性和模板中的元素进行关联。 创建完成后,我们可以在 XAML 中使用自定义控件,就像使用任何其他的 WPF 控件一样。可以设置自定义控件的属性、订阅事件,并将其嵌入到应用程序的界面中。 总的来说,WPF 的自定义控件功能可以帮助开发者实现更加灵活和个性化的用户界面设计。通过继承和扩展现有的控件,添加依赖属性和控件模板,我们可以创建出符合特定需求的自定义控件,并在应用程序中灵活使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大浪淘沙胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值