ドラッグ&ドロップ プロセスをビューに実装するには、ドラッグ開始をトリガーする可能性のあるイベントに応答し、応答してドロップ イベントを使用します。
ドラッグを開始する
ユーザーはジェスチャーでドラッグを開始します。通常は、ドラッグするアイテムをタップまたは長押しします。
これを View
で処理するには、移動するデータの ClipData
オブジェクトと ClipData.Item
オブジェクトを作成します。ClipData
の一部として、ClipData
内の ClipDescription
オブジェクトに格納されているメタデータを指定します。データの移動以外のドラッグ&ドロップ オペレーションの場合は、実際のオブジェクトの代わりに null
を使用することをおすすめします。
例として、次のコード スニペットは、ImageView
のタグ(ラベル)を含む ClipData
オブジェクトを作成することで、ImageView
上での長押しジェスチャーに対応する方法を示しています。
Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(context).apply { // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { 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. 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 creates a new // ClipDescription object within the ClipData and sets its MIME type // to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. val myShadow = View.DragShadowBuilder(view: this) // Start the drag. v.startDragAndDrop(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. ) // Indicate that the long-click is handled. true } }
Java
// Create a string for the ImageView label. private static final String IMAGEVIEW_TAG = "icon bitmap"; ... // Create a new ImageView. ImageView imageView = new ImageView(context); // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. imageView.setImageBitmap(iconBitmap); // Set the tag. imageView.setTag(IMAGEVIEW_TAG); // Set a long-click listener for the ImageView using an anonymous listener // object that implements the OnLongClickListener interface. imageView.setOnLongClickListener( 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((CharSequence) v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, // and the already-created item. This creates a new ClipDescription object // within the ClipData and sets its MIME type to "text/plain". ClipData dragData = new ClipData( (CharSequence) v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. View.DragShadowBuilder myShadow = new View.DragShadowBuilder(imageView); // Start the drag. v.startDragAndDrop(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. ); // Indicate that the long-click is handled. return true; });
ドラッグ開始に対応する
ドラッグ オペレーションの間は、現在のレイアウトにある View
オブジェクトのドラッグ イベント リスナーに対して、システムからドラッグ イベントが送信されます。リスナーは、DragEvent.getAction()
を呼び出してアクション タイプを取得することで対応します。ドラッグの開始時、このメソッドは ACTION_DRAG_STARTED
を返します。
アクション タイプ ACTION_DRAG_STARTED
のイベントに応答して、ドラッグ イベント リスナーは次のことを行う必要があります。
DragEvent.getClipDescription()
を呼び出し、返されたClipDescription
の MIME タイプ メソッドを使用して、リスナーがドラッグ対象のデータを受け入れ可能かどうかを確認します。ドラッグ&ドロップ オペレーションがデータの移動ではない場合、この処理は不要です。
ドラッグ イベント リスナーがドロップを受理できる場合は、
true
を返して、引き続きリスナーにドラッグ イベントを送信するようシステムに指示する必要があります。リスナーがドロップを受け付けない場合、false
を返す必要があります。システムはACTION_DRAG_ENDED
を送信してドラッグ&ドロップ オペレーションを完了するまで、リスナーへのドラッグ イベントの送信を停止します。
ACTION_DRAG_STARTED
イベントの場合、DragEvent
メソッド(getClipData()
、getX()
、getY()
、getResult()
)は無効です。
ドラッグ中にイベントを処理する
ドラッグ アクションの実行中、ACTION_DRAG_STARTED
ドラッグ イベントに応じて true
を返すドラッグ イベント リスナーは、引き続きドラッグ イベントを受信します。ドラッグ中にリスナーが受信するドラッグ イベントのタイプは、ドラッグ シャドウの場所とリスナーの View
の可視性によって異なります。リスナーは主にドラッグ イベントを使用して、View
の外観を変更する必要があるかどうかを判断します。
ドラッグの間、DragEvent.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_ENTERED
やACTION_DRAG_LOCATION
を受信したとき、リスナーはView
の外観を変更することによって、ビューが潜在的なドロップ ターゲットであることを示せます。- アクション タイプ
ACTION_DRAG_LOCATION
のイベントには、タッチポイントの位置に対応するgetX()
とgetY()
の有効なデータが含まれます。リスナーはこの情報を使用して、タッチポイントでのView
の外観を変更したり、ユーザーがコンテンツをドロップできる正確な位置を決定したりできます。 ACTION_DRAG_EXITED
に応じて、リスナーはACTION_DRAG_ENTERED
またはACTION_DRAG_LOCATION
に応じて適用する外観の変更をすべてリセットする必要があります。こうすることで、そのView
が現時点でのドロップ先ではなくなったことをユーザーに示せます。
ドロップに対応する
ユーザーが View
上でドラッグ シャドウを解放し、以前に View
がドラッグ対象のコンテンツを受け入れることができると報告すると、システムはアクション タイプ ACTION_DROP
で View
にドラッグ イベントを送信します。
ドラッグ イベント リスナーは、次のことを行う必要があります。
getClipData()
を呼び出して、startDragAndDrop()
の呼び出し時に指定されたClipData
オブジェクトを取得し、データを処理します。ドラッグ&ドロップ オペレーションがデータ移動ではない場合、この処理は不要です。ドロップが正常に処理された場合はブール値
true
を返し、そうでない場合はfalse
を返します。戻り値は、最終的なACTION_DRAG_ENDED
イベントに対してgetResult()
が返す値になります。システムがACTION_DROP
イベントを送信しない場合、ACTION_DRAG_ENDED
イベントに対してgetResult()
が返す値はfalse
です。
ACTION_DROP
イベントの場合、getX()
と getY()
は、ドロップを受け取った View
の座標系を使用して、ドロップが発生した時点のタッチポイントの X と Y の位置を返します。
ユーザーは、ドラッグ イベント リスナーがドラッグ イベントを受信していない View
、アプリの UI の空の領域、またはアプリ外の領域では、ドラッグ シャドウを解放できますが、Android はアクション タイプ ACTION_DROP
のイベントを送信せず、ACTION_DRAG_ENDED
イベントのみを送信します。
ドラッグ終了に対応する
ユーザーがドラッグ シャドウを解放するとすぐに、アプリ内のすべてのドラッグ イベント リスナーに、アクション タイプが ACTION_DRAG_ENDED
のドラッグ イベントが送信されます。これは、ドラッグ オペレーションが完了したことを示します。
各ドラッグ イベント リスナーは、次のことを行う必要があります。
- オペレーション中にリスナーの外観が変更された場合は、オペレーションが完了したことをユーザーに視覚的に知らせるため、リスナーの外観をリセットする必要があります。
- 必要であれば、
getResult()
を呼び出すことで、ドラッグ&ドロップ オペレーションについてのより詳しい情報を取得できます。アクション タイプACTION_DROP
のイベントに対してリスナーがtrue
を返す場合、getResult()
はブール値true
を返します。それ以外のケースでは、システムがACTION_DROP
イベントを送信しない場合を含め、getResult()
はブール値false
を返します。 - ドロップ オペレーションが正常に完了したことを示すには、リスナーからシステムにブール値
true
を返す必要があります。false
を返さないことで、ドロップ シャドウがソースに戻ることを示すビジュアル キューによって、操作が失敗したことをユーザーに示唆できます。
ドラッグ イベントへの対応例
ドラッグ イベントはすべて、ドラッグ イベント メソッドかリスナーが受信します。次のコード スニペットは、ドラッグ イベントに応答する例です。
Kotlin
val imageView = ImageView(this) // Set the drag event listener for the View. imageView.setOnDragListener { v, e -> // Handle each of the expected events. when (e.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determine whether this View can accept the dragged data. if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply 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() // Return true to indicate that the View can accept the dragged // data. true } else { // Return false to indicate that, during the current drag and // drop operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. false } } DragEvent.ACTION_DRAG_ENTERED -> { // Apply a green tint to the View. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event. true DragEvent.ACTION_DRAG_EXITED -> { // Reset the color tint to blue. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DROP -> { // Get the item containing the dragged data. val item: ClipData.Item = e.clipData.getItemAt(0) // Get the text data from the item. val dragData = item.text // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show() // Turn off color tints. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Return true. DragEvent.getResult() returns true. true } DragEvent.ACTION_DRAG_ENDED -> { // Turn off color tinting. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Do a getResult() and display what happens. when(e.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() // Return true. The value is ignored. true } else -> { // An unknown action type is received. Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") false } } }
Java
View imageView = new ImageView(this); // Set the drag event listener for the View. imageView.setOnDragListener( (v, e) -> { // Handle each of the expected events. switch(e.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determine whether this View can accept the dragged data. if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true to indicate that the View can accept the dragged // data. return true; } // Return false to indicate that, during the current drag-and-drop // operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. return false; case DragEvent.ACTION_DRAG_ENTERED: // Apply a green tint to the View. ((ImageView)v).setColorFilter(Color.GREEN); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event. return true; case DragEvent.ACTION_DRAG_EXITED: // Reset the color tint to blue. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DROP: // Get the item containing the dragged data. ClipData.Item item = e.getClipData().getItemAt(0); // Get the text data from the item. CharSequence dragData = item.getText(); // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show(); // Turn off color tints. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Return true. DragEvent.getResult() returns true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turn off color tinting. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Do a getResult() and displays what happens. if (e.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(); } // Return true. The value is ignored. return true; // An unknown action type is received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
ドラッグ シャドウをカスタマイズする
カスタマイズされた myDragShadowBuilder
を定義するには、View.DragShadowBuilder
のメソッドをオーバーライドします。次のコード スニペットでは、TextView
用に小さい長方形のグレーのドラッグ シャドウを作成します。
Kotlin
private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) { private val shadow = ColorDrawable(Color.LTGRAY) // Define a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original // View. val width: Int = view.width / 2 // Set 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. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2) } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on 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 object. private static Drawable shadow; // Constructor. public MyDragShadowBuilder(View view) { // Store the View parameter. super(view); // Create a draggable image that fills the Canvas provided by the // system. shadow = new ColorDrawable(Color.LTGRAY); } // Define a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Define local variables. int width, height; // Set the width of the shadow to half the width of the original // View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original // View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2); } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }