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