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

しっぽを追いかけて

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

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

Xamarin.iOS でスワイプ操作を検知する

とりあえず円弧による残り時間の表示まで追加してみました

f:id:matatabi_ux:20141005084603p:plain

こうなるとスワイプによる操作も実装したくなるもの・・・iOS ではどうするのか?

前回の投稿 で対応した GestureRecognizer の無効化がされていれば、ManipulationDelta みたいなイベントハンドラが利用できるようです

具体的には ViewRenderer に次のような記述を追加しますよ

/// <summary>
/// ArcStepper のレンダリングクラス
/// </summary>
public class ArcStepperRenderer : ViewRenderer<ArcStepper, UIView>
{
    #region Privates

    /// <summary>
    /// ドラッグ移動量
    /// </summary>
    private Xamarin.Forms.Point delta = new Xamarin.Forms.Point(0, 0);

    /// <summary>
    /// 移動方向
    /// </summary>
    private ManipulationModes manipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;

    #endregion //Privates

    /// <summary>
    /// ドラッグ移動イベントハンドラ
    /// </summary>
    /// <param name="touches">タッチ情報</param>
    /// <param name="evt">イベント引数</param>
    public override void TouchesMoved(NSSet touches, UIEvent evt)
    {
        base.TouchesMoved(touches, evt);

        var touch = touches.AnyObject as UITouch;
        if (touch == null)
        {
            return;
        }

        var newPoint = touch.LocationInView(this.Control);
        var oldPoint = touch.PreviousLocationInView(this.Control);
        this.delta.X += newPoint.X - oldPoint.X;
        this.delta.Y += newPoint.Y - oldPoint.Y;

        if (Math.Max(Math.Abs(this.delta.X), Math.Abs(this.delta.Y)) < this.Element.Radius * 0.15f)
        {
            return;
        }

        switch (this.manipulationMode)
        {
            case (ManipulationModes.TranslateX | ManipulationModes.TranslateY):
                if (Math.Abs(this.delta.X) > Math.Abs(this.delta.Y))
                {
                    this.manipulationMode = ManipulationModes.TranslateY;
                    this.delta.Y = 0;
                }
                else
                {
                    this.manipulationMode = ManipulationModes.TranslateX;
                    this.delta.X = 0;
                }
                break;

            case ManipulationModes.TranslateX:
                this.delta.X = 0;
                break;

            case ManipulationModes.TranslateY:
                this.delta.Y = 0;
                break;
        }

        if (Math.Abs(this.delta.X) > this.Element.Radius * 0.75f)
        {
            this.delta.X = Math.Sign(this.delta.X) * this.Element.Radius * 0.75f;
        }
        if (Math.Abs(this.delta.Y) > this.Element.Radius * 0.75f)
        {
            this.delta.Y = Math.Sign(this.delta.Y) * this.Element.Radius * 0.75f;
        }
        this.Frame = new RectangleF(new PointF(Convert.ToSingle(this.delta.X), Convert.ToSingle(this.delta.Y)), this.Frame.Size);
    }

    /// <summary>
    /// ドラッグ中断イベントハンドラ
    /// </summary>
    /// <param name="touches">タッチ情報</param>
    /// <param name="evt">イベント引数</param>
    public override void TouchesCancelled(NSSet touches, UIEvent evt)
    {
        base.TouchesCancelled(touches, evt);

        this.delta.X = 0;
        this.delta.Y = 0;
        this.manipulationMode = 0;
        this.Frame = new RectangleF(new PointF(0f, 0f), this.Frame.Size);
    }

    /// <summary>
    /// ドラッグ終了イベントハンドラ
    /// </summary>
    /// <param name="touches">タッチ情報</param>
    /// <param name="evt">イベント引数</param>
    public override void TouchesEnded(NSSet touches, UIEvent evt)
    {
        base.TouchesEnded(touches, evt);

        if (Math.Abs(this.delta.Y) > this.Element.Radius * 0.25f)
        {
            this.Element.OnSwipe(this.delta.Y > 0 ? SwipeDirection.Down : SwipeDirection.Up);
        }
        else if (Math.Abs(this.delta.X) > this.Element.Radius * 0.25f)
        {
            this.Element.OnSwipe(this.delta.X > 0 ? SwipeDirection.Right : SwipeDirection.Left);
        }

        this.delta.X = 0;
        this.delta.Y = 0;
        this.manipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
        this.Frame = new RectangleF(new PointF(0f, 0f), this.Frame.Size);
    }
}

TouchesMoved が Windows ランタイムでいう ManipulationDelta で、TouchesCancelled や TouchesEnded が ManipulationCompleted という感じですね

UITouch というクラスを使って移動量を計算すればそれほどアルゴリズムを変更せずにスワイプ操作が実装できます

なお、上記で利用している OnSwipe メソッドは残り時間の増減の処理で Xamarin.Forms 側の共通処理として ArcStepper.xaml.cs に下記のように記述しています

/// <summary>
/// スワイプ時の処理
/// </summary>
/// <param name="direction">方向</param>
public void OnSwipe(SwipeDirection direction)
{
    var vm = this.BindingContext as TopPageViewModel;
    if (vm == null)
    {
        return;
    }

    var increment = 0d;
    switch (direction)
    {
        case SwipeDirection.Down:
            increment = this.Increment;
            break;

        case SwipeDirection.Up:
            increment = -this.Increment;
            break;

        case SwipeDirection.Right:
            increment = this.LargeIncrement;
            break;

        case SwipeDirection.Left:
            increment = -this.LargeIncrement;
            break;
    }

    if (vm.TimerValue.TotalSeconds + increment * 60 > this.Maximum)
    {
        vm.TimerValue = TimeSpan.FromSeconds(this.Maximum);
        return;
    }
    if (vm.TimerValue.TotalSeconds + increment * 60 < this.Minimum)
    {
        vm.TimerValue = TimeSpan.FromSeconds(this.Minimum);
        return;
    }
    vm.TimerValue = vm.TimerValue.Add(TimeSpan.FromMinutes(increment));
}

また、利用している2種類の Enum はなんてことない下記のような記述です

/// <summary>
/// 移動方向
/// </summary>
public enum ManipulationModes
{
    /// <summary>
    /// 水平移動
    /// </summary>
    TranslateX = 1,

    /// <summary>
    /// 垂直移動
    /// </summary>
    TranslateY = 2,
}

/// <summary>
/// スワイプ方向
/// </summary>
public enum SwipeDirection
{
    /// <summary>
    /// なし
    /// </summary>
    None,

    /// <summary>
    /// スワイプダウン
    /// </summary>
    Down,

    /// <summary>
    /// スワイプアウト
    /// </summary>
    Up,

    /// <summary>
    /// 右スワイプ
    /// </summary>
    Right,

    /// <summary>
    /// 左スワイプ
    /// </summary>
    Left,
}

そんな感じで実行してみますよ


Xamarin.iOS でスワイプ操作を試す - YouTube

ちゃんとできましたね