|
- 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
- {
- /// <summary>
- /// 滑动视图
- /// </summary>
- [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)));
- }
-
- /// <inheritdoc/>
- protected override bool IsItemItsOwnContainerOverride(object item)
- {
- return item is FlipViewItem;
- }
-
- /// <inheritdoc/>
- protected override DependencyObject GetContainerForItemOverride()
- {
- return new FlipViewItem();
- }
-
- /// <inheritdoc/>
- 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
-
- /// <summary>
- /// 箭头按钮样式
- /// </summary>
- public static readonly DependencyProperty ArrowButtonStyleProperty =
- DependencyProperty.Register("ArrowButtonStyle", typeof(Style), typeof(FlipView), new PropertyMetadata(default(Style)));
-
- /// <summary>
- /// 箭头按钮样式
- /// </summary>
- public Style ArrowButtonStyle
- {
- get { return (Style)GetValue(ArrowButtonStyleProperty); }
- set { SetValue(ArrowButtonStyleProperty, value); }
- }
-
- /// <summary>
- /// 方向
- /// </summary>
- public static readonly DependencyProperty OrientationProperty =
- DependencyProperty.Register("Orientation", typeof(Orientation), typeof(FlipView), new PropertyMetadata(default(Orientation)));
-
- /// <summary>
- /// 方向
- /// </summary>
- public Orientation Orientation
- {
- get { return (Orientation)GetValue(OrientationProperty); }
- set { SetValue(OrientationProperty, value); }
- }
-
- /// <summary>
- /// 垂直偏移
- /// </summary>
- public static readonly DependencyProperty VerticalOffsetProperty =
- DependencyProperty.Register("VerticalOffset", typeof(double), typeof(FlipView), new PropertyMetadata(default(double)));
-
- /// <summary>
- /// 垂直偏移
- /// </summary>
- public double VerticalOffset
- {
- get { return (double)GetValue(VerticalOffsetProperty); }
- set { SetValue(VerticalOffsetProperty, value); }
- }
-
- /// <summary>
- /// 水平偏移
- /// </summary>
- public static readonly DependencyProperty HorizontalOffsetProperty =
- DependencyProperty.Register("HorizontalOffset", typeof(double), typeof(FlipView), new PropertyMetadata(default(double)));
-
- /// <summary>
- /// 水平偏移
- /// </summary>
- public double HorizontalOffset
- {
- get { return (double)GetValue(HorizontalOffsetProperty); }
- set { SetValue(HorizontalOffsetProperty, value); }
- }
-
- /// <summary>
- /// 动画时长
- /// </summary>
- public static readonly DependencyProperty AnimateDurationProperty =
- DependencyProperty.Register("AnimateDuration", typeof(Duration), typeof(FlipView), new PropertyMetadata(default(Duration)));
-
- /// <summary>
- /// 动画时长
- /// </summary>
- public Duration AnimateDuration
- {
- get { return (Duration)GetValue(AnimateDurationProperty); }
- set { SetValue(AnimateDurationProperty, value); }
- }
-
- /// <summary>
- /// 动画缓动函数
- /// </summary>
- public static readonly DependencyProperty AnimateEasingFunctionProperty =
- DependencyProperty.Register("AnimateEasingFunction", typeof(IEasingFunction), typeof(FlipView), new PropertyMetadata(default(IEasingFunction)));
-
- /// <summary>
- /// 动画缓动函数
- /// </summary>
- public IEasingFunction AnimateEasingFunction
- {
- get { return (IEasingFunction)GetValue(AnimateEasingFunctionProperty); }
- set { SetValue(AnimateEasingFunctionProperty, value); }
- }
-
- /// <summary>
- /// 是否按钮浮动
- /// </summary>
- public static readonly DependencyProperty IsButtonFloatProperty =
- DependencyProperty.Register("IsButtonFloat", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.TrueBox, OnIsButtonFloatChanged));
-
- /// <summary>
- /// 是否按钮浮动
- /// </summary>
- public bool IsButtonFloat
- {
- get { return (bool)GetValue(IsButtonFloatProperty); }
- set { SetValue(IsButtonFloatProperty, BooleanBoxes.Box(value)); }
- }
-
- /// <summary>
- /// 是否循环
- /// </summary>
- public static readonly DependencyProperty IsLoopProperty =
- DependencyProperty.Register("IsLoop", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsLoopChanged));
-
- /// <summary>
- /// 是否循环
- /// </summary>
- public bool IsLoop
- {
- get { return (bool)GetValue(IsLoopProperty); }
- set { SetValue(IsLoopProperty, BooleanBoxes.Box(value)); }
- }
-
- /// <summary>
- /// 自动播放
- /// </summary>
- public static readonly DependencyProperty IsAutoPlayProperty =
- DependencyProperty.Register("IsAutoPlay", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox, OnIsAutoPlayChanged));
-
- /// <summary>
- /// 自动播放
- /// </summary>
- public bool IsAutoPlay
- {
- get { return (bool)GetValue(IsAutoPlayProperty); }
- set { SetValue(IsAutoPlayProperty, BooleanBoxes.Box(value)); }
- }
-
- /// <summary>
- /// 是否滚动鼠标
- /// </summary>
- public static readonly DependencyProperty IsMouseWheelProperty =
- DependencyProperty.Register("IsMouseWheel", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.TrueBox));
-
- /// <summary>
- /// 是否滚动鼠标
- /// </summary>
- public bool IsMouseWheel
- {
- get { return (bool)GetValue(IsMouseWheelProperty); }
- set { SetValue(IsMouseWheelProperty, BooleanBoxes.Box(value)); }
- }
-
- /// <summary>
- /// 播放间隔
- /// </summary>
- public static readonly DependencyProperty AutoPlayIntervalProperty =
- DependencyProperty.Register("AutoPlayInterval", typeof(TimeSpan), typeof(FlipView), new PropertyMetadata(TimeSpan.FromSeconds(3), OnAutoPlayIntervalChanged));
-
- /// <summary>
- /// 播放间隔
- /// </summary>
- public TimeSpan AutoPlayInterval
- {
- get { return (TimeSpan)GetValue(AutoPlayIntervalProperty); }
- set { SetValue(AutoPlayIntervalProperty, value); }
- }
-
- /// <summary>
- /// 是否连续切换
- /// </summary>
- public static readonly DependencyProperty IsContinuousSwitchingProperty =
- DependencyProperty.Register("IsContinuousSwitching", typeof(bool), typeof(FlipView), new PropertyMetadata(BooleanBoxes.FalseBox));
-
- /// <summary>
- /// 是否连续切换
- /// </summary>
- public bool IsContinuousSwitching
- {
- get { return (bool)GetValue(IsContinuousSwitchingProperty); }
- set { SetValue(IsContinuousSwitchingProperty, BooleanBoxes.Box(value)); }
- }
- #endregion
-
- private void RestartTimer()
- {
- timer.Stop();
- if (IsAutoPlay)
- {
- timer.Start();
- }
- }
-
- /// <inheritdoc/>
- 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;
- }
- }
-
- /// <summary>
- /// 是否禁止切换 item
- /// </summary>
- private static bool IsForbidSwitching(FlipView flipView)
- {
- return (flipView.horizontalAnimating || flipView.verticalAnimating) && !flipView.IsContinuousSwitching;
- }
-
- /// <inheritdoc/>
- protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
- {
- if (IsForbidSwitching(this))
- {
- e.Handled = true;
- return;
- }
- }
-
- /// <inheritdoc/>
- protected override void OnPreviewMouseMove(MouseEventArgs e)
- {
- if (IsForbidSwitching(this))
- {
- ReleaseMouseCapture();
- e.Handled = true;
- return;
- }
- }
-
- /// <inheritdoc/>
- protected override void OnPreviewKeyDown(KeyEventArgs e)
- {
- if (IsForbidSwitching(this))
- {
- e.Handled = true;
- return;
- }
- }
-
- /// <summary>
- /// 开始滚动偏移动画
- /// </summary>
- /// <param name="flipView">滑动视图</param>
- /// <param name="offset">偏移</param>
- 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);
- }
- }
-
- /// <summary>
- /// 改变滚动偏移
- /// </summary>
- /// <param name="flipView">滑动视图</param>
- /// <param name="offset">偏移</param>
- 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;
- }
- }
-
- /// <summary>
- /// 获取当前偏移
- /// </summary>
- /// <param name="item">子项</param>
- /// <param name="orientation">方向</param>
- /// <param name="scrollViewer">滚动视图</param>
- /// <returns>偏移</returns>
- 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;
- }
- }
-
- /// <summary>
- /// 滚动到当前子项
- /// </summary>
- /// <param name="flipView">滑动视图</param>
- /// <param name="noAnimation">不使用动画</param>
- 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);
- }
- }
-
- /// <summary>
- /// 更新子项排序
- /// </summary>
- 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;
- }
-
- /// <summary>
- /// 下一张
- /// </summary>
- private void ClickNextItem(object sender, RoutedEventArgs e)
- {
- if (IsForbidSwitching(this) || SelectedIndex >= Items.Count - 1)
- {
- return;
- }
-
- SelectedIndex++;
- }
-
- /// <summary>
- /// 上一张
- /// </summary>
- private void ClickLastItem(object sender, RoutedEventArgs e)
- {
- if (IsForbidSwitching(this) || SelectedIndex <= 0)
- {
- return;
- }
-
- SelectedIndex--;
- }
-
- /// <summary>
- /// 水平动画
- /// </summary>
- /// <param name="flipView">滑动视图</param>
- /// <param name="offset">偏移</param>
- /// <param name="duration">动画时长</param>
- 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);
- }
-
- /// <summary>
- /// 垂直动画
- /// </summary>
- /// <param name="flipView">滑动视图</param>
- /// <param name="offset">偏移</param>
- /// <param name="duration">动画时长</param>
- 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);
- }
-
- /// <summary>
- /// 定时触发翻页
- /// </summary>
- private void Timer_Tick(object sender, EventArgs e)
- {
- ClickNextItem(this, null);
- }
-
- /// <summary>
- /// 是否自动播放改变
- /// </summary>
- private static void OnIsAutoPlayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var flipView = d as FlipView;
-
- if (flipView.IsAutoPlay)
- {
- flipView.timer?.Start();
- }
- else
- {
- flipView.timer.Stop();
- }
- }
-
- /// <summary>
- /// 自动播放间隔改变
- /// </summary>
- private static void OnAutoPlayIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var flipView = d as FlipView;
- flipView.timer.Interval = flipView.AutoPlayInterval;
- }
-
- /// <summary>
- /// 滚动鼠标翻页
- /// </summary>
- 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);
- }
- }
-
- /// <summary>
- /// 滚动区域大小变化
- /// </summary>
- 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);
- }
- }
- }
|