WPF 实现拟物音量控件

2023-06-02 10:31:28 +08:00
 yanjinhua

WPF 实现拟物音量控件

控件名:Wheel

作 者:WPFDevelopersOrg - 俞宏伟

原文链接https://github.com/WPFDevelopersOrg/SimulationControl

1 )创建 Wheel.xaml 代码如下:

<UserControl x:Class="TopControl.Wheel"
             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:TopControl"
             mc:Ignorable="d" 
             Width="30" Height="180">
    <Grid>
        <!-- 进度 -->
        <Grid Width="2" Background="#0a0a0a" Height="180" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
        <Grid Width="2" x:Name="Grid_Value" Height="0" HorizontalAlignment="Left" VerticalAlignment="Bottom">
            <Grid Height="180" VerticalAlignment="Bottom">
                <Grid.Background>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                        <GradientStop Offset="0" Color="#f0d967"/>
                        <GradientStop Offset="1" Color="#33b08d"/>
                    </LinearGradientBrush>
                </Grid.Background>
            </Grid>
        </Grid>
        <Grid Background="#0a0a0a" Width="26" HorizontalAlignment="Right" Height="180" Margin="2,0,0,0"/>
        <!-- 滚轮 -->
        <Grid x:Name="WheelArea" Height="176" Width="22" HorizontalAlignment="Right" Margin="0,0,2,0"
              MouseDown="WheelArea_MouseDown" MouseMove="WheelArea_MouseMove" MouseUp="WheelArea_MouseUp" MouseWheel="WheelArea_MouseWheel">
            <Grid.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
                    <GradientStop Color="#141414" Offset="0"/>
                    <GradientStop Color="#3c3c3c" Offset="0.5"/>
                    <GradientStop Color="#141414" Offset="1"/>
                </LinearGradientBrush>
            </Grid.Background>
            <Grid x:Name="LayerBox" IsHitTestVisible="False"/>
        </Grid>
    </Grid>
</UserControl>

2 )创建 Wheel.xaml.cs 代码如下:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace TopControl
{
    public partial class Wheel : UserControl
    {
        public Wheel()
        {
            InitializeComponent();
            Loaded += Wheel_Loaded;
        }

        #region 属性

        public int MinValue { get; set; } = -720;

        public int MaxValue { get; set; } = 720;

        public int Value { get; set; } = -720;

        #endregion

        #region 控件事件

        private void Wheel_Loaded(object sender, RoutedEventArgs e)
        {
            InitLayer();
        }

        private void WheelArea_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left)
            {
                BeginDrag();
            }
        }

        private void WheelArea_MouseMove(object sender, MouseEventArgs e)
        {
            if (_dragMode)
            {
                Drag();
            }
        }

        private void WheelArea_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton == MouseButton.Left && _dragMode)
            {
                EndDrag();
            }
        }

        private void WheelArea_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (_dragMode) return;

            int offset = e.Delta / 120;
            if (offset < 0 && Value > MinValue)
            {
                Value += offset;
                UpdateProgress();
                _wheelLayer.AngleOffset -= offset * _wheelSpeed;
                _wheelLayer.UpdateLayer();
            }
            else if (offset > 0 && Value < MaxValue)
            {
                Value += offset;
                UpdateProgress();
                _wheelLayer.AngleOffset -= offset * _wheelSpeed;
                _wheelLayer.UpdateLayer();
            }
        }

        #endregion

        #region 鼠标操作

        private void BeginDrag()
        {
            _dragMode = true;
            WheelArea.CaptureMouse();

            _dragStart = Mouse.GetPosition(WheelArea);
            _angleStart = _wheelLayer.AngleOffset;

            _valueStart = Value;
            _offsetDown = Value - MinValue;
            _offsetUp = Value - MaxValue;
        }

        private void Drag()
        {
            double offset_y = Mouse.GetPosition(WheelArea).Y - _dragStart.Y;
            if (offset_y < _offsetUp) offset_y = _offsetUp;
            else if (offset_y > _offsetDown) offset_y = _offsetDown;

            double offset_angle = offset_y * _wheelSpeed;
            Value = _valueStart - (int)offset_y;
            _wheelLayer.AngleOffset = _angleStart + offset_angle;
            UpdateProgress();
            _wheelLayer.UpdateLayer();
        }

        private void EndDrag()
        {
            _dragMode = false;
            WheelArea.ReleaseMouseCapture();
        }

        #endregion

        #region 私有方法

        /// <summary>
        /// 初始化图层
        /// </summary>
        private void InitLayer()
        {
            _wheelLayer.Width = LayerBox.ActualWidth;
            _wheelLayer.Height = LayerBox.ActualHeight;
            LayerBox.Children.Add(_wheelLayer);
            _wheelLayer.Init();
            _wheelLayer.UpdateLayer();
        }

        /// <summary>
        /// 更新进度
        /// </summary>
        private void UpdateProgress()
        {
            Grid_Value.Height = (double)(Value - MinValue) / (MaxValue - MinValue) * 180;
        }

        #endregion

        #region 字段

        private readonly WheelLayer _wheelLayer = new WheelLayer();

        private Point _dragStart = new Point();
        private double _angleStart = 0;
        private int _valueStart = 0;
        private bool _dragMode = false;

        /// <summary>滚轮速度</summary>
        private readonly double _wheelSpeed = 0.7;

        /// <summary>最大向上偏移</summary>
        private double _offsetUp;
        /// <summary>最大向下偏移</summary>
        private double _offsetDown;

        #endregion
    }
}

3 )创建 WheelLayer.cs 代码如下:

using MathUtil;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Media;
using WpfUtil;

namespace TopControl
{
    public class WheelLayer : SingleLayer
    {
        #region 属性

        /// <summary>槽高度:180px - 上下边框( 4px )</summary>
        public int GrooveHeight { get; set; } = 176;

        /// <summary>角度:圆弧切线与槽边的夹角</summary>
        public int Angle { get; set; } = 90;

        /// <summary>刻度线数量</summary>
        public int LineCount { get; set; } = 90;

        /// <summary>角度偏移</summary>
        public double AngleOffset { get; set; } = 0;

        #endregion

        #region 公开方法

        public override void Init()
        {
            // 起点、终点、中点
            Point2D startPoint = new Point2D(0, 0);
            Point2D endPoint = new Point2D(GrooveHeight, 0);
            Point2D centerPoint = startPoint.CenterWith(endPoint);
            // 向量:中点 -> 起点
            Vector2 centerToStart = new Vector2(centerPoint, startPoint);
            centerToStart.Rotate(-90);
            // 向量:终点 -> 中点
            Vector2 endToCenter = new Vector2(endPoint, centerPoint);
            endToCenter.Rotate(-90 + Angle);
            // 圆心
            _circleCenter = centerToStart.IntersectionWith(endToCenter);
            // 向量:圆心 -> 起点
            Vector2 vector = new Vector2(_circleCenter, startPoint);
            _radius = vector.Distance;
            _anglePerLine = 360.0 / LineCount;
        }

        protected override void OnUpdate()
        {
            // 最高点
            Point2D top = new Point2D(_circleCenter.X, _circleCenter.Y - _radius);
            // 向量:圆心 -> 最高点
            Vector2 vector = new Vector2(_circleCenter, top);
            double max = Math.Abs(vector.Target.Y);
            // 偏移角度
            vector.Rotate(AngleOffset);
            // 开始旋转计算刻度位置
            List<Point2D> pointList = new List<Point2D>();
            for (int counter = 0; counter < LineCount; counter++)
            {
                if (vector.Target.Y < 0) pointList.Add(vector.Target);
                vector.Rotate(-_anglePerLine);
            }

            // 绘制刻度线
            foreach (var item in pointList)
                DrawHorizontalLine(item.X, Math.Abs(item.Y) / max);
        }

        #endregion

        #region 私有方法

        /// <summary>
        /// 绘制水平线
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void DrawHorizontalLine(double y, double opacity)
        {
            Pen pen = new Pen(new SolidColorBrush(Color.FromArgb((byte)(opacity * 255), 32, 32, 32)), 1);
            Pen pen2 = new Pen(new SolidColorBrush(Color.FromArgb((byte)(opacity * 255), 64, 64, 64)), 1);
            _dc.DrawLine(pen, new Point(2, y - 0.5), new Point(Width - 2, y - 0.5));
            _dc.DrawLine(pen2, new Point(2, y + 0.5), new Point(Width - 2, y + 0.5));
        }

        #endregion

        #region 字段

        /// <summary>圆心</summary>
        private Point2D _circleCenter = new Point2D(0, 0);
        /// <summary>半径</summary>
        private double _radius = 0;
        /// <summary>刻度线之间的夹角</summary>
        private double _anglePerLine = 0;

        #endregion
    }
}

4 )创建 WheelExample.xaml 代码如下:

<wd:Window x:Class="TopControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TopControl"
        xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
        mc:Ignorable="d" Background="#1e1e1e"
        Title="https://github.com/WPFDevelopersOrg/SimulationControl - 俞宏伟" Height="450" Width="800">
    <Grid>
        <local:Wheel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="50"/>
    </Grid>
</wd:Window>

更多代码请访问GitHub源码地址

1300 次点击
所在节点    C#
2 条回复
Soar360
2023-08-20 17:37:26 +08:00
支持支持
yanjinhua
140 天前
@Soar360 感谢大佬 666

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/945156

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX