• WPF 应用


    1. 功能

    上次在公众号看到一个转盘效果,觉得挺有意思,便也跟着实现并优化了一下。
    具体功能:

    • 将 n 个小圆沿着一个大圆的路径排列
    • 能根据 n 的数量自适应各个小圆之间的间隔
    • 定义一个大圆最多放 x 个小圆,当小圆的数量超出 x 个时,自适应沿着第二个大圆排序,大圆的半径依次递减,每个大圆上的数量也等比递减
    • 可有用户 启动/暂定 转动、变色效果

    2. 效果

    3. 实现

    3.1 布局控件

    通过继承 Panel 实现一个子元素沿着圆排列的布局控件,从而实现功能点的前 3 点。

    public class CirclePathPanel : System.Windows.Controls.Panel
    {
        public double ChildrenWidth
        {
            get { return (double)GetValue(ChildrenWidthProperty); }
            set { SetValue(ChildrenWidthProperty, value); }
        }
    
        public readonly static DependencyProperty ChildrenWidthProperty =
            DependencyProperty.Register("ChildrenWidth", typeof(double), typeof(CirclePathPanel), new PropertyMetadata(40.0));
    
        double _marginBetweenEllipse = 80;//大圆之间的距离
        List<int> _indexOfEllipse = new List<int>() { 32, 16, 8, 4 };//各个大圆上依次的小圆数量
    
        /// <summary>
        /// 获取第 n 个元素所在的环数
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        int GetLoopNum(int index)
        {
            int loopNum = 1;
    
            foreach (int n in _indexOfEllipse)
            {
                index -= n;
                if (index >= 0)
                {
                    loopNum++;
                }
            }
    
            return loopNum;
        }
    
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Children.Count > 0)
            {
                Point center = new Point(finalSize.Width / 2, finalSize.Height / 2);
    
                double outsideRadius = Math.Min(finalSize.Width, finalSize.Height) / 2.0; //最大圆的半径
    
                int flag = 32; //第一个环上的元素量,从第二环开始依次自动减半,16、8、4
                int indexOfCurrent = 0; //current 元素的在整个大集合中的索引值
                double radius; //元素所在环的半径
                double angleIncrRadians; //元素之间相隔的角度
                double angleInRadians = 0.0;//current 元素所在位置的角度
    
                foreach (UIElement child in Children)
                {
                    //确定当前索引的元素位于第几个圆
                    int loopNum = GetLoopNum(indexOfCurrent);
                    radius = outsideRadius + 40 - _marginBetweenEllipse * loopNum;                    
                     
                    //各个大圆的半径
                    if (loopNum > 1)
                    {
                        angleIncrRadians = 2.0 * Math.PI / Math.Min(Children.Count - (_indexOfEllipse[0] * 2 - _indexOfEllipse[loopNum - 2]), _indexOfEllipse[loopNum - 1]);
                    }
                    else 
                    {
                        angleIncrRadians = 2.0 * Math.PI / Math.Min(Children.Count, _indexOfEllipse[0]); ;
                    }
    
                    // 确定元素的位置
                    double x = radius * Math.Cos(angleInRadians) + center.X;
                    double y = radius * Math.Sin(angleInRadians) + center.Y;
                    Point childPosition = new Point(x, y);
                    childPosition.X -= ChildrenWidth / 2 ;
                    childPosition.Y -= ChildrenWidth / 2 ;
                                        
                    child.Arrange(new Rect(childPosition, new Size(ChildrenWidth, ChildrenWidth)));
    
                    angleInRadians += angleIncrRadians;
    
                    indexOfCurrent++;
                    
                    if (indexOfCurrent > 59) { break; }
                }
            }
            return finalSize;
        }
    }
    

    3.2 封装集合控件

    设置集合控件的布局面板为 CirclePathPanel。

    <UserControl x:Class="WpfApp1.TurnTableUserControl"
                 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:WpfApp1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800" x:Name="selfUserControl">
        <UserControl.Resources>
            <Style TargetType="ItemsControl">
                <Setter Property="Width" Value="{Binding OutSideWidth, ElementName=selfUserControl}"/>
                <Setter Property="Height" Value="{Binding OutSideWidth, ElementName=selfUserControl}"/>
                <Setter Property="Background" Value="Transparent"/>
                <Setter Property="BorderBrush" Value="LightBlue"/>
                <Setter Property="BorderThickness" Value="2"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="ItemsControl">
                            <Border 
                                BorderBrush="{TemplateBinding BorderBrush}"
                                Background="{TemplateBinding Background}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                SnapsToDevicePixels="True" 
                                CornerRadius="{Binding OutSideRadius, ElementName=selfUserControl}">
                                <Grid>
                                    <Ellipse Fill="#424242" 
                                             Width="{Binding InSideRadius, ElementName=selfUserControl}" 
                                             Height="{Binding InSideRadius, ElementName=selfUserControl}"/>
                                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"></ItemsPresenter>
                                </Grid>
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </UserControl.Resources>
    
        <ItemsControl ItemsSource="{Binding}">  
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Ellipse Width="{Binding ChildrenWidth, ElementName=selfUserControl}" 
                             Height="{Binding ChildrenWidth, ElementName=selfUserControl}" 
                             Fill="{Binding Fill}" Stroke="DeepSkyBlue" StrokeThickness="1"></Ellipse>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
    
            <ItemsControl.RenderTransform>
                <TransformGroup>
                    <RotateTransform />
                </TransformGroup>
            </ItemsControl.RenderTransform>
        </ItemsControl>
    </UserControl>
    
    
    public partial class TurnTableUserControl : UserControl
    {
        /// <summary>
        /// 最外圆直径
        /// </summary>
        public double OutSideWidth
        {
            get { return (double)GetValue(OutSideWidthProperty); }
            set { SetValue(OutSideWidthProperty, value); }
        }
    
        public readonly static DependencyProperty OutSideWidthProperty =
            DependencyProperty.Register("OutSideWidth", typeof(double), typeof(TurnTableUserControl), new PropertyMetadata(800.0));
    
        /// <summary>
        /// 最外圆半径
        /// </summary>
        public double OutSideRadius
        {
            get { return (double)GetValue(OutSideRadiusProperty); }
            set { SetValue(OutSideRadiusProperty, value); }
        }
    
        public readonly static DependencyProperty OutSideRadiusProperty =
            DependencyProperty.Register("OutSideRadius", typeof(double), typeof(TurnTableUserControl), new PropertyMetadata(400.0));
    
        /// <summary>
        /// 最内圆半径
        /// </summary>
        public double InSideRadius
        {
            get { return (double)GetValue(InSideRadiusProperty); }
            set { SetValue(InSideRadiusProperty, value); }
        }
    
        public readonly static DependencyProperty InSideRadiusProperty =
            DependencyProperty.Register("InSideRadius", typeof(double), typeof(TurnTableUserControl), new PropertyMetadata(60.0));
    
        /// <summary>
        /// 元素直径
        /// </summary>
        public double ChildrenWidth
        {
            get { return (double)GetValue(ChildrenWidthProperty); }
            set { SetValue(ChildrenWidthProperty, value); }
        }
    
        public readonly static DependencyProperty ChildrenWidthProperty =
            DependencyProperty.Register("ChildrenWidth", typeof(double), typeof(TurnTableUserControl), new PropertyMetadata(40.0));
    
        public TurnTableUserControl()
        {
            InitializeComponent();
        }
    }
    

    3.3 调用并添加小圆

    <Window x:Class="WpfApp1.Window1"
            ...>
        <Grid>
            <local:TurnTableUserControl x:Name="turnTableUC" 
                                        DataContext="{Binding MyData}"             
                                        OutSideRadius="400" OutSideWidth="800"
                                        InSideRadius="60" ChildrenWidth="50"/> 
        </Grid>
    </Window>
    
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();           
    
            this.DataContext = new EllipseChildViewModel();
        }       
    }
    
    public class NotifyPropertyChanged : System.ComponentModel.INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public void OnProperty(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = PropertyChanged;
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    
    public class EllipseChild : NotifyPropertyChanged
    {
        Brush _fill;
    
        public Brush Fill
        {
            get { return _fill; }
            set
            {
                _fill = value;
                OnProperty("Fill");
            }
        }
    }
    
    public class EllipseChildViewModel : NotifyPropertyChanged
    {
        ObservableCollection<EllipseChild> _myData;
        public ObservableCollection<EllipseChild> MyData
        {
            get { return _myData; }
            set
            {
                _myData = value;
                OnProperty("MyData");
            }
        }
              
        public EllipseChildViewModel()
        {
            MyData = GetData();
        }
    
        public ObservableCollection<EllipseChild> GetData()
        {
            ObservableCollection<EllipseChild> mydata = new ObservableCollection<EllipseChild>();
    
            foreach (int _ in Enumerable.Range(0, 10))
            {
                mydata.Add(new EllipseChild() { Fill = new SolidColorBrush(Color.FromRgb(76, 175, 80)) });
                mydata.Add(new EllipseChild() { Fill = new SolidColorBrush(Colors.Transparent) });
                mydata.Add(new EllipseChild() { Fill = new SolidColorBrush(Color.FromRgb(68, 138, 254)) });
                ...
            }
            return mydata;
        }
    }
    

    至此,就实现了前 3 个功能点:


    3.4 实现转动、变色效果

    <Window x:Class="WpfApp1.Window1"
           ...>
        <Grid>
            <StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 0">
                <Button Content="转" Click="Button_Click_rorate" Height="30" Width="100" Margin="0 10" Background="Transparent" BorderBrush="DarkSalmon"/>
                <Button Content="变" Command="{Binding ChangeCommand}" Height="30" Width="100"  Background="Transparent" BorderBrush="DarkSalmon"/>
            </StackPanel>
            
            <local:TurnTableUserControl x:Name="turnTableUC" 
                                        DataContext="{Binding MyData}"             
                                        OutSideRadius="400" OutSideWidth="800"
                                        InSideRadius="60" ChildrenWidth="50"/> 
        </Grid>
    </Window>
    
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();           
    
            this.DataContext = new EllipseChildViewModel();
        }       
    
        private void Button_Click_rorate(object sender, RoutedEventArgs e)
        {
            turnTableUC.IsActionTurn = !turnTableUC.IsActionTurn;
        }
    }
    
    public class EllipseChildViewModel : NotifyPropertyChanged
    {
        ...
        public DelegateCommand ChangeCommand { get; set; }
    
        public EllipseChildViewModel()
        {
            ChangeCommand = new DelegateCommand() { ExecuteCommand = new Action<object>(ChangeColor) };
            MyData = GetData();
        }
    
        CancellationTokenSource tokenSource;
        CancellationToken token;
    
        public void ChangeColor(object obj)
        {         
            if (tokenSource == null || token.IsCancellationRequested)
            {
                tokenSource = new CancellationTokenSource();
                token = tokenSource.Token;
                Change(500);
            }             
            else
            {
                tokenSource.Cancel();
            }
        }
        
        /// <summary>
        /// 启动/停止变色,当启动变色时,每隔 milliseconds 变一次色
        /// </summary>
        /// <param name="milliseconds">每次变色的时间间隔,单位毫秒</param>
        public void Change(int milliseconds)
        {            
            Task.Factory.StartNew(async () =>
            {
                try
                {
                    while (true)
                    {
                        if (token.IsCancellationRequested) return;
        
                        App.Current.Dispatcher.Invoke(() =>
                        {
                            Random rd = new Random();
                            foreach (EllipseChild ec in MyData)
                            {
                                ec.Fill = new SolidColorBrush(Color.FromRgb((byte)rd.Next(0, 256), (byte)rd.Next(0, 256), (byte)rd.Next(0, 256)));
                            }
                        });
        
                        await Task.Delay(milliseconds);
                    }
                }
                catch (Exception ex) {}
            });
        }
    }
    
    public partial class TurnTableUserControl : UserControl
    {
        ...
        
        /// <summary>
        /// 启动、暂停动画
        /// </summary>
        public bool IsActionTurn
        {
            get { return (bool)GetValue(IsActionTurnProperty); }
            set { SetValue(IsActionTurnProperty, value); }
        }
    
        public readonly static DependencyProperty IsActionTurnProperty =
            DependencyProperty.Register("IsActionTurn", typeof(bool), typeof(TurnTableUserControl),
                new PropertyMetadata(false, new PropertyChangedCallback(ChangedActionStatus)));
    
        /// <summary>
        /// 是否首次启动动画
        /// </summary>
        static bool _isFirstTimeActionStoryboard = true;
    
        /// <summary>
        /// 控件动画
        /// </summary>
        static Storyboard _storyboard;
    
        public TurnTableUserControl()
        {
            InitializeComponent();
            _storyboard = itemsControl.FindResource("storyboard") as Storyboard;
        }
    
        static void ChangedActionStatus(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {           
            if ((bool)e.NewValue == true)
            {
                
                if (_isFirstTimeActionStoryboard)
                {//首次启动动画
                    _storyboard.Begin();
                    _isFirstTimeActionStoryboard = !_isFirstTimeActionStoryboard;
                }
                else
                {//恢复动画
                    _storyboard.Resume();
                }
            }
            else
            {//暂停动画                
                _storyboard.Pause();
            }
        }
    }
    
  • 相关阅读:
    LeetCode——376.摆动序列
    kaggle——分销商产品未来销售情况预测
    LeetCode——264. 丑数 II
    LeetCode——71.简化路径
    LeetCode——15. 三数之和
    kaggle——NFL Big Data Bowl 2020 Official Starter Notebook
    LeetCode——199. 二叉树的右视图
    数据结构与算法——哈希函数和哈希表等(2)
    数据结构与算法——哈希函数与哈希表等(1)
    Python——Pandas 时间序列数据处理
  • 原文地址:https://www.cnblogs.com/MichaelLoveSna/p/14495651.html
Copyright © 2020-2023  润新知