Fork me on GitHub

简易的DragDropCarousel 拖拽轮播控件

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

 

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

直接上代码

DragDropCarousel核心代码

[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)
            {

              _isMoving = false;
            _isMouseDown = false;

                return;

            }
_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 }

  一些辅助的代码

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);

  

/// <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);
    }

  

默认样式

<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>

  

界面的使用

 <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 @ 2022-08-01 19:46  黄高林  阅读(77)  评论(0编辑  收藏  举报