基本的なコンセプト

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

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

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

開始

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

  • ドラッグするデータ。
  • ドラッグ シャドウを描画するためのコールバック
  • ドラッグしたデータを説明するメタデータ
  • システムは、ドラッグ シャドウを取得するためにアプリにコールバックすることで応答します。それにより、デバイスにドラッグ シャドウが表示されます。
  • 次に、アクション タイプ ACTION_DRAG_STARTED のドラッグ イベントが、現在のレイアウト内のすべての View オブジェクトのドラッグ イベント リスナーに送信されます。ドラッグ イベント(発生する可能性のあるドロップ イベントを含む)を引き続き受信するには、ドラッグ イベント リスナーが 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() を呼び出します。DragEvent クラスの定数によって定義される有効な値は 6 つあります(表 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 オブジェクトのリスナーに送信されるのは、ACTION_DRAG_STARTED ドラッグ イベントに対してリスナーがブール値 true を返す場合のみです。リスナーが登録されていない 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 を返します。

ドラッグ シャドウ

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

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

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

View.DragShadowBuilder(View)

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

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

View.DragShadowBuilder()

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

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

onProvideShadowMetrics()

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

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

outShadowTouchPoint: Point オブジェクト。タッチポイントとは、ドラッグ シャドウ内の位置で、ユーザーの指の下に位置する必要があります。X の位置を x、Y の位置を y に指定します。

onDrawShadow()

onProvideShadowMetrics() 呼び出しの直後に、ドラッグ シャドウを作成する onDrawShadow() が呼び出されます。このメソッドの引数は Canvas オブジェクトです。このオブジェクトは、onProvideShadowMetrics() で指定したパラメータからシステムが作成します。このメソッドは、指定された Canvas にドラッグ シャドウを描画します。

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

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

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 の組み合わせに似ています。