読者です 読者をやめる 読者になる 読者になる

しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

スワイプで数値の増減ができる UI

C# Windows ランタイムアプリ Windows ストアアプリ XAML

円弧シェイプを利用した数値の表現 - しっぽを追いかけて では Button で増減させていましたが、タッチ操作しやすくさせるためスワイプで増減できるようにしてみます!

f:id:matatabi_ux:20140816030257p:plain

まずは XAML を修正

<UserControl x:Class="ShapeSample.Controls.ArcChart"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ctrl="using:ShapeSample.Controls"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             FontSize="56"
             FontWeight="Bold"
             Foreground="#FFE3E3E3"
             ManipulationCompleted="OnManipulationCompleted"
             ManipulationDelta="OnManipulationDelta"
             mc:Ignorable="d">

    <UserControl.ManipulationMode>
        <ManipulationModes>TranslateX,TranslateY</ManipulationModes>
    </UserControl.ManipulationMode>

    <UserControl.Resources>
        <Storyboard x:Name="ValueChangeAnimation">
            <DoubleAnimationUsingKeyFrames Duration="0:0:0.7"
                                           EnableDependentAnimation="True"
                                           Storyboard.TargetName="Arc"
                                           Storyboard.TargetProperty="(ctrl:Arc.EndAngle)">
                <DiscreteDoubleKeyFrame KeyTime="0" Value="120" />
                <EasingDoubleKeyFrame KeyTime="0:0:0.7" Value="420">
                    <EasingDoubleKeyFrame.EasingFunction>
                        <QuarticEase EasingMode="EaseOut" />
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            </DoubleAnimationUsingKeyFrames>
        </Storyboard>
    </UserControl.Resources>

    <Grid x:Name="Chart" Background="Transparent">
        <Grid Margin="15,15,0,0"
              HorizontalAlignment="Center"
              VerticalAlignment="Center">
            <ctrl:Arc x:Name="BackgroundArc"
                      EndAngle="420"
                      StartAngle="120"
                      Stroke="#FF1A1A1A"
                      StrokeThickness="30" />
            <ctrl:Arc x:Name="Arc"
                      EndAngle="120"
                      StartAngle="120"
                      StrokeThickness="30" />
        </Grid>
        <TextBlock x:Name="Number"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center" />
    </Grid>
</UserControl>

ManipulationMode に TranslateX と TranslateY を設定して、ManipulationDelta と ManipulationCompleted のイベントハンドラを追加しました

地味に重要なのがここの修正

    <Grid x:Name="Chart" Background="Transparent">

Grid の Background に Transparent を設定することで背景部分がタッチに反応するようにします

次にコードビハインドも修正します

/// <summary>
/// 円弧型チャート
/// </summary>
public sealed partial class ArcChart : UserControl
{
    #region Privates

    /// <summary>
    /// 移動距離
    /// </summary>
    private Point manipulationGap = new Point(0, 0);

    #endregion //Privates

    #region Value 依存関係プロパティ

    ~ 前回と変更ないので中略 ~

    #endregion //Stroke 依存関係プロパティ

    #region HorizontalThreshold 依存関係プロパティ
    /// <summary>
    /// HorizontalThreshold 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty HorizontalThresholdProperty
        = DependencyProperty.Register(
        "HorizontalThreshold",
        typeof(double),
        typeof(ArcChart),
        new PropertyMetadata(
            60d));

    /// <summary>
    /// 水平移動距離のしきい値
    /// </summary>
    public double HorizontalThreshold
    {
        get { return (double)this.GetValue(HorizontalThresholdProperty); }
        set { this.SetValue(HorizontalThresholdProperty, value); }
    }
    #endregion //HorizontalThreshold 依存関係プロパティ

    #region VerticalThreshold 依存関係プロパティ
    /// <summary>
    /// VerticalThreshold 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty VerticalThresholdProperty
        = DependencyProperty.Register(
        "VerticalThreshold",
        typeof(double),
        typeof(ArcChart),
        new PropertyMetadata(
            40d));

    /// <summary>
    /// 垂直移動距離のしきい値
    /// </summary>
    public double VerticalThreshold
    {
        get { return (double)this.GetValue(VerticalThresholdProperty); }
        set { this.SetValue(VerticalThresholdProperty, value); }
    }
    #endregion //VerticalThreshold 依存関係プロパティ

    #region HorizontalPlay 依存関係プロパティ
    /// <summary>
    /// HorizontalPlay 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty HorizontalPlayProperty
        = DependencyProperty.Register(
        "HorizontalPlay",
        typeof(double),
        typeof(ArcChart),
        new PropertyMetadata(
            20d));

    /// <summary>
    /// 水平移動距離のあそび値
    /// </summary>
    public double HorizontalPlay
    {
        get { return (double)this.GetValue(HorizontalPlayProperty); }
        set { this.SetValue(HorizontalPlayProperty, value); }
    }
    #endregion //HorizontalPlay 依存関係プロパティ

    #region VerticalPlay 依存関係プロパティ
    /// <summary>
    /// VerticalPlay 依存関係プロパティ
    /// </summary>
    public static readonly DependencyProperty VerticalPlayProperty
        = DependencyProperty.Register(
        "VerticalPlay",
        typeof(double),
        typeof(ArcChart),
        new PropertyMetadata(
            20d));

    /// <summary>
    /// 垂直移動距離のあそび値
    /// </summary>
    public double VerticalPlay
    {
        get { return (double)this.GetValue(VerticalPlayProperty); }
        set { this.SetValue(VerticalPlayProperty, value); }
    }
    #endregion //VerticalPlay 依存関係プロパティ

    /// <summary>
    /// 値変更時イベント
    /// </summary>
    public event DoubleValueChangedEventHandler ValueChanging;

    /// <summary>
    /// 値変更後イベント
    /// </summary>
    public event DoubleValueChangedEventHandler ValueChanged;

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ArcChart()
    {
        this.InitializeComponent();

        this.OnValueChanging(this, new DoubleValueChangedEventArgs(this.Value, this.MinValue));
    }

    ~ 前回と変更ないので中略 ~

    /// <summary>
    /// ドラッグ移動時の処理
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
    {
        // 水平か垂直かどちらか一方だけに平行移動するようにする
        if (this.ManipulationMode == ManipulationModes.TranslateX || Math.Abs(e.Delta.Translation.X) > Math.Abs(e.Delta.Translation.Y))
        {
            this.manipulationGap.X += e.Delta.Translation.X;
            this.manipulationGap.Y = 0;
            this.ManipulationMode = ManipulationModes.TranslateX;
            e.Handled = true;
        }
        else if (this.ManipulationMode == ManipulationModes.TranslateY || Math.Abs(e.Delta.Translation.X) <= Math.Abs(e.Delta.Translation.Y))
        {
            this.manipulationGap.X = 0;
            this.manipulationGap.Y += e.Delta.Translation.Y;
            this.ManipulationMode = ManipulationModes.TranslateY;
            e.Handled = true;
        }

        // しきい値 + あそび値 を越えて移動しないようにする
        if (Math.Abs(this.manipulationGap.X) > this.HorizontalThreshold + this.HorizontalPlay)
        {
            this.manipulationGap.X = (this.HorizontalThreshold + this.HorizontalPlay) * Math.Sign(this.manipulationGap.X);
        }
        if (Math.Abs(this.manipulationGap.Y) > this.VerticalThreshold + this.VerticalPlay)
        {
            this.manipulationGap.Y = (this.VerticalThreshold + this.VerticalPlay) * Math.Sign(this.manipulationGap.Y);
        }

        // 平行移動させる
        this.Chart.RenderTransform = new TranslateTransform()
        {
            X = this.manipulationGap.X,
            Y = this.manipulationGap.Y,
        };
    }

    /// <summary>
    /// ドラッグ終了時の処理
    /// </summary>
    /// <param name="sender">イベント発行者</param>
    /// <param name="e">イベント引数</param>
    private void OnManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
    {
        // 移動距離がしきい値を越えていた場合、値を増減する
        if (Math.Abs(this.manipulationGap.X) >= this.HorizontalThreshold)
        {
            this.Value += 10 * Math.Sign(this.manipulationGap.X);
        }
        else if (Math.Abs(this.manipulationGap.Y) >= this.VerticalThreshold)
        {
            this.Value += Math.Sign(this.manipulationGap.Y);
        }

        // 元の位置に戻す
        this.Chart.RenderTransform = new TranslateTransform()
        {
            X = 0,
            Y = 0,
        };
        this.manipulationGap.X = 0;
        this.manipulationGap.Y = 0;
        this.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
    }
}

追加した 2 つのイベントハンドラしきい値、あそびの依存関係プロパティを追加しました

ドラッグ中は水平か垂直のどちらかに平行移動するようにして、ドラッグを離した時に増減の判定とともに元の位置に戻しています

操作してみた様子は次の動画をご覧ください


スワイプで増減するコントロール - YouTube

マウスだと微妙ですね・・・やはりタッチ向きかな?