以降のセクションでは、ドラッグ&ドロップ プロセスの重要なコンセプトについて説明します。
ドラッグ&ドロップのプロセス
ドラッグ&ドロップのプロセスには、開始、継続、ドロップ、終了の 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 オブジェクトのリスナーに送信されるのは、そのリスナーが ACTION_DRAG_STARTED ドラッグ イベントに対する応答としてブール値 true を返した場合のみです。ユーザーがドラッグ シャドウを解放した場所が、リスナーが登録されていない View 上であるか、現在のレイアウトに含まれていない場所である場合は、このアクション タイプは送信されません。
リスナーでドロップを正常に処理した場合は、ブール値 |
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()が呼び出されます。このメソッドには、onProvideShadowMetrics()で渡したパラメータに基づいてシステムにより作成されるCanvasオブジェクトという単一の引数があります。このメソッドを使って、用意された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 の組み合わせに似ています。