您可以藉由回應事件,在檢視畫面中實作拖曳程序 可能觸發拖曳開始、回應和消耗放置事件。
開始拖曳
使用者以手勢開始拖曳,通常是輕觸或點選, 然後按住要拖曳的項目
如要在 View
中處理這種情況,請建立
ClipData
物件和
ClipData.Item
物件:
要移動的資料做為 ClipData
的一部分,請提供
儲存在
ClipDescription
物件
ClipData
。適用於並不代表
資料移動時,建議您使用 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()
。
在拖曳期間處理事件
在拖曳動作期間,拖曳事件監聽器,以回應 true
ACTION_DRAG_STARTED
拖曳事件會繼續接收拖曳事件。類型
事件監聽器在拖曳期間收到的拖曳事件的多寡,取決於
拖曳陰影以及事件監聽器 View
的顯示設定。事件監聽器會使用拖曳功能
事件,主要用於判斷是否必須變更 View
的外觀。
在拖曳動作期間,DragEvent.getAction()
會傳回以下三個值的其中之一:
ACTION_DRAG_ENTERED
: 監聽器會在接觸點收到此事件動作類型 在這個畫面上,請在使用者的手指或滑鼠下方輸入 事件監聽器View
的定界框。ACTION_DRAG_LOCATION
: 一旦事件監聽器收到ACTION_DRAG_ENTERED
事件,就會收到新的ACTION_DRAG_LOCATION
每次接觸點移動至觸發事件為止 就會收到ACTION_DRAG_EXITED
事件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()
即可取得最初的ClipData
物件 所提供的參數startDragAndDrop()
敬上 以及處理資料如果拖曳作業不代表資料 這不需要的話傳回布林值
true
,表示已成功處理放置。 如果不是,則使用false
。傳回的值會成為 最終ACTION_DRAG_ENDED
事件的getResult()
。如果系統 未送出ACTION_DROP
事件,getResult()
傳回的值。ACTION_DRAG_ENDED
事件為false
。
如果是 ACTION_DROP
事件,getX()
和 getY()
會使用
接收捨棄值的 View
,傳回
與主要接觸點相比。
使用者可以透過拖曳事件的 View
釋放拖曳陰影
事件監聽器未收到拖曳事件、應用程式使用者介面的空白區域,
在應用程式以外的區域,Android 不會傳送含有動作的事件
輸入 ACTION_DROP
,且只會傳送 ACTION_DRAG_ENDED
事件。
回應拖曳結束事件
使用者放開拖曳陰影後,系統會立即傳送拖曳
動作類型為 ACTION_DRAG_ENDED
的事件可對所有拖曳事件監聽器
應用程式。這表示拖曳作業已完成。
每個拖曳事件監聽器都必須執行以下操作:
- 如果事件監聽器在作業期間變更外觀,應重設 重新顯示應用程式的預設外觀,以視覺化的方式告知使用者 作業完成。
- 事件監聽器可以選擇呼叫
getResult()
以進一步瞭解作業。如果事件監聽器回傳true
以回應動作事件 輸入ACTION_DROP
,然後getResult()
會傳回布林值true
。所有其他大小getResult()
會傳回布林值false
,包括系統 但不會傳送ACTION_DROP
事件。 - 如要表示放置作業成功完成,事件監聽器:
應將布林值
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); } }