using BPA.UIControl.Commons.KnownBoxes; using System; using System.Collections; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media.Animation; using System.Windows.Threading; namespace BPA.UIControl { /// /// 滑动视图 /// [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(FlipViewItem))] [TemplatePart(Name = LeftButtonName, Type = typeof(Button))] [TemplatePart(Name = RightButtonName, Type = typeof(Button))] [TemplatePart(Name = UpButtonName, Type = typeof(Button))] [TemplatePart(Name = DownButtonName, Type = typeof(Button))] [TemplatePart(Name = ContentScrollViewerName, Type = typeof(ScrollViewer))] public class FlipView : ListBox { const string LeftButtonName = "PART_LeftButton"; const string RightButtonName = "PART_RightButton"; const string UpButtonName = "PART_UpButton"; const string DownButtonName = "PART_DownButton"; const string ContentScrollViewerName = "PART_ContentScrollViewer"; private ScrollViewer scrollViewer; private DispatcherTimer timer; Storyboard horizontalStoryboard; Storyboard verticalStoryboard; private bool horizontalAnimating; private bool verticalAnimating; private bool sorting; private TouchPoint firstPoint; static FlipView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipView), new FrameworkPropertyMetadata(typeof(FlipView))); } /// protected override bool IsItemItsOwnContainerOverride(object item) { return item is FlipViewItem; } /// protected override DependencyObject GetContainerForItemOverride() { return new FlipViewItem(); } /// public override void OnApplyTemplate() { base.OnApplyTemplate(); if (GetTemplateChild(LeftButtonName) is ButtonBase leftButton) { leftButton.Click += ClickLastItem; } if (GetTemplateChild(RightButtonName) is ButtonBase rightButton) { rightButton.Click += ClickNextItem; } if (GetTemplateChild(UpButtonName) is ButtonBase upButton) { upButton.Click += ClickLastItem; } if (GetTemplateChild(DownButtonName) is ButtonBase downButton) { downButton.Click += ClickNextItem; } scrollViewer = GetTemplateChild(ContentScrollViewerName) as ScrollViewer; scrollViewer.SizeChanged += ScrollViewer_SizeChanged; timer = new DispatcherTimer(); timer.Interval = AutoPlayInterval; timer.Tick += Timer_Tick; if (IsAutoPlay) { timer.Start(); } PreviewMouseWheel += FlipView_PreviewMouseWheel; PreviewTouchDown += FlipView_PreviewTouchDown; PreviewTouchMove += FlipView_PreviewTouchMove; Loaded += FlipView_Loaded; UpdateItemSort(this); horizontalStoryboard = new Storyboard(); horizontalStoryboard.Completed += (sender, e) => { var state = horizontalStoryboard.GetCurrentState(this); if (state != ClockState.Active) { horizontalAnimating = false; var horizontalOffset = HorizontalOffset; BeginAnimation(HorizontalOffsetProperty, null); HorizontalOffset = horizontalOffset; UpdateItemSort(this); } }; verticalStoryboard = new Storyboard(); verticalStoryboard.Completed += (sender, e) => { var state = verticalStoryboard.GetCurrentState(this); if (state != ClockState.Active) { verticalAnimating = false; var verticalOffset = VerticalOffset; BeginAnimation(VerticalOffsetProperty, null); VerticalOffset = verticalOffset; UpdateItemSort(this); } }; } private void FlipView_Loaded(object sender, RoutedEventArgs e) { ScrollSelectedItemToCenter(this, noAnimation: true); } #region properties /// /// 箭头按钮样式 /// public static readonly DependencyProperty ArrowButtonStyleProperty = DependencyProperty.Register("ArrowButtonStyle", typeof(Style), typeof(FlipView), new PropertyMetadata(default(Style))); /// /// 箭头按钮样式 /// public Style ArrowButtonStyle { get { return (Style)GetValue(ArrowButtonStyleProperty); } set { SetValue(ArrowButtonStyleProperty, value); } } /// /// 方向 /// public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(FlipView), new PropertyMetadata(default(Orientation))); /// /// 方向 /// public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } /// /// 垂直偏移 /// public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register("VerticalOffset", typeof(double), typeof(FlipView), new PropertyMetadata(default(double))); /// /// 垂直偏移 /// public double VerticalOffset { get { return (double)GetValue(VerticalOffsetProperty); } set { SetValue(VerticalOffsetProperty, value); } } /// /// 水平偏移 /// public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register("HorizontalOffset", typeof(double), typeof(FlipView), new PropertyMetadata(default(double))); /// /// 水平偏移 /// public double HorizontalOffset { get { return (double)GetValue(HorizontalOffsetProperty); } set { SetValue(HorizontalOffsetProperty, value); } } /// /// 动画时长 /// public static readonly DependencyProperty AnimateDurationProperty = DependencyProperty.Register("AnimateDuration", typeof(Duration), typeof(FlipView), new PropertyMetadata(default(Duration))); /// /// 动画时长 /// public Duration AnimateDuration { get { return (Duration)GetValue(AnimateDurationProperty); } set { SetValue(AnimateDurationProperty, value); } } /// /// 动画缓动函数 /// public static readonly DependencyProperty AnimateEasingFunctionProperty = DependencyProperty.Register("AnimateEasingFunction", typeof(IEasingFunction), typeof(FlipView), new PropertyMetadata(default(IEasingFunction))); /// /// 动画缓动函数 /// public IEasingFunction AnimateEasingFunction { get { return (IEasingFunction)GetValue(AnimateEasingFunctionProperty); } set { SetValue(AnimateEasingFunctionProperty, value); } } /// /// 是否按钮浮动 /// public static readonly DependencyProperty IsButtonFloatProperty = DependencyProperty.Register("IsButtonFloat", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.TrueBox, OnIsButtonFloatChanged)); /// /// 是否按钮浮动 /// public bool IsButtonFloat { get { return (bool)GetValue(IsButtonFloatProperty); } set { SetValue(IsButtonFloatProperty, BooleanBoxes.Box(value)); } } /// /// 是否循环 /// public static readonly DependencyProperty IsLoopProperty = DependencyProperty.Register("IsLoop", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsLoopChanged)); /// /// 是否循环 /// public bool IsLoop { get { return (bool)GetValue(IsLoopProperty); } set { SetValue(IsLoopProperty, BooleanBoxes.Box(value)); } } /// /// 自动播放 /// public static readonly DependencyProperty IsAutoPlayProperty = DependencyProperty.Register("IsAutoPlay", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsAutoPlayChanged)); /// /// 自动播放 /// public bool IsAutoPlay { get { return (bool)GetValue(IsAutoPlayProperty); } set { SetValue(IsAutoPlayProperty, BooleanBoxes.Box(value)); } } /// /// 是否滚动鼠标 /// public static readonly DependencyProperty IsMouseWheelProperty = DependencyProperty.Register("IsMouseWheel", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.TrueBox)); /// /// 是否滚动鼠标 /// public bool IsMouseWheel { get { return (bool)GetValue(IsMouseWheelProperty); } set { SetValue(IsMouseWheelProperty, BooleanBoxes.Box(value)); } } /// /// 播放间隔 /// public static readonly DependencyProperty AutoPlayIntervalProperty = DependencyProperty.Register("AutoPlayInterval", typeof(TimeSpan), typeof(FlipView), new PropertyMetadata(TimeSpan.FromSeconds(3), OnAutoPlayIntervalChanged)); /// /// 播放间隔 /// public TimeSpan AutoPlayInterval { get { return (TimeSpan)GetValue(AutoPlayIntervalProperty); } set { SetValue(AutoPlayIntervalProperty, value); } } /// /// 是否连续切换 /// public static readonly DependencyProperty IsContinuousSwitchingProperty = DependencyProperty.Register("IsContinuousSwitching", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox)); /// /// 是否连续切换 /// public bool IsContinuousSwitching { get { return (bool)GetValue(IsContinuousSwitchingProperty); } set { SetValue(IsContinuousSwitchingProperty, BooleanBoxes.Box(value)); } } #endregion private void RestartTimer() { timer.Stop(); if (IsAutoPlay) { timer.Start(); } } /// protected override void OnSelectionChanged(SelectionChangedEventArgs e) { base.OnSelectionChanged(e); if (IsLoaded && !sorting && SelectedIndex >= 0) { RestartTimer(); ScrollSelectedItemToCenter(this); } if (SelectedIndex < 0 && Items.Count > 0) { SelectedIndex = 0; } } /// /// 是否禁止切换 item /// private static bool IsForbidSwitching(FlipView flipView) { return (flipView.horizontalAnimating || flipView.verticalAnimating) && !flipView.IsContinuousSwitching; } /// protected override void OnPreviewMouseDown(MouseButtonEventArgs e) { if (IsForbidSwitching(this)) { e.Handled = true; return; } } /// protected override void OnPreviewMouseMove(MouseEventArgs e) { if (IsForbidSwitching(this)) { ReleaseMouseCapture(); e.Handled = true; return; } } /// protected override void OnPreviewKeyDown(KeyEventArgs e) { if (IsForbidSwitching(this)) { e.Handled = true; return; } } /// /// 开始滚动偏移动画 /// /// 滑动视图 /// 偏移 private static void StartOffsetAnimation(FlipView flipView, double offset) { if (flipView.Orientation == Orientation.Horizontal && flipView.HorizontalOffset != offset) { HorizontalAnimation(flipView, offset, flipView.AnimateDuration); } else if (flipView.Orientation == Orientation.Vertical && flipView.VerticalOffset != offset) { VerticalAnimation(flipView, offset, flipView.AnimateDuration); } } /// /// 改变滚动偏移 /// /// 滑动视图 /// 偏移 private static void ChangeOffset(FlipView flipView, double offset) { if (flipView.Orientation == Orientation.Horizontal && flipView.scrollViewer.HorizontalOffset != offset) { flipView.HorizontalOffset = offset; } else if (flipView.Orientation == Orientation.Vertical && flipView.scrollViewer.VerticalOffset != offset) { flipView.VerticalOffset = offset; } } /// /// 获取当前偏移 /// /// 子项 /// 方向 /// 滚动视图 /// 偏移 private static double GetCurrentOffset(FlipViewItem item, Orientation orientation, ScrollViewer scrollViewer) { var point = new Point(scrollViewer.HorizontalOffset, scrollViewer.VerticalOffset); var targetPosition = item.TransformToVisual(scrollViewer).Transform(point); if (orientation == Orientation.Horizontal) { return targetPosition.X - (scrollViewer.ViewportWidth - item.ActualWidth) / 2; } else { return targetPosition.Y - (scrollViewer.ViewportHeight - item.ActualHeight) / 2; } } /// /// 滚动到当前子项 /// /// 滑动视图 /// 不使用动画 private static void ScrollSelectedItemToCenter(FlipView flipView, bool noAnimation = false) { if (!flipView.IsLoaded || flipView.SelectedIndex < 0 || IsForbidSwitching(flipView)) { return; } var item = flipView.ItemContainerGenerator.ContainerFromIndex(flipView.SelectedIndex) as FlipViewItem; var offset = GetCurrentOffset(item, flipView.Orientation, flipView.scrollViewer); if (noAnimation) { ChangeOffset(flipView, offset); } else { StartOffsetAnimation(flipView, offset); } } /// /// 更新子项排序 /// private static void UpdateItemSort(FlipView flipView) { flipView.sorting = true; var count = flipView.Items.Count; if (flipView.Items.Count > 0) { if (flipView.SelectedIndex < 0) { flipView.SelectedIndex = 0; } if (flipView.IsLoop) { int frontCount = (count - 1) / 2; // 当前选中项前面应该有多少个 int index = flipView.Items.IndexOf(flipView.SelectedItem); if (index < frontCount) // 需要向前补 item { index = flipView.Items.IndexOf(flipView.SelectedItem); int num = frontCount - index; // 补 item 数量 while (num-- > 0) { var item = flipView.Items[count - 1]; if (flipView.ItemContainerGenerator.ContainerFromItem(item) is FlipViewItem flipViewItem) { var offset = flipView.Orientation == Orientation.Horizontal ? flipView.HorizontalOffset + flipViewItem.ActualWidth : flipView.VerticalOffset + flipViewItem.ActualHeight; if (flipView.IsLoaded) { ChangeOffset(flipView, offset); } } if (flipView.ItemsSource is IList list) { list.Remove(item); list.Insert(0, item); } else { flipView.Items.Remove(item); flipView.Items.Insert(0, item); } } } else if (index > frontCount) // 需要向后补 item { int num = index - frontCount; // 补 item 数量 while (num-- > 0) { var item = flipView.Items[0]; if (flipView.ItemContainerGenerator.ContainerFromItem(item) is FlipViewItem flipViewItem) { var offset = flipView.Orientation == Orientation.Horizontal ? flipView.HorizontalOffset - flipViewItem.ActualWidth : flipView.VerticalOffset - flipViewItem.ActualHeight; if (flipView.IsLoaded) { ChangeOffset(flipView, offset); } } if (flipView.ItemsSource is IList list) { list.Remove(item); list.Insert(count - 1, item); } else { flipView.Items.Remove(item); flipView.Items.Insert(count - 1, item); } } } flipView.UpdateLayout(); ScrollSelectedItemToCenter(flipView, noAnimation: true); } } flipView.sorting = false; } /// /// 下一张 /// private void ClickNextItem(object sender, RoutedEventArgs e) { if (IsForbidSwitching(this) || SelectedIndex >= Items.Count - 1) { return; } SelectedIndex++; } /// /// 上一张 /// private void ClickLastItem(object sender, RoutedEventArgs e) { if (IsForbidSwitching(this) || SelectedIndex <= 0) { return; } SelectedIndex--; } /// /// 水平动画 /// /// 滑动视图 /// 偏移 /// 动画时长 private static void HorizontalAnimation(FlipView flipView, double offset, Duration duration) { if (offset < 0) { offset = 0; } else if (offset > flipView.scrollViewer.ScrollableWidth) { offset = flipView.scrollViewer.ScrollableWidth; } DoubleAnimation animation = flipView.horizontalStoryboard.Children.Count > 0 ? flipView.horizontalStoryboard.Children[0] as DoubleAnimation : new DoubleAnimation(); animation.From = flipView.HorizontalOffset; animation.To = offset; animation.Duration = duration; animation.EasingFunction = flipView.AnimateEasingFunction; Storyboard.SetTargetProperty(animation, new PropertyPath(HorizontalOffsetProperty)); Storyboard.SetTarget(animation, flipView); if (flipView.horizontalStoryboard.Children.Count == 0) { flipView.horizontalStoryboard.Children.Add(animation); } flipView.horizontalAnimating = true; flipView.horizontalStoryboard.Begin(flipView, isControllable: true); } /// /// 垂直动画 /// /// 滑动视图 /// 偏移 /// 动画时长 private static void VerticalAnimation(FlipView flipView, double offset, Duration duration) { if (offset < 0) { offset = 0; } else if (offset > flipView.scrollViewer.ScrollableHeight) { offset = flipView.scrollViewer.ScrollableHeight; } DoubleAnimation animation = flipView.verticalStoryboard.Children.Count > 0 ? flipView.verticalStoryboard.Children[0] as DoubleAnimation : new DoubleAnimation(); animation.From = flipView.VerticalOffset; animation.To = offset; animation.Duration = flipView.AnimateDuration; animation.EasingFunction = flipView.AnimateEasingFunction; Storyboard.SetTargetProperty(animation, new PropertyPath(VerticalOffsetProperty)); Storyboard.SetTarget(animation, flipView); if (flipView.verticalStoryboard.Children.Count == 0) { flipView.verticalStoryboard.Children.Add(animation); } flipView.verticalAnimating = true; flipView.verticalStoryboard.Begin(flipView, isControllable: true); } private static void OnIsLoopChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if ((bool)e.NewValue) { UpdateItemSort(d as FlipView); } } private static void OnIsButtonFloatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = d as FlipView; if (!flipView.IsLoaded) { return; } ScrollSelectedItemToCenter(flipView); } /// /// 定时触发翻页 /// private void Timer_Tick(object sender, EventArgs e) { ClickNextItem(this, null); } /// /// 是否自动播放改变 /// private static void OnIsAutoPlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = d as FlipView; if (flipView.IsAutoPlay) { flipView.timer?.Start(); } else { flipView.timer.Stop(); } } /// /// 自动播放间隔改变 /// private static void OnAutoPlayIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = d as FlipView; flipView.timer.Interval = flipView.AutoPlayInterval; } /// /// 滚动鼠标翻页 /// private void FlipView_PreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; if (!IsMouseWheel) { return; } if (e.Delta > 0) { ClickLastItem(null, null); } else { ClickNextItem(null, null); } } private void FlipView_PreviewTouchDown(object sender, TouchEventArgs e) { e.Handled = true; firstPoint = e.GetTouchPoint(this); } private void FlipView_PreviewTouchMove(object sender, TouchEventArgs e) { if (!IsMouseWheel || firstPoint is null) { return; } var lastPoint = e.GetTouchPoint(this); double offset; if (this.Orientation == Orientation.Horizontal) { offset = lastPoint.Position.X - firstPoint.Position.X; } else { offset = lastPoint.Position.Y - firstPoint.Position.Y; } if (offset > 30) { firstPoint = lastPoint; ClickLastItem(null, null); } else if (offset < -30) { firstPoint = lastPoint; ClickNextItem(null, null); } } /// /// 滚动区域大小变化 /// private void ScrollViewer_SizeChanged(object sender, SizeChangedEventArgs e) { if (SelectedIndex < 0 || IsForbidSwitching(this) || !(ItemContainerGenerator.ContainerFromIndex(SelectedIndex) is FlipViewItem item)) { return; } var offset = GetCurrentOffset(item, Orientation, scrollViewer); ChangeOffset(this, offset); } } }