基本的なコンセプト

Compose をお試しください
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose でドラッグ&ドロップを使用する方法をご覧ください。

以降のセクションでは、ドラッグ&ドロップ プロセスの重要なコンセプトについて説明します。

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

ドラッグ&ドロップ プロセスには、開始、続行、ドロップ、終了の 4 つのステップまたは状態があります。

開始

ユーザーのドラッグ ジェスチャーに応じて、アプリは startDragAndDrop() を呼び出し、ドラッグ&ドロップ オペレーションを開始するようシステムに指示します。このメソッドの引数は次のとおりです。

  • ドラッグするデータ。
  • ドラッグ シャドウを描画するためのコールバック
  • ドラッグしたデータを説明するメタデータ
  • システムは、アプリにコールバックしてドラッグ シャドウを取得します。これにより、デバイスにドラッグ シャドウが表示されます。
  • 次に、システムから現在のレイアウト上にあるすべての View オブジェクトのドラッグ イベント リスナーに対して、アクション タイプ ACTION_DRAG_STARTED のドラッグ イベントが送信されます。ドラッグ イベント リスナーでは、今後発生する可能性のあるドロップ イベントなどのドラッグ イベントを受信し続けるには、true を返す必要があります。これにより、リスナーがシステムに登録されます。登録されたリスナーのみが、引き続きドラッグ イベントを受信します。この時点で、リスナーはドロップ ターゲットの View オブジェクトの外観を変更し、ビューによるドロップ イベントの受け入れが可能であることを示すこともできます。
  • ドラッグ イベント リスナーが false を返した場合、システムからアクション タイプ ACTION_DRAG_ENDED のドラッグ イベントが送信されるまで、現在のオペレーションのドラッグ イベントは受信されません。 false を返すことで、リスナーはドラッグ&ドロップ オペレーションに関心がなく、ドラッグしたデータを受け入れたくないことをシステムに伝えます。
続行しています
ユーザーがドラッグを続けます。ドラッグ シャドウがドロップ ターゲットの境界ボックスと交差すると、システムから 1 つ以上のドラッグ イベントが、ターゲットのドラッグ イベント リスナーに送信されます。このイベントを受けて、リスナーはドロップ ターゲット View の外観を変更することもできます。たとえば、イベント がドラッグ シャドウがドロップ ターゲットの境界ボックスに入ったことを示す場合(アクション タイプ ACTION_DRAG_ENTERED )、リスナーはViewをハイライト表示して反応できます。
ドロップ
ユーザーが、ドロップ ターゲットの境界ボックス内でドラッグ シャドウを解放するステップ。ドロップ ターゲットのリスナーには、アクション タイプ ACTION_DROP のドラッグ イベントが送信されます。 このドラッグ イベント オブジェクトには、そのドラッグ オペレーションを開始した startDragAndDrop() 呼び出しでシステムに渡されたデータが含まれています。リスナーがドロップされたデータを正常に処理した場合は、ブール値 true をシステムに返す必要があります。 : このステップが発生するのは、ユーザーがドラッグ シャドウを View の境界ボックス内にドロップし、そのリスナーがドラッグ イベント(ドロップ ターゲット)を受信するよう登録されている場合に限られます。それ以外の状況でユーザーがドラッグ シャドウを解放しても、ACTION_DROP ドラッグ イベントは送信されません。
終了

ユーザーがドラッグ シャドウを解放し、システムから

アクション タイプ ACTION_DROP のドラッグ イベントが送信された後(必要な場合)、ドラッグ&ドロップ オペレーションが終了したことを示すアクション タイプ ACTION_DRAG_ENDED のドラッグ イベントがシステムから送信されます。この動作は、ユーザーがドラッグ シャドウをどこで解放したかにかかわらず行われます。このイベントは、ACTION_DROP イベントを受け取ったリスナーも含め、ドラッグ イベントを受信するよう登録されているリスナーすべてに送信されます。

これらの各ステップについて詳しくは、 ドラッグ&ドロップ オペレーションをご覧ください。

ドラッグ イベント

ドラッグ イベントは、システムから DragEvent オブジェクトの形式で送信されます。このオブジェクトには、ドラッグ&ドロップ プロセスで何が起きているかが記述されたアクション タイプが含まれています。アクション タイプに応じて、オブジェクトに他のデータを含めることもできます。

ドラッグ イベント リスナーは DragEvent オブジェクトを受け取ります。アクション タイプを取得するためには、 リスナーで DragEvent.getAction() を呼び出します。 取り得る値は 6 つあり、DragEvent クラスの定数で定義されています。表 1 に示します。

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

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

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

ACTION_DRAG_ENDED システムがドラッグ&ドロップ オペレーションを終了します。このアクション タイプ の前に ACTION_DROP イベントが発生するとは限りません。システムから ACTION_DROP が送信されていた場合、ACTION_DRAG_ENDED アクション タイプを受信したからといって、ドロップが正常に処理されたことにはなりません。リスナーは、ACTION_DROP への応答で返された値を取得するには、表 2 に示すように、getResult() を呼び出す必要があります。ACTION_DROP イベントが送信されない場合、getResult()false を返します。

DragEvent オブジェクトには、startDragAndDrop() 呼び出しでアプリからシステムに渡したデータとメタデータも含まれています。表 2 に示すように、一部のデータは、特定のアクション タイプでのみ有効です。イベントとその関連データについて詳しくは、ドラッグ&ドロップ オペレーションをご覧ください。

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

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

DragEvent メソッド getAction(), describeContents(), writeToParcel(), および toString() は常に 有効なデータを返します。

特定のアクション タイプに対してメソッドに有効なデータがない場合は、そのデータの種類に応じて null または 0 が返されます。

ドラッグ シャドウ

ドラッグ&ドロップ オペレーション中、システムはユーザーがドラッグする画像を表示します。データを移動する場合は、この画像はドラッグ中のデータを表します。その他の操作の場合は、そのドラッグ オペレーションをなんらかのかたちで表す画像になります。

この画像を、ドラッグ シャドウと呼びます。 これは、 a View.DragShadowBuilder オブジェクト用に宣言したメソッドで作成します。startDragAndDrop() を使用してドラッグ&ドロップ オペレーションを開始するときに、ビルダーをシステムに渡します。システムでは、startDragAndDrop() に対する応答の一環として、View.DragShadowBuilder で定義したコールバック メソッドが呼び出され、ドラッグ シャドウが取得されます。

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

View.DragShadowBuilder(View)

このコンストラクタでは、アプリのあらゆる View オブジェクトを使用できます。このコンストラクタにより View オブジェクトが View.DragShadowBuilder オブジェクトに格納されるため、コールバックが アクセスしてドラッグ シャドウを作成できます。ビューは、ユーザーがドラッグ操作を開始するために選択する View である必要はありません。

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

View.DragShadowBuilder()

このコンストラクタでは、View オブジェクト内で View.DragShadowBuilder オブジェクトを指定しません。フィールドは null に設定されます。View.DragShadowBuilder の拡張や、そのメソッドのオーバーライドを行わない限り、ドラッグ シャドウは表示されません。システムはエラーをスローしません。

View.DragShadowBuilder クラスには、ドラッグ シャドウを一緒に作成するメソッドが 2 つあります。

onProvideShadowMetrics()

startDragAndDrop() を呼び出すとすぐに、システムによってこのメソッドが呼び出されます。このメソッドを使用して、ドラッグ シャドウのサイズやタッチポイントをシステムに送信します。このメソッドには、次の 2 つのパラメータがあります。

outShadowSize: Point オブジェクト。ドラッグ シャドウの幅を x、高さを yに指定します。

outShadowTouchPoint: Point オブジェクト。タッチポイントとは、ドラッグ中にユーザーの指の下にくるドラッグ シャドウ内の位置です。 その X 座標は xY 座標は y に指定します。

onDrawShadow()

onProvideShadowMetrics() 呼び出しの直後に、ドラッグ シャドウを作成する onDrawShadow() が呼び出されます。このメソッドには、システムが onProvideShadowMetrics()で指定したパラメータから構築するCanvasオブジェクトという 1 つの引数があります。このメソッドを使って、用意された Canvas 内にドラッグ シャドウを描画します。

パフォーマンスを向上させるには、ドラッグ シャドウのサイズを小さくします。単一のアイテムの場合は、アイコンを使用することをおすすめします。複数のアイテムを選択する場合は、画面全体に画像を広げるのではなく、アイコンを重ねて使用することをおすすめします。

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

A View は、 View.OnDragListener を実装したドラッグ イベント リスナーか、そのビューの onDragEvent() コールバック メソッドのいずれかによってドラッグ イベントを受信します。システムがメソッドまたはリスナーを呼び出すと、 引数が提供されます。DragEvent

ほとんどの場合、コールバック メソッドよりもリスナーを使用することをおすすめします。UI を設計する場合、通常は View クラスをサブクラス化しませんが、コールバック メソッドを使う場合は、メソッドをオーバーライドするためにサブクラスを作成する必要があります。これに対してリスナークラスは、1 つ実装すれば、そのリスナークラスを異なる複数の View オブジェクトで使用できます。匿名インライン クラスまたはラムダ式として実装することもできます。View オブジェクトのリスナーを設定するには、setOnDragListener() を呼び出します。

別の方法として、メソッドをオーバーライドせずに onDragEvent() のデフォルト実装を変更することもできます。ビューに OnReceiveContentListener を設定します。詳しくは、 setOnReceiveContentListener()をご覧ください。 onDragEvent() メソッドは、デフォルトで次の処理を行います。

  • startDragAndDrop() の呼び出しに応じて true を返します。
  • ドラッグ&ドロップ データがビューにドロップされた場合は、 performReceiveContent() を呼び出します。データは メソッドに ContentInfo オブジェクトとして渡されます。このメソッドは OnReceiveContentListener を呼び出します。

  • ドラッグ&ドロップ データがビューにドロップされ、OnReceiveContentListener がコンテンツを消費した場合は、true を返します。

アプリでデータを処理するには OnReceiveContentListener を定義します。API レベル 24 までの下位互換性を確保するには、Jetpack バージョンの OnReceiveContentListener を使用してください。

View オブジェクトに対して、ドラッグ イベント リスナーとコールバック メソッドの両方を設定できます。その場合、システムはまずリスナーを呼び出します。リスナーから false が返されない限り、コールバック メソッドは呼び出されません。

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