やりたいことは下記3つ。
- マウスホイールで横スクロール
- ドラッグで横スクロール
- 慣性スクロール
WPF版(Custom Control)
/// <summary>/// 慣性スクロールビューア
/// </summary>
public class MomentumScrollViewer : ScrollViewer
{
#region インスタンスフィールド
/// <summary>
/// マウスオフセット位置
/// </summary>
private double mouseOffset;
/// <summary>
/// 開始位置
/// </summary>
private double startOffset;
/// <summary>
/// プレス有無
/// </summary>
private bool isPressed = false;
/// <summary>
/// 最終位置
/// </summary>
private double lastPosition;
/// <summary>
/// 移動量
/// </summary>
private double momentumValue;
#endregion
#region 依存プロパティ
/// <summary>
/// 運動量を取得または設定します。
/// </summary>
public double MomentumValue
{
get { return (double)GetValue(MomentumValueProperty); }
set { SetValue(MomentumValueProperty, value); }
}
/// <summary>
/// 運動量依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty MomentumValueProperty =
DependencyProperty.Register("MomentumValue", typeof(double), typeof(MomentumScrollViewer), new PropertyMetadata(0.5));
/// <summary>
/// 水平位置を取得または設定します。
/// </summary>
public double HorizontalPosition
{
get { return (double)GetValue(HorizontalPositionProperty); }
set { SetValue(HorizontalPositionProperty, value); }
}
/// <summary>
/// 水平位置依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty HorizontalPositionProperty =
DependencyProperty.Register("HorizontalPosition", typeof(double), typeof(MomentumScrollViewer), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalPositionChanged)));
#endregion
#region コンストラクタ
/// <summary>
/// コンストラクタ
/// </summary>
public MomentumScrollViewer()
{
// 各イベント登録
this.PreviewMouseWheel += MomentumScrollViewer_PreviewMouseWheel;
this.Loaded += (sender, e) =>
{
var content = this.GetValue(ScrollViewer.ContentProperty) as FrameworkElement;
if (content != null)
{
content.MouseLeftButtonDown += content_MouseLeftButtonDown;
content.MouseLeftButtonUp += content_MouseLeftButtonUp;
content.MouseMove += content_MouseMove;
content.MouseLeave += content_MouseLeave;
}
};
}
#endregion
#region イベント
/// <summary>
/// 水平スクロール位置変更イベント
/// </summary>
/// <param name="obj"></param>
/// <param name="e"></param>
private static void OnHorizontalPositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = obj as MomentumScrollViewer;
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
}
}
#region ホイールでのスクロール
/// <summary>
/// マウスホイールイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MomentumScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
beginScroll(e.Delta);
e.Handled = true;
}
#endregion
#region ドラッグによるスクロール
/// <summary>
/// 左ボタンダウンイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void content_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(this);
this.mouseOffset = position.X;
this.startOffset = this.HorizontalOffset;
this.isPressed = true;
}
/// <summary>
/// マウス移動イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void content_MouseMove(object sender, MouseEventArgs e)
{
if (this.isPressed)
{
var position = e.GetPosition(this);
// 前回値からの運動量を保持し、前回値を更新
momentumValue = lastPosition - position.X;
lastPosition = position.X;
// 変化量を算出し、スクロール
var delta = (position.X > mouseOffset) ? -(position.X - mouseOffset) : mouseOffset - position.X;
this.ScrollToHorizontalOffset(startOffset + delta);
}
}
/// <summary>
/// マウスリーブイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void content_MouseLeave(object sender, MouseEventArgs e)
{
dragEnd();
}
/// <summary>
/// 左ボタンアップイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void content_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
dragEnd();
}
#endregion
#endregion
#region プライベートメソッド
/// <summary>
/// ドラッグ終了処理
/// </summary>
private void dragEnd()
{
if (this.isPressed)
{
this.isPressed = false;
// 運動量がある場合は慣性スクロール
if (momentumValue != 0)
{
beginScroll(momentumValue * (-1));
}
}
}
/// <summary>
/// 慣性スクロール開始
/// </summary>
/// <param name="delta">変化量</param>
private void beginScroll(double delta)
{
// 目的地設定
var to = HorizontalOffset - delta * MomentumValue;
if (to < 0)
to = 0;
if (to > ExtentWidth)
to = ExtentWidth;
// 慣性スクロールアニメーション開始
var animation = new DoubleAnimation(to, new Duration(TimeSpan.FromMilliseconds(1000)));
animation.EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut };
this.BeginAnimation(MomentumScrollViewer.HorizontalPositionProperty, animation);
}
#endregion
}
Sivlerlight版(Behavior)
/// <summary>/// 慣性スクロールビヘイビア
/// </summary>
public class MomentumScrollingBehavior : Behavior<ScrollViewer>
{
#region インスタンスフィールド
/// <summary>
/// マウスオフセット位置
/// </summary>
private double mouseOffset;
/// <summary>
/// 開始位置
/// </summary>
private double startOffset;
/// <summary>
/// プレス有無
/// </summary>
private bool isPressed = false;
/// <summary>
/// 最終位置
/// </summary>
private double lastPosition;
/// <summary>
/// 移動量
/// </summary>
private double momentumValue;
#endregion
#region 依存プロパティ
/// <summary>
/// 運動量を取得または設定します。
/// </summary>
public double MomentumValue
{
get { return (double)GetValue(MomentumValueProperty); }
set { SetValue(MomentumValueProperty, value); }
}
/// <summary>
/// 運動量依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty MomentumValueProperty =
DependencyProperty.Register("MomentumValue", typeof(double), typeof(MomentumScrollingBehavior), new PropertyMetadata(0.5));
/// <summary>
/// 水平位置を取得または設定します。
/// </summary>
public double HorizontalPosition
{
get { return (double)GetValue(HorizontalPositionProperty); }
set { SetValue(HorizontalPositionProperty, value); }
}
/// <summary>
/// 水平位置依存関係プロパティを識別します。
/// </summary>
public static readonly DependencyProperty HorizontalPositionProperty =
DependencyProperty.Register("HorizontalPosition", typeof(double), typeof(MomentumScrollingBehavior), new PropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalPositionChanged)));
#endregion
#region イベント
/// <summary>
/// アタッチ完了
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.MouseWheel += scrollViewer_MouseWheel;
this.AssociatedObject.Loaded += (sender, e) =>
{
var content = this.AssociatedObject.GetValue(ScrollViewer.ContentProperty) as FrameworkElement;
if (content != null)
{
content.MouseLeftButtonDown += content_MouseLeftButtonDown;
content.MouseLeftButtonUp += content_MouseLeftButtonUp;
content.MouseMove += content_MouseMove;
content.MouseLeave += content_MouseLeave;
}
};
}
/// <summary>
/// デタッチ
/// </summary>
protected override void OnDetaching()
{
this.AssociatedObject.MouseWheel -= scrollViewer_MouseWheel;
var content = this.AssociatedObject.GetValue(ScrollViewer.ContentProperty) as FrameworkElement;
if (content != null)
{
content.MouseLeftButtonDown -= content_MouseLeftButtonDown;
content.MouseLeftButtonUp -= content_MouseLeftButtonUp;
content.MouseMove -= content_MouseMove;
content.MouseLeave -= content_MouseLeave;
}
base.OnDetaching();
}
/// <summary>
/// 水平スクロール位置変更イベント
/// </summary>
/// <param name="obj"></param>
/// <param name="e"></param>
private static void OnHorizontalPositionChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var behavior = obj as MomentumScrollingBehavior;
if (behavior != null)
{
behavior.AssociatedObject.ScrollToHorizontalOffset((double)e.NewValue);
}
}
/// <summary>
/// マウスホイールイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void scrollViewer_MouseWheel(object sender, MouseWheelEventArgs e)
{
beginScroll(e.Delta);
e.Handled = true;
}
#region ドラッグによるスクロール
/// <summary>
/// 左ボタンダウンイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void content_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(this.AssociatedObject);
this.mouseOffset = position.X;
this.startOffset = this.AssociatedObject.HorizontalOffset;
this.isPressed = true;
}
/// <summary>
/// マウス移動イベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void content_MouseMove(object sender, MouseEventArgs e)
{
if (this.isPressed)
{
var position = e.GetPosition(this.AssociatedObject);
// 前回値からの運動量を保持し、前回値を更新
momentumValue = lastPosition - position.X;
lastPosition = position.X;
// 変化量を算出し、スクロール
var delta = (position.X > mouseOffset) ? -(position.X - mouseOffset) : mouseOffset - position.X;
this.AssociatedObject.ScrollToHorizontalOffset(startOffset + delta);
}
}
/// <summary>
/// マウスリーブイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void content_MouseLeave(object sender, MouseEventArgs e)
{
dragEnd();
}
/// <summary>
/// 左ボタンアップイベント
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void content_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
dragEnd();
}
#endregion
#endregion
#region プライベートメソッド
/// <summary>
/// ドラッグ終了処理
/// </summary>
private void dragEnd()
{
if (this.isPressed)
{
this.isPressed = false;
// 運動量がある場合は慣性スクロール
if (momentumValue != 0)
{
beginScroll(momentumValue * (-1));
}
}
}
/// <summary>
/// 慣性スクロール開始
/// </summary>
/// <param name="delta">変化量</param>
private void beginScroll(double delta)
{
// 目的地設定
var to = this.AssociatedObject.HorizontalOffset - delta * MomentumValue;
if (to < 0)
to = 0;
if (to > this.AssociatedObject.ExtentWidth)
to = this.AssociatedObject.ExtentWidth;
// 慣性スクロールアニメーション開始
var storyboard = new Storyboard();
storyboard.Children.Add(new DoubleAnimation()
{
From = this.AssociatedObject.HorizontalOffset,
To = to,
Duration = new Duration(TimeSpan.FromMilliseconds(1000)),
EasingFunction = new CircleEase() { EasingMode = EasingMode.EaseOut }
});
Storyboard.SetTarget(storyboard, this);
Storyboard.SetTargetProperty(storyboard, new PropertyPath("HorizontalPosition"));
storyboard.Begin();
}
#endregion
}
0 件のコメント:
コメントを投稿