以降のセクションでは、ドラッグ&ドロップ プロセスの重要なコンセプトについて説明します。
ドラッグ&ドロップのプロセス
ドラッグ&ドロップのプロセスには、開始、継続、ドロップ、終了の 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
の組み合わせに似ています。