The Android Developer Challenge is back! Submit your idea before December 2.

ドラッグ&ドロップ

Android のドラッグ&ドロップ フレームワークを利用すると、ユーザーがグラフィカルなドラッグ&ドロップ操作によって、View から別の View へデータを移動できるようになります。このフレームワークには、ドラッグ イベントクラス、ドラッグ イベントリスナ、そしてヘルパー メソッドとヘルパー クラスが含まれます。

本来はデータ移動を目的とするフレームワークですが、他の UI アクションにも利用できます。たとえばアプリ内で、ユーザーがある色のアイコンを別のアイコンの上にドラッグした場合、その 2 色を混ぜ合わせることができます。ただしこのドキュメントでは、このフレームワークについて、データ移動の観点から説明します。

以下の関連リソースもご覧ください。

概要

ドラッグ&ドロップ操作は、データのドラッグ開始のシグナルとして認識される操作をユーザーが開始した時点で始まります。アプリはそれに応答して、システムにドラッグの開始を伝えます。するとシステムは、ドラッグされているデータの表現を取得するため、アプリにコールバックします。ユーザーの指がこの表現(「ドラッグ シャドウ」)を現在のレイアウト上で動かすと、ドラッグ イベントが、レイアウト内の View オブジェクトに紐づけられたドラッグ イベントリスナ オブジェクトとドラッグ イベント コールバック メソッドに送信されます。ユーザーがドラッグ シャドウを解放すると、システムはドラッグ操作を終了させます。

ドラッグ イベントリスナ オブジェクト(「リスナ」)は、View.OnDragListener を実装するクラスで作成します。ある View のドラッグ イベントリスナ オブジェクトは、その View オブジェクトの setOnDragListener() メソッドで設定します。それぞれの View オブジェクトには、onDragEvent() コールバック メソッドもあります。これらの詳細については、ドラッグ イベントリスナとコールバック メソッドのセクションをご覧ください。

:以下のセクションでは、単純化するために、ドラッグ イベントを受信するルーチンを「ドラッグ イベントリスナ」と呼んでいますが、実際にはコールバック メソッドの場合もあります。

ドラッグを開始すると、移動しているデータと、このデータを記述するメタデータの両方が、システムへの呼び出しの一部として含まれます。ドラッグ中、レイアウト内のすべての View のドラッグ イベントリスナやコールバック メソッドにドラッグ イベントが送信されます。リスナやコールバック メソッドは、メタデータに基づいて、データがドロップされたときにそれを受信するかどうかを決定できます。ユーザーがデータを View オブジェクトにドロップすると、その View オブジェクトのリスナやコールバック メソッドが、あらかじめシステムに対してドロップを受信するよう伝えていた場合、システムはドラッグ イベントでデータをそのリスナやコールバック メソッドに送信します。

アプリは、startDrag() メソッドを呼び出すことにより、システムにドラッグの開始を伝えます。これはシステムに、ドラッグ イベントの送信を開始するよう指示します。さらにこのメソッドは、ドラッグしているデータも送信します。

現在のレイアウトにアタッチされているすべての View について、startDrag() を呼び出すことが可能です。システムは View オブジェクトを、レイアウトのグローバル設定にアクセスするためだけに使用します。

アプリが startDrag() を呼び出すと、その後のプロセスでは、現在のレイアウトの View オブジェクトに送信されたイベントが使用されます。

注:アプリがマルチウィンドウ モードで実行されている場合、ユーザーはデータをあるアプリから別のアプリにドラッグ&ドロップできます。詳細については、ドラッグ&ドロップのサポートをご覧ください。

ドラッグ&ドロップ プロセス

ドラッグ&ドロップ プロセスは基本的に 4 つのステップ、または状態からなります。

ドラッグ開始
ユーザーによるドラッグ開始の操作に対するレスポンスとして、アプリは startDrag() を呼び出し、システムにドラッグ開始を伝えます。startDrag() の引数で、ドラッグするデータ、このデータのメタデータ、ドラッグ シャドウを描画するためのコールバックを渡します。

システムはまず、アプリにドラッグ シャドウを取得するようコールバックします。するとアプリは端末にドラッグ シャドウを表示します。

次にシステムは、現在のレイアウト上のすべての View オブジェクトのドラッグ イベントリスナに、アクション タイプ ACTION_DRAG_STARTED のドラッグ イベントを送信します。ドラッグ イベントリスナは、発生する可能性のあるドロップ イベントも含めてドラッグ イベントを受信し続けるには、true を返す必要があります。これによってリスナはシステムに登録されます。登録されたリスナのみがドラッグ イベントを受信し続けます。この時点でリスナは、View オブジェクトの外観を変更して、ドロップ イベントの受信が可能であることを表現できます。

ドラッグ イベントリスナが false を返した場合、システムがアクション タイプ ACTION_DRAG_ENDED のドラッグ イベントを送信するまで、現在の操作のドラッグ イベントは受信されません。false を送信することで、リスナはシステムに対して、そのドラッグ操作に関心がなく、ドラッグされたデータを受信したくないことを伝えます。

ドラッグ中
ユーザーはドラッグを続けています。ドラッグ シャドウが View オブジェクトの境界ボックスと交差すると、システムは 1 つ以上のドラッグ イベントを、その View オブジェクトのドラッグ イベントリスナに送信します(それがイベントを受信するよう登録してある場合)。このイベントリスナは、このイベントへの応答として、View オブジェクトの外観変更を選択することもあります。たとえばイベントが、ドラッグ シャドウが View の境界ボックスに入ったことを示すものであれば(アクション タイプ ACTION_DRAG_ENTERED)、リスナは View をハイライト表示させるという反応ができます。
ドロップ
ユーザーは、データを受け入れ可能な View の境界ボックス内で、ドラッグ シャドウを解放します。システムはその View オブジェクトのリスナに、アクション タイプ ACTION_DROP のドラッグ イベントを送信します。このドラッグ イベントには、startDrag() を呼び出してドラッグ操作を開始したときに、システムに渡されたデータが含まれています。ドロップ受け入れのコードが正常に処理された場合、リスナはブール値 true をシステムに返します。

このステップが発生するのは、View オブジェクトの境界ボックス内にドラッグ シャドウがドロップされ、その View のリスナがドラッグ イベントを受信するよう登録されていた場合に限られます。それ以外の状況でドラッグ シャドウを解放しても、ドラッグ イベント ACTION_DROP は送信されません。

ドラッグの終了
ユーザーがドラッグ シャドウを解放し、システムが(必要に応じて)アクション タイプ ACTION_DROP のドラッグ イベントを送信した後、システムはドラッグ操作が終了したことを示すため、アクション タイプ ACTION_DRAG_ENDED のドラッグ イベントを送信します。これは、ユーザーがドラッグ シャドウを解放した場所に関係なく行われます。このイベントは、ドラッグ イベントを受信するよう登録されているリスナすべてに送信されます。既に ACTION_DROP イベントを受け取ったリスナも対象になります。

この 4 つのステップについては、ドラッグ&ドロップ操作を設計するのセクションでそれぞれ詳しく説明しています。

ドラッグ イベントリスナとコールバック メソッド

View は、View.OnDragListener を実装するドラッグ イベントリスナか、その onDragEvent(DragEvent) コールバック メソッドのいずれかによって、ドラッグ イベントを受信します。システムは、このイベントリスナかコールバック メソッドを呼び出して、DragEvent オブジェクトを渡します。

多くの場合、イベントリスナを使用するほうが適切です。UI を設計する場合には、通常は View クラスをサブクラスに分けませんが、コールバック メソッドを使う場合には、そのメソッドをオーバーライドするためにサブクラス化しなければなりません。一方、1 つのリスナクラスを実装してから、それを異なる View オブジェクトで使用できます。それを匿名のインライン クラスとして実装することもできます。View オブジェクトにこのリスナを設定するには、setOnDragListener() を呼び出します。

View オブジェクトには、リスナとコールバック メソッドの両方を設定することができます。その場合、システムは最初にリスナを呼び出します。リスナが false を返さない限り、システムはコールバック メソッドを呼び出しません。

onDragEvent(DragEvent) メソッドと View.OnDragListener という組み合わせは、タップ イベントで使用されている onTouchEvent()View.OnTouchListener の組み合わせに似ています。

ドラッグ イベント

システムはドラッグ イベントを DragEvent オブジェクトとして送信します。このオブジェクトには、ドラッグ&ドロップ プロセスで何が起こっているかをイベントリスナに伝えるアクション タイプが含まれています。アクション タイプによっては他のデータも含まれます。

アクション タイプを取得するために、リスナは getAction() を呼び出します。有効な値は 6 つあり、DragEvent クラスの定数によって定義されます。これらの値については表 1 にまとめています。

DragEvent オブジェクトには、startDrag() への呼び出しの中で、アプリからシステムに渡されたデータも含まれます。このデータの一部は、特定のアクション タイプでのみ有効です。表 2 は各アクションで有効なデータの一覧です。ドラッグ&ドロップ操作を設計するでも、そうしたデータが有効なイベントについて詳しく説明しています。

表 1. DragEvent のアクション タイプ

getAction() の値 意味
ACTION_DRAG_STARTED アプリが startDrag() を呼び出してドラッグ シャドウを取得した直後に、View オブジェクトのドラッグ イベントリスナはこのイベント アクション タイプを受信します。
ACTION_DRAG_ENTERED ドラッグ シャドウが View の境界ボックスに入るとすぐ、View オブジェクトのドラッグ イベントリスナはこのイベント アクション タイプを受信します。これは、ドラッグ シャドウが境界ボックスに入ったときに、イベントリスナが最初に受信するイベント アクション タイプです。リスナは、この操作でドラッグ イベントを受信し続ける場合、システムにブール値 true を返す必要があります。
ACTION_DRAG_LOCATION View オブジェクトのドラッグ イベントリスナが ACTION_DRAG_ENTERED イベントを受信していて、ドラッグ シャドウがまだこの View の境界ボックス内にあるとき、イベントリスナはこのイベント アクション タイプを受信します。
ACTION_DRAG_EXITED View オブジェクトのドラッグ イベントリスナが ACTION_DRAG_ENTERED と少なくとも 1 つの ACTION_DRAG_LOCATION イベントを受け取り、ユーザーがドラッグ シャドウを View の境界ボックス外に移動させると、イベントリスナはこのイベント アクション タイプを受信します。
ACTION_DROP ユーザーが View オブジェクトの上でドラッグ シャドウを解放したとき、View オブジェクトのドラッグ イベントリスナはこのイベント アクション タイプを受信します。このアクション タイプが View オブジェクトのイベントリスナに送信されるのは、このイベント リスナが ACTION_DRAG_STARTED ドラッグ イベントへの応答としてブール値 true を返していた場合のみです。ユーザーがドラッグ シャドウを解放したのが、リスナが登録されていない View の上や、現在のレイアウトの一部ではない場所だった場合には、このアクション タイプは送信されません。

リスナがドロップを正常に処理した場合、ブール値 true が返されます。それ以外の場合には false が返されます。

ACTION_DRAG_ENDED システムがドラッグ操作を終了するときに、View オブジェクトのドラッグ イベントリスナはこのイベント アクション タイプを受信します。このアクション タイプの前に ACTION_DROP イベントが発生するとは限りません。システムが ACTION_DROP を送信していた場合、ACTION_DRAG_ENDED アクション タイプを受信することが、ドロップ操作が正常に処理されたことを意味するわけではありません。リスナは、ACTION_DROP への応答で返された値を取得するために、getResult() を呼び出す必要があります。ACTION_DROP イベントが送信されていなかった場合、getResult()false を返します。

表 2. アクション タイプ別の有効な DragEvent データ

getAction() getClipDescription() getLocalState() getX() getY() getClipData() getResult()
ACTION_DRAG_STARTED X X X      
ACTION_DRAG_ENTERED X X X X    
ACTION_DRAG_LOCATION X X X X    
ACTION_DRAG_EXITED X X        
ACTION_DROP X X X X X  
ACTION_DRAG_ENDED X X       X

メソッド getAction()describeContents()writeToParcel()toString() は必ず有効なデータを返します。

ある特定のアクション タイプに対して有効なデータを含まないメソッドは、その結果のタイプに応じて、null または 0 のいずれかを返します。

ドラッグ シャドウ

ドラッグ&ドロップ操作の間、システムはユーザーがドラッグするイメージを表示します。データ移動の場合、このイメージはドラッグされているデータを表します。他の操作の場合、このイメージはさまなドラッグ操作を表します。

このイメージはドラッグ シャドウと呼ばれます。View.DragShadowBuilder オブジェクト用に宣言したメソッドによってドラッグ シャドウを作成します。そしてドラッグを開始するときに startDrag() を使ってそれをシステムに渡します。システムは、startDrag() への応答の一部として、ドラッグ シャドウを取得するために View.DragShadowBuilder 内で定義済みのコールバック メソッドを呼び出します。

View.DragShadowBuilder クラスには 2 つのコンストラクタがあります。

View.DragShadowBuilder(View)
このコンストラクタは、アプリのあらゆる View オブジェクトを受け入れます。コンストラクタは View オブジェクトを View.DragShadowBuilder オブジェクトに格納するため、ドラッグ シャドウを作成する際、コールバック中に View オブジェクトにアクセスできます。これは、ユーザーがドラッグ操作開始時に選択した View(存在する場合)に関連付けられている必要はありません。

このコンストラクタを使用すれば、View.DragShadowBuilder を拡張したり、そのメソッドをオーバーライドしたりする必要がなくなります。デフォルトでは、ドラッグ シャドウの外観は引数として渡す View と同じになり、画面上でユーザーがタップしている場所の中心に表示されます。

View.DragShadowBuilder()
このコンストラクタを使う場合、View.DragShadowBuilder オブジェクトで View オブジェクトは使用できません(このフィールドは null に設定されます)。このコンストラクタを使い、View.DragShadowBuilder を拡張したり、そのメソッドをオーバーライドしたりしない場合、ドラッグ シャドウは非表示になります。システムはエラーにはなりません。

View.DragShadowBuilder クラスには 2 つのメソッドがあります。

onProvideShadowMetrics()
startDrag() を呼び出した直後に、システムがこのメソッドを呼び出します。このメソッドを使って、ドラッグ シャドウの寸法やタッチポイントをシステムに送ります。このメソッドには引数が 2 つあります。
dimensions
Point オブジェクト。ドラッグ シャドウの幅は x、高さは y で指定します。
touch_point
Point オブジェクト。タッチポイントはドラッグ シャドウ内にあり、ドラッグ中のユーザーの指の下にあたります。その X 座標は x、Y 座標は y に格納されます。
onDrawShadow()
onProvideShadowMetrics() を呼び出した直後、システムは onDrawShadow() を呼び出して、ドラッグ シャドウ自体を取得します。このメソッドには Canvas オブジェクトという 1 つの引数があり、システムはこれを、onProvideShadowMetrics() で渡したパラメータから作成します。このメソッドを使って、提供された Canvas オブジェクト内にドラッグ シャドウを描画します。

パフォーマンスを向上させるには、ドラッグ シャドウのサイズを最小限に抑える必要があります。1 つのアイテムには、1 つのアイコンを使います。複数選択する場合には、画面全体に広がるフルサイズのイメージではなく、スタックしたアイコンを使用できます。

ドラッグ&ドロップ操作を設計する

このセクションでは、ドラッグの開始、ドラッグ中のイベントへの応答、ドロップ イベントへの応答、ドラッグ&ドロップ操作の終了について、その方法を順に説明します。

ドラッグを開始する

ユーザーは、View オブジェクト上でのドラッグ操作(通常は長押し)によってドラッグを開始します。この操作への応答として、次の処理が必要です。

  1. 必要に応じて、移動されるデータの ClipDataClipData.Item を作成します。ClipData オブジェクトの一部として ClipData 内の ClipDescription オブジェクトに格納されているメタデータを渡します。データ移動に相当しないドラッグ&ドロップ操作の場合には、実際のオブジェクトの代わりに null を使います。

    たとえば、以下のコード スニペットは、ImageView のタグやラベルを含む ClipData オブジェクトを作成することで、ImageView 上での長押しに応答する方法を示しています。このスニペットに続いて、次のスニペットでは、View.DragShadowBuilder にメソッドをオーバーライドする方法を示します。

    Kotlin

    const val IMAGEVIEW_TAG = "icon bitmap"
    ...
    val imageView = ImageView(this).apply {
        setImageBitmap(iconBitmap)
        tag = IMAGEVIEW_TAG
        imageView.setOnLongClickListener { v: View ->
            // Create a new ClipData.
            // This is done in two steps to provide clarity. The convenience method
            // ClipData.newPlainText() can create a plain text ClipData in one step.
    
            // Create a new ClipData.Item from the ImageView object's tag
            val item = ClipData.Item(v.tag as? CharSequence)
    
            // Create a new ClipData using the tag as a label, the plain text MIME type, and
            // the already-created item. This will create a new ClipDescription object within the
            // ClipData, and set its MIME type entry to "text/plain"
            val dragData = ClipData(
                    v.tag as? CharSequence,
                    arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
                    item)
    
            // Instantiates the drag shadow builder.
            val myShadow = MyDragShadowBuilder(this)
    
            // Starts the drag
            v.startDrag(
                    dragData,   // the data to be dragged
                    myShadow,   // the drag shadow builder
                    null,       // no need to use local data
                    0           // flags (not currently used, set to 0)
            )
        }
    }
    

    Java

    // Create a string for the ImageView label
    private static final String IMAGEVIEW_TAG = "icon bitmap"
    
    // Creates a new ImageView
    ImageView imageView = new ImageView(this);
    
    // Sets the bitmap for the ImageView from an icon bit map (defined elsewhere)
    imageView.setImageBitmap(iconBitmap);
    
    // Sets the tag
    imageView.setTag(IMAGEVIEW_TAG);
    
        ...
    
    // Sets a long click listener for the ImageView using an anonymous listener object that
    // implements the OnLongClickListener interface
    imageView.setOnLongClickListener(new View.OnLongClickListener() {
    
        // Defines the one method for the interface, which is called when the View is long-clicked
        public boolean onLongClick(View v) {
    
        // Create a new ClipData.
        // This is done in two steps to provide clarity. The convenience method
        // ClipData.newPlainText() can create a plain text ClipData in one step.
    
        // Create a new ClipData.Item from the ImageView object's tag
        ClipData.Item item = new ClipData.Item(v.getTag());
    
        // Create a new ClipData using the tag as a label, the plain text MIME type, and
        // the already-created item. This will create a new ClipDescription object within the
        // ClipData, and set its MIME type entry to "text/plain"
        ClipData dragData = new ClipData(
            v.getTag(),
            new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN },
            item);
    
        // Instantiates the drag shadow builder.
        View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
    
        // Starts the drag
    
                v.startDrag(dragData,  // the data to be dragged
                            myShadow,  // the drag shadow builder
                            null,      // no need to use local data
                            0          // flags (not currently used, set to 0)
                );
    
        }
    }
    
  2. 以下のコード スニペットでは myDragShadowBuilder を定義しています。ここでは、TextView をドラッグした場合のドラッグ シャドウとして、小さなグレーの長方形を作成しています。

    Kotlin

    private class MyDragShadowBuilder(v: View) : View.DragShadowBuilder(v) {
    
        private val shadow = ColorDrawable(Color.LTGRAY)
    
        // Defines a callback that sends the drag shadow dimensions and touch point back to the
        // system.
        override fun onProvideShadowMetrics(size: Point, touch: Point) {
            // Sets the width of the shadow to half the width of the original View
            val width: Int = view.width / 2
    
            // Sets the height of the shadow to half the height of the original View
            val height: Int = view.height / 2
    
            // The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
            // Canvas that the system will provide. As a result, the drag shadow will fill the
            // Canvas.
            shadow.setBounds(0, 0, width, height)
    
            // Sets the size parameter's width and height values. These get back to the system
            // through the size parameter.
            size.set(width, height)
    
            // Sets the touch point's position to be in the middle of the drag shadow
            touch.set(width / 2, height / 2)
        }
    
        // Defines a callback that draws the drag shadow in a Canvas that the system constructs
        // from the dimensions passed in onProvideShadowMetrics().
        override fun onDrawShadow(canvas: Canvas) {
            // Draws the ColorDrawable in the Canvas passed in from the system.
            shadow.draw(canvas)
        }
    }
    

    Java

    private static class MyDragShadowBuilder extends View.DragShadowBuilder {
    
        // The drag shadow image, defined as a drawable thing
        private static Drawable shadow;
    
            // Defines the constructor for myDragShadowBuilder
            public MyDragShadowBuilder(View v) {
    
                // Stores the View parameter passed to myDragShadowBuilder.
                super(v);
    
                // Creates a draggable image that will fill the Canvas provided by the system.
                shadow = new ColorDrawable(Color.LTGRAY);
            }
    
            // Defines a callback that sends the drag shadow dimensions and touch point back to the
            // system.
            @Override
            public void onProvideShadowMetrics (Point size, Point touch) {
                // Defines local variables
                private int width, height;
    
                // Sets the width of the shadow to half the width of the original View
                width = getView().getWidth() / 2;
    
                // Sets the height of the shadow to half the height of the original View
                height = getView().getHeight() / 2;
    
                // The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
                // Canvas that the system will provide. As a result, the drag shadow will fill the
                // Canvas.
                shadow.setBounds(0, 0, width, height);
    
                // Sets the size parameter's width and height values. These get back to the system
                // through the size parameter.
                size.set(width, height);
    
                // Sets the touch point's position to be in the middle of the drag shadow
                touch.set(width / 2, height / 2);
            }
    
            // Defines a callback that draws the drag shadow in a Canvas that the system constructs
            // from the dimensions passed in onProvideShadowMetrics().
            @Override
            public void onDrawShadow(Canvas canvas) {
    
                // Draws the ColorDrawable in the Canvas passed in from the system.
                shadow.draw(canvas);
            }
        }
    

    注:View.DragShadowBuilder を拡張する必要はありません。コンストラクタ View.DragShadowBuilder(View) は、デフォルトでは、渡された View の引数と同じサイズで、中央にタッチポイントがあるドラッグ シャドウを作成します。

ドラッグ開始に応答する

ドラッグ操作中には、現在のレイアウトにある View オブジェクトのドラッグ イベントリスナにドラッグ イベントが送信されます。リスナは、getAction() を呼び出してアクション タイプを取得することで応答する必要があります。ドラッグ開始時には、このメソッドは ACTION_DRAG_STARTED を返します。

アクション タイプ ACTION_DRAG_STARTED のイベントに対して、リスナは以下の処理を行います。

  1. getClipDescription() を呼び出して ClipDescription を取得します。ClipDescription の MIME タイプ メソッドを使って、リスナがドラッグされたデータを受け入れ可能かどうか確認します。

    ドラッグ&ドロップ操作がデータ移動を意味するものでなければ、この処理は不要な場合があります。

  2. ドロップを受け入れ可能なリスナであれば、true が返されます。これにより、引き続きリスナにドラッグ イベントが送信されるようになります。ドロップの受け入れが不可能な場合、リスナは false を返します。すると ACTION_DRAG_ENDED が送信されるまで、ドラッグ イベントが送信されなくなります。

ACTION_DRAG_STARTED イベントに対しては、getClipData()getX()getY()getResult()DragEvent メソッドは有効ではないことに注意してください。

ドラッグ中にイベントを処理する

ACTION_DRAG_STARTED ドラッグ イベントに true を返したリスナは、ドラッグ中にドラッグ イベントを受信し続けます。ドラッグ中にリスナが受信するドラッグ イベントの種類は、ドラッグ シャドウの場所と、リスナの View の可視性によって異なります。

ドラッグ中、リスナはドラッグ イベントを主に、View の外観を変えるべきかどうか判断するために使います。

ドラッグ中、getAction() は以下の 3 つの値のいずれかを返します。

  • ACTION_DRAG_ENTERED: タッチポイント(ユーザーの指の下に位置する画面上の点)がリスナの View の境界ボックスに入ったとき、リスナはこの値を受信します。
  • ACTION_DRAG_LOCATION: リスナは、ACTION_DRAG_ENTERED イベントを受信してから ACTION_DRAG_EXITED を受信するまでの間、タッチポイントが移動するたびに新しい ACTION_DRAG_LOCATION イベントを受信します。getX() メソッドと getY() メソッドはタッチポイントの X 座標と Y 座標を返します。
  • ACTION_DRAG_EXITED: 前に ACTION_DRAG_ENTERED を受信していたリスナは、ドラッグ シャドウがリスナの View の境界ボックス内から出て行った後に、このイベントを受信します。

リスナは、こうしたアクション タイプに応答する必要はありません。リスナが値を返しても、システムは無視します。これらの各アクション タイプを受信したときの応答について、以下にガイドラインを示します。

  • ACTION_DRAG_ENTEREDACTION_DRAG_LOCATION を受信したとき、リスナは、View の外観を変更することによって、ドロップを受け入れる準備ができていることを示せます。
  • アクション タイプ ACTION_DRAG_LOCATION のイベントには、タッチポイントの場所に応じて getX()getY() の有効なデータが含まれます。リスナはこの情報を使って、View の中でタッチポイントがある部分の外観を変えることがあります。さらにリスナはこの情報から、ユーザーがドラッグ シャドウをドロップしようとしている正確な場所を判断できます。
  • ACTION_DRAG_EXITED を受信したとき、リスナは ACTION_DRAG_ENTEREDACTION_DRAG_LOCATION に基づいて適用していた外観の変更をすべてリセットします。こうすることで、ユーザーに対して、その View がすぐにドロップ可能なターゲットではなくなったことを示します。

ドロップに応答する

ユーザーが、アプリの View の上でドラッグ シャドウを解放する場合、その View がそれ以前に、ドラッグされたコンテンツを受け入れ可能と報告していれば、その View には、アクション タイプ ACTION_DROP のドラッグ イベントが送信されます。リスナは以下の処理を行います。

  1. getClipData() を呼び出して、startDrag() の呼び出し時に提供されていた ClipData オブジェクトを取得し、それを格納します。ドラッグ&ドロップ操作がデータ移動を意味するものでなければ、この処理は不要な場合があります。
  2. ドロップが正常に処理された場合、それを示すためにブール値 true を返します。そうでない場合には、ブール値 false を返します。返される値は、ACTION_DRAG_ENDED イベントの getResult() によって返される値になります。

    システムが ACTION_DROP イベントを送信しない場合、ACTION_DRAG_ENDED イベントの getResult() の値が false になることに注意してください。

ACTION_DROP イベントでは、getX()getY() は、ドロップを受信した View の座標系を使って、ドロップの瞬間のドラッグ ポイントの X 座標と Y 座標を返します。

ユーザーは、リスナがドラッグ イベントを受信しない View の上でドラッグ シャドウを解放できます。さらに、アプリの UI がない領域や、アプリ外の領域でドラッグ シャドウを解放することもできます。こうしたケースでは、システムからアクション タイプ ACTION_DROP のイベントが送信されることはありませんが、ACTION_DRAG_ENDED イベントは送信されます。

ドラッグ終了に応答する

ユーザーがドラッグ シャドウを解放した直後、アプリ上のドラッグ イベントリスナすべてに、アクション タイプ ACTION_DRAG_ENDED のドラッグ イベントが送信されます。これは、ドラッグ操作が終了したことを示します。

それぞれのリスナは以下の処理を行います。

  1. ドラッグ操作中にリスナが View オブジェクトの外観を変更していた場合、リスナは View をデフォルトの外観にリセットします。これによって、ユーザーに対して、ドラッグ操作が終了したことを視覚的に示します。
  2. リスナは必要に応じて、getResult() を呼び出して、ドラッグ操作についてより詳しい情報を取得できます。イベント アクション タイプ ACTION_DROP に対して、リスナが true を返していた場合、getResult() はブール値 true を返します。ACTION_DROP イベントが送信されないケースも含めて、他のケースでは、getResult() はブール値 false を返します。
  3. リスナはブール値 true をシステムに返します。

ドラッグ イベントへの応答の例

ドラッグ イベントはすべて、ドラッグ イベントメソッドか、リスナによって最初に受信されます。以下のコード スニペットは、ドラッグ イベントに対するリスナの反応をわかりやすく示した例です。

Kotlin

// Creates a new drag event listener
private val dragListen = View.OnDragListener { v, event ->

    // Handles each of the expected events
    when (event.action) {
        DragEvent.ACTION_DRAG_STARTED -> {
            // Determines if this View can accept the dragged data
            if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                // As an example of what your application might do,
                // applies a blue color tint to the View to indicate that it can accept
                // data.
                (v as? ImageView)?.setColorFilter(Color.BLUE)

                // Invalidate the view to force a redraw in the new tint
                v.invalidate()

                // returns true to indicate that the View can accept the dragged data.
                true
            } else {
                // Returns false. During the current drag and drop operation, this View will
                // not receive events again until ACTION_DRAG_ENDED is sent.
                false
            }
        }
        DragEvent.ACTION_DRAG_ENTERED -> {
            // Applies a green tint to the View. Return true; the return value is ignored.
            (v as? ImageView)?.setColorFilter(Color.GREEN)

            // Invalidate the view to force a redraw in the new tint
            v.invalidate()
            true
        }

        DragEvent.ACTION_DRAG_LOCATION ->
            // Ignore the event
            true
        DragEvent.ACTION_DRAG_EXITED -> {
            // Re-sets the color tint to blue. Returns true; the return value is ignored.
            (v as? ImageView)?.setColorFilter(Color.BLUE)

            // Invalidate the view to force a redraw in the new tint
            v.invalidate()
            true
        }
        DragEvent.ACTION_DROP -> {
            // Gets the item containing the dragged data
            val item: ClipData.Item = event.clipData.getItemAt(0)

            // Gets the text data from the item.
            val dragData = item.text

            // Displays a message containing the dragged data.
            Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show()

            // Turns off any color tints
            (v as? ImageView)?.clearColorFilter()

            // Invalidates the view to force a redraw
            v.invalidate()

            // Returns true. DragEvent.getResult() will return true.
            true
        }

        DragEvent.ACTION_DRAG_ENDED -> {
            // Turns off any color tinting
            (v as? ImageView)?.clearColorFilter()

            // Invalidates the view to force a redraw
            v.invalidate()

            // Does a getResult(), and displays what happened.
            when(event.result) {
                true ->
                    Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG)
                else ->
                    Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG)
            }.show()

            // returns true; the value is ignored.
            true
        }
        else -> {
            // An unknown action type was received.
            Log.e("DragDrop Example", "Unknown action type received by OnDragListener.")
            false
        }
    }
}
...
val imageView = ImageView(this)

// Sets the drag event listener for the View
imageView.setOnDragListener(dragListen)

Java

// Creates a new drag event listener
dragListen = new myDragEventListener();

View imageView = new ImageView(this);

// Sets the drag event listener for the View
imageView.setOnDragListener(dragListen);

...

protected class myDragEventListener implements View.OnDragListener {

    // This is the method that the system calls when it dispatches a drag event to the
    // listener.
    public boolean onDrag(View v, DragEvent event) {

        // Defines a variable to store the action type for the incoming event
        final int action = event.getAction();

        // Handles each of the expected events
        switch(action) {

            case DragEvent.ACTION_DRAG_STARTED:

                // Determines if this View can accept the dragged data
                if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {

                    // As an example of what your application might do,
                    // applies a blue color tint to the View to indicate that it can accept
                    // data.
                    v.setColorFilter(Color.BLUE);

                    // Invalidate the view to force a redraw in the new tint
                    v.invalidate();

                    // returns true to indicate that the View can accept the dragged data.
                    return true;

                }

                // Returns false. During the current drag and drop operation, this View will
                // not receive events again until ACTION_DRAG_ENDED is sent.
                return false;

            case DragEvent.ACTION_DRAG_ENTERED:

                // Applies a green tint to the View. Return true; the return value is ignored.

                v.setColorFilter(Color.GREEN);

                // Invalidate the view to force a redraw in the new tint
                v.invalidate();

                return true;

            case DragEvent.ACTION_DRAG_LOCATION:

                // Ignore the event
                return true;

            case DragEvent.ACTION_DRAG_EXITED:

                // Re-sets the color tint to blue. Returns true; the return value is ignored.
                v.setColorFilter(Color.BLUE);

                // Invalidate the view to force a redraw in the new tint
                v.invalidate();

                return true;

            case DragEvent.ACTION_DROP:

                // Gets the item containing the dragged data
                ClipData.Item item = event.getClipData().getItemAt(0);

                // Gets the text data from the item.
                dragData = item.getText();

                // Displays a message containing the dragged data.
                Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show();

                // Turns off any color tints
                v.clearColorFilter();

                // Invalidates the view to force a redraw
                v.invalidate();

                // Returns true. DragEvent.getResult() will return true.
                return true;

            case DragEvent.ACTION_DRAG_ENDED:

                // Turns off any color tinting
                v.clearColorFilter();

                // Invalidates the view to force a redraw
                v.invalidate();

                // Does a getResult(), and displays what happened.
                if (event.getResult()) {
                    Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show();

                } else {
                    Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show();

                }

                // returns true; the value is ignored.
                return true;

            // An unknown action type was received.
            default:
                Log.e("DragDrop Example","Unknown action type received by OnDragListener.");
                break;
        }

        return false;
    }
};