Fork me on GitHub

简易的DragDropCarousel 拖拽轮播控件

上一篇文章有写到 自动轮播的控件  简易的AutoPlayCarousel 轮播控件 - 黄高林 - 博客园 (cnblogs.com)

 

本章是基于自动轮播的一种衍生,通过拖拽鼠标进切换

直接上代码

DragDropCarousel核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
[ContentProperty(nameof(ItemSources))]
    [TemplatePart(Name = "PART_StackPanel", Type = typeof(StackPanel))]
    public class DragDropCarousel : Control
    {
        #region 字段
        private StackPanel _stkMain;
        /// <summary>
        /// 标记是否点击
        /// </summary>
        private bool _isMouseDown;
        /// <summary>
        /// 标记是否动画中,动画过程中不允许操作
        /// </summary>
        private bool _isAnimating;
        /// <summary>
        /// 最后一次点击的位置
        /// </summary>
        private Point _lastDownPoint;
        /// <summary>
        /// 移动中
        /// </summary>
        private bool _isMoving;
        #endregion
 
        #region 构造
        static DragDropCarousel()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DragDropCarousel), new FrameworkPropertyMetadata(typeof(DragDropCarousel)));
        }
        public DragDropCarousel()
        {
            Loaded += Carousel_Loaded;
            SizeChanged += Carousel_SizeChanged;
        }
        #endregion
 
        #region 属性
        /// <summary>
        /// 子控件集合
        /// </summary>
        public ObservableCollection<FrameworkElement> ItemSources
        {
            get => (ObservableCollection<FrameworkElement>)GetValue(ItemSourcesProperty);
            private set => SetValue(ItemSourcesProperty, value);
        }
 
        public static readonly DependencyProperty ItemSourcesProperty =
            DependencyProperty.Register("ItemSources", typeof(ObservableCollection<FrameworkElement>), typeof(DragDropCarousel), new PropertyMetadata(new ObservableCollection<FrameworkElement>()));
        /// <summary>
        /// 方向
        /// </summary>
        public Orientation Orientation
        {
            get => (Orientation)GetValue(OrientationProperty);
            set => SetValue(OrientationProperty, value);
        }
 
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DragDropCarousel), new PropertyMetadata(Orientation.Horizontal));
 
        /// <summary>
        /// 下标
        /// </summary>
        public int Index
        {
            get => (int)GetValue(IndexProperty);
            set => SetValue(IndexProperty, value);
        }
 
        public static readonly DependencyProperty IndexProperty =
            DependencyProperty.Register("Index", typeof(int), typeof(DragDropCarousel), new PropertyMetadata(0));
 
        /// <summary>
        /// 抬起手时,滑动切页动画持续时间
        /// </summary>
        public TimeSpan AnimateDuration
        {
            get => (TimeSpan)GetValue(AnimateDurationProperty);
            set => SetValue(AnimateDurationProperty, value);
        }
 
        public static readonly DependencyProperty AnimateDurationProperty =
            DependencyProperty.Register("AnimateDuration", typeof(TimeSpan), typeof(DragDropCarousel), new PropertyMetadata(TimeSpan.FromSeconds(0.5)));
 
        /// <summary>
        /// 移动阈值
        /// </summary>
        public double MoveThreshold
        {
            get => (double)GetValue(MoveThresholdProperty);
            set => SetValue(MoveThresholdProperty, value);
        }
 
        // Using a DependencyProperty as the backing store for MoveThreshold.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MoveThresholdProperty =
            DependencyProperty.Register("MoveThreshold", typeof(double), typeof(DragDropCarousel), new PropertyMetadata(10d));
 
 
        #endregion
 
        #region 对外方法
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _stkMain = GetTemplateChild("PART_StackPanel") as StackPanel;
        }
        #endregion
 
        #region Event Handler
 
        private void Carousel_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            foreach (FrameworkElement children in ItemSources)
            {
                children.Width = ActualWidth;
                children.Height = ActualHeight;
                UnRegisterMouseEvent(children);
                RegisterMouseEvent(children);
            }
        }
        private void UnRegisterMouseEvent(FrameworkElement children)
        {
            children.MouseDown -= Children_MouseDown;
            children.MouseMove -= Children_MouseMove;
            children.MouseUp -= Children_MouseUp;
        }
        private void RegisterMouseEvent(FrameworkElement children)
        {
            children.MouseDown += Children_MouseDown;
            children.MouseMove += Children_MouseMove;
            children.MouseUp += Children_MouseUp;
        }
 
        private void Children_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (!_isMouseDown || _isAnimating||!_isMoving)
            {<em id="__mceDel">            }<br>             _isMoving=false;
            _isMouseDown = false;
            var targetIndex = GetTargetIndex(e, _lastDownPoint);
            Index = targetIndex;
            var newThickness = new Thickness(-1 * ActualWidth * targetIndex, 0, 0, 0);
            var oldThickness = _stkMain.Margin;
            var thicknessAnimation = new ThicknessAnimation()
            {
                From = oldThickness,
                To = newThickness,
                Duration = AnimateDuration,
                EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut },
                FillBehavior = FillBehavior.Stop,
            };
            thicknessAnimation.Completed += (s, args) =>
            {
                _isAnimating = false;
                _stkMain.Margin = newThickness;
            };
            _isAnimating = true;
            _stkMain.BeginAnimation(MarginProperty, thicknessAnimation);
        }
 
        private int GetTargetIndex(MouseButtonEventArgs e, Point lastDownPoint)
        {
            var position = e.GetPosition(this);
            var vector = position - lastDownPoint;
            int targetIndex;
            if (vector.X < 0)
            {
                targetIndex = Index + 1 > (ItemSources.Count - 1) ? Index : Index + 1;
            }
            else
            {
                targetIndex = Index - 1 >= 0 ? Index - 1 : 0;
            }
            return targetIndex;
        }
 
        private void Children_MouseMove(object sender, MouseEventArgs e)
        {
 
            var position = e.GetPosition(this);
            if (!IsCanMove(_lastDownPoint, position))
            {
                _stkMain.Margin = new Thickness((-1 * ActualWidth * Index), 0, 0, 0);
                return;
            }
            var moveLength = _lastDownPoint.X - position.X;
            _stkMain.Margin = new Thickness((-1 * ActualWidth * Index - moveLength), 0, 0, 0);
        }
 
        private bool IsCanMove(Point lastPoint, Point currentPoint)
        {
            if (!_isMouseDown)
            {
                return false;
            }
 
 
            //以下控制不允许超出边界,想要回弹效果,可以去掉这部分代码
            var vector = currentPoint - lastPoint;
            if (!_isMoving && Math.Abs(vector.X) <= MoveThreshold)
            {
                return false;
            }
 
            _isMoving = true;
            if (vector.X < 0)
            {
                return Index != ItemSources.Count - 1;
            }
 
            return Index != 0;
 
        }
 
        private void Children_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!(MoveElementAttach.GetAllowMove(e.Source as UIElement) ?? false) || _isAnimating)
            {
                _isMouseDown = false;
                return;
            }
            _isMouseDown = true;
            _lastDownPoint = e.GetPosition(this);
        }
 
        private void Carousel_Loaded(object sender, RoutedEventArgs e)
        {
            if (ItemSources == null)
                return;
            Loaded -= Carousel_Loaded;
            foreach (FrameworkElement child in ItemSources)
            {
                child.Width = ActualWidth;
                child.Height = ActualHeight;
            }
        }
        #endregion
 
    }
</em>

              _isMoving = false;
            _isMouseDown = false;

                return;

  一些辅助的代码

1
2
3
4
5
6
7
8
9
10
11
public class IndexChangedEventArgs : RoutedEventArgs
    {
        public IndexChangedEventArgs(int currentIndex, RoutedEvent routedEvent) : base(routedEvent)
        {
            CurrentIndex = currentIndex;
        }
 
        public int CurrentIndex { get; set; }
    }
 
    public delegate void IndexChangedEventHandler(object sender, IndexChangedEventArgs e);

  

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>
    /// 移动附加属性
    /// </summary>
    public class MoveElementAttach : DependencyObject
    {
        /// <summary>允许操作</summary>
        public static readonly DependencyProperty AllowMoveProperty = DependencyProperty.RegisterAttached("AllowMove", typeof(bool), typeof(MoveElementAttach), new PropertyMetadata((PropertyChangedCallback)null));
 
        public static bool? GetAllowMove(DependencyObject obj) => (bool?)obj?.GetValue(MoveElementAttach.AllowMoveProperty);
 
        public static void SetAllowMove(DependencyObject obj, bool? value) => obj.SetValue(MoveElementAttach.AllowMoveProperty, (object)value);
    }

  

默认样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<sys:Double x:Key="DefaultFontSize">14</sys:Double>
    <sys:Boolean x:Key="DefaultSnapsToDevicePixels">false</sys:Boolean>
 
    <Style TargetType="{x:Type local:DragDropCarousel}">
        <Setter Property="SnapsToDevicePixels" Value="{StaticResource DefaultSnapsToDevicePixels}" />
        <Setter Property="FontSize" Value="{StaticResource DefaultFontSize}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:DragDropCarousel}">
                    <StackPanel x:Name="PART_StackPanel" Orientation="Horizontal">
                        <ItemsControl x:Name="PART_ItemsControl"   ItemsSource="{TemplateBinding ItemSources}"
                                          VerticalAlignment="Stretch"
                                          HorizontalAlignment="Stretch">
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <StackPanel Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Orientation="{Binding Orientation,RelativeSource={RelativeSource AncestorType=local:DragDropCarousel}}"/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

  

界面的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<customControl:DragDropCarousel  x:Name="Carousel"
                                Height="1080">
           <Grid Background="Red" customControl:MoveElementAttach.AllowMove="True" >
               <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="500" Width="500" Background="Black">
 
                   <TextBlock Text="1" FontSize="20" Foreground="Wheat" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
               </Grid>
           </Grid>
           <Grid Background="Green" customControl:MoveElementAttach.AllowMove="True" >
 
               <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="500" Width="500" Background="Black">
 
                   <TextBlock Text="2" FontSize="20" Foreground="Wheat" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
               </Grid>
 
           </Grid>
           <Grid Background="Yellow" customControl:MoveElementAttach.AllowMove="True"  >
               <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="500" Width="500" Background="Black">
 
                   <TextBlock Text="3" FontSize="20" Foreground="Wheat" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
               </Grid>
           </Grid>
           <Grid Background="Blue" customControl:MoveElementAttach.AllowMove="True"  >
               <Grid HorizontalAlignment="Center" VerticalAlignment="Center" Height="500" Width="500" Background="Black">
 
                   <TextBlock Text="4" FontSize="20" Foreground="Wheat" HorizontalAlignment="Center" VerticalAlignment="Center"></TextBlock>
               </Grid>
           </Grid>
       </customControl:DragDropCarousel>

  

通过 customControl:MoveElementAttach.AllowMove="True" 来标记那些控件支持拖拽,哪些不支持拖拽,这种只是临时解决方案,其实还有其他的解决方案。后续我如果要用到再持续更新

 

posted @   黄高林  阅读(83)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示