Xamarinでコントロールのドラッグに対応する

いわゆる画像をドラッグします。本来はこれがやりたかったのですが、プラットフォームごとに微妙に違うので断念。どうせ C# で言語をそろえるので拡張メソッドが適当なヘルパークラスを作れば統一できそうです。

■Windows ストアアプリの場合

ManipulationDelta を使う。

<Canvas 
    x:Name="canvas"
    ManipulationDelta="canvas_ManipulationDelta"
    HorizontalAlignment="Left" Height="564" Margin="80,10,0,0" Grid.Row="1" VerticalAlignment="Top" Width="1200">
    <Ellipse 
        x:Name="circle"
        ManipulationMode="All"
        Fill="#FF4CE035" Height="100" Canvas.Left="133" Stroke="Black" Canvas.Top="63" Width="100" 
        />
</Canvas>

Canvas で ManipulationDeltaイベントを設定してして、ドラッグ対象のコントロールに ManipulationMode=”All” を指定します。

private void canvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var el = e.OriginalSource as UIElement;
    var cir = el as Ellipse;
    double x = Canvas.GetLeft(el) + e.Delta.Translation.X;
    double y = Canvas.GetTop(el) + e.Delta.Translation.Y;
    // 画面からはみ出ないようにする
    if (-cir.ActualWidth / 2 <= x &&
        x <= canvas.ActualWidth - cir.ActualWidth / 2 &&
        -cir.ActualHeight / 2 <= y &&
        y <= canvas.ActualHeight - cir.ActualHeight / 2)
    {
        Canvas.SetLeft(el, x);
        Canvas.SetTop(el, y);
    }
}

ManipulationDelta イベントでは、対象のオブジェクトと移動距離が取れるのでこれを使って位置を移動します。Canvas上に乗せているので、位置指定は Canvas.SetLeft と Canvas.SetTop を使います。

■Androidの場合

Touch イベントを使います。Touch イベントで、DOWN/MOVE/UP が取れるので、これで対象のコントロール(ウィジット)を移動させます。

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    // Set our view from the "main" layout resource
    SetContentView(Resource.Layout.Main);

    // Get our button from the layout resource,
    // and attach an event to it
    this.iv1 = FindViewById(Resource.Id.imageView1);
    this.iv2 = FindViewById(Resource.Id.imageView2);
    this.iv1.SetImageResource(Resource.Drawable.MarkBlue);
    this.iv2.SetImageResource(Resource.Drawable.MarkBlue);
    this.text1 = FindViewById(Resource.Id.textView1);
    iv1.Touch += iv1_Touch;
    iv2.Touch += iv1_Touch;
}

bool moveFlag = false;
float _sx, _sy;
void iv1_Touch(object sender, View.TouchEventArgs e)
{
    var el = sender as ImageView;
    if (el == null) return;

    float x = e.Event.RawX;
    float y = e.Event.RawY;

    switch (e.Event.Action)
    {
    case MotionEventActions.Down:
        _sx = x - el.Left;
        _sy = y - el.Top;
        moveFlag = true;
        break;
    case MotionEventActions.Move:
        if (moveFlag)
        {
            int left = (int)(x - _sx);
            int top = (int)(y - _sy);
            el.Layout(left, top,
                left + el.Width,
                top + el.Height);
        }
        break;  
    case MotionEventActions.Up:
        moveFlag = false;
        break;
    }
}

framelayout にコントロールを乗せておけば、Layout メソッドで位置指定ができます。

■iOSの場合

ドラッグするコントロールを継承する方法もあるのですが、ViewControllerでまとめて操作してみます。TouchesBegan、TouchesMoved、TouchesEnded をオーバーライドします。

    
UIImageView dragItem = null;
/// タッチ開始
public override void TouchesBegan(NSSet touches, UIEvent evt)
{
    base.TouchesBegan(touches, evt);
     UITouch touch = touches.AnyObject as UITouch;
     foreach (var el in this.drags) {
        if (el.Frame.Contains (touch.LocationInView (View))) {
            this.dragItem = el;
            break;
        }
    }
}
/// タッチ移動
public override void TouchesMoved(NSSet touches, UIEvent evt)
{
    base.TouchesMoved(touches, evt);
    UITouch touch = touches.AnyObject as UITouch;
    PointF newPoint = touch.LocationInView(View);
    PointF previousPoint = touch.PreviousLocationInView(View);
    if (dragItem != null ) {
        float offsetX = previousPoint.X - newPoint.X;
        float offsetY = previousPoint.Y - newPoint.Y;
        dragItem.Frame = new RectangleF (
            new PointF (dragItem.Frame.X - offsetX, dragItem.Frame.Y - offsetY),
            dragItem.Frame.Size);
    }
}
/// タッチ終了
public override void TouchesEnded (NSSet touches, UIEvent evt)
{
    base.TouchesEnded (touches, evt);
    dragItem = null;
}

TouchesMoved メソッドで、コントロールの移動前と移動後が取れるので、これを使ってコントロールを移動させます。移動したコントロールの判別を Frame.Contains で使っているのがいいまいちなのですが。

それぞれドラッグイベントの取得の仕方が微妙に違うので、ややこしいです。

  • Windows ストアアプリの場合は、ドラッグ対象にフラグを設定して、コンテナでイベント取得
  • Androidの場合は、ドラッグ対象にイベントを設定して、コンテナに委譲
  • iOSの場合は、コンテナでイベントを取得して、ドラッグ対象を計算で特定

このあたりは、どうせ C# で作るのだから統一しておきたいところです。個人的には Android 方式のほうがわかりやすいのですが(Java ではリスナークラスが必要になるので逆にわかりにくい)、差分を取れるほうが楽なので、ManipulationDelta のようにできると良いかなと。

サンプルは こちら https://github.com/moonmile/SampleTouch

カテゴリー: Android, WinRT, Xamarin, iOS パーマリンク