基本概念

試試 Compose 的方式
Jetpack Compose 是 Android 推薦的 UI 工具包。瞭解如何在 Compose 中使用拖曳功能。

以下各節將說明拖曳程序的幾個重要概念。

拖曳程序

拖曳過程有四個步驟或狀態:開始、繼續、放置和結束。

已開始

為回應使用者的拖曳手勢,應用程式會呼叫 startDragAndDrop() 以指示系統開始拖曳作業。方法的引數提供以下內容:

  • 要拖曳的資料。
  • 用於繪製拖曳陰影的回呼
  • 描述拖曳資料的中繼資料
  • 系統回應時,會呼叫應用程式以取得拖曳陰影。然後在裝置上顯示拖曳陰影。
  • 接著,系統會將動作類型為 ACTION_DRAG_STARTED 的拖曳事件傳送至目前版面配置中所有 View 物件的拖曳事件監聽器。如要繼續接收拖曳事件 (包括可能的放置事件),拖曳事件監聽器必須回傳 true。這會向系統註冊事件監聽器。只有已註冊的監聽器會繼續收到拖曳事件。此時,事件監聽器也可以變更放置目標 View 物件的外觀,以表示檢視畫面能夠接受拖曳事件。
  • 如果拖曳事件監聽器回傳 false,將不會收到目前作業的拖曳事件,直到系統傳送動作類型為 ACTION_DRAG_ENDED 的拖曳事件。事件監聽器會藉由回傳 false,向系統表示不想參與拖曳作業及接受拖曳的資料。
繼續
使用者繼續拖曳。如果拖曳陰影與放置目標的在定界框交會,系統會傳送一或多個拖曳事件至目標的拖曳事件監聽器。事件監聽器可能會變更放置目標 View 的外觀,以回應事件。舉例來說,如果事件指出拖曳陰影已進入放置目標的定界框 (動作類型 ACTION_DRAG_ENTERED),事件監聽器就會醒目顯示 View
已拖曳
使用者在放置目標的定界框內放開拖曳陰影。系統會將動作類型為 ACTION_DROP 的拖曳事件傳送給放置目標的事件監聽器。拖曳事件物件包含在開始作業的 startDragAndDrop() 在呼叫中傳遞給系統的資料。如果事件監聽器成功處理已拖曳的資料,就應將布林值 true 回傳系統。: 只有在使用者將拖曳陰影拖曳到已註冊而得以接收拖曳事件的 View (放置目標) 定界框中,此步驟才會有效。如果使用者在其他情況下放開拖曳陰影,系統就不會傳送 ACTION_DROP 拖曳事件。
已結束

使用者放開拖曳陰影,且在系統傳送

傳送動作類型為 ACTION_DROP 的拖曳事件,如果需要,系統會傳送動作類型為 ACTION_DRAG_ENDED 的拖曳事件,表示拖曳及放置作業已結束。無論使用者放開拖曳陰影的位置為何。即使事件監聽器收到 ACTION_DROP 事件,系統仍會將事件傳送給每個註冊接收拖曳事件的事件監聽器。

如要進一步瞭解這些步驟,請參閱「拖曳作業」一節。

拖曳事件

系統會以 DragEvent 物件的形式傳送拖曳事件,此物件包含描述拖曳過程的動作類型。視動作類型而定,物件中也可能包含其他資料。

拖曳事件監聽器會接收 DragEvent 物件。為了取得動作類型,事件監聽器會呼叫 DragEvent.getAction()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 和至少一個 ACTION_DRAG_LOCATION 事件之後,拖曳陰影會移至拖曳事件監聽器 View 的定界框外。
ACTION_DROP 拖曳陰影會在拖曳事件監聽器的 View 上放開。只有在事件監聽器回傳布林值 true 以回應 ACTION_DRAG_STARTED 拖曳事件時,系統才會將動作類型傳送至 View 物件的事件監聽器。如果使用者在未註冊事件監聽器的 View 上放開拖曳陰影,或在不屬於目前版面配置的任一處放開,就不會傳送此動作類型。

如果事件監聽器成功處理放置作業,就會傳回布林值 true。否則,必須傳回 false

ACTION_DRAG_ENDED 系統即將結束拖曳作業。這個動作類型不一定要放在 ACTION_DROP 事件之前。如果系統傳送 ACTION_DROP,即使收到 ACTION_DRAG_ENDED 動作類型也不表示有成功放開。事件監聽器必須呼叫 getResult() (如表 2所示),才能取得回應 ACTION_DROP 時傳回的值。如果未傳送 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 類別有兩個建構函式:

View.DragShadowBuilder(View)

此建構函式接受應用程式的任何 View 物件。建構函式會將 View 物件儲存在 View.DragShadowBuilder 物件中,因此回呼可以存取該物件來建構拖曳陰影。檢視畫面不一定要是使用者為了開始作業而選取的 View

使用此建構函式時,您不必擴充 View.DragShadowBuilder 或覆寫其方法。根據預設,您取得的拖曳陰影外觀會與做為引數傳遞的 View 相同,並在使用者觸控畫面的位置下方置中顯示。

View.DragShadowBuilder()

如果使用此建構函式,View.DragShadowBuilder 物件就不會有 View 物件。欄位已設為 null。您必須擴充 View.DragShadowBuilder 並覆寫其方法,否則陰影就不會顯示。系統不會擲回錯誤。

View.DragShadowBuilder 類別有兩種方法共同建立拖曳陰影:

onProvideShadowMetrics()

當您呼叫 startDragAndDrop() 後,系統會立即呼叫此方法。此方法會將拖曳陰影的維度和觸控點傳送到系統。此方法有兩個參數:

outShadowSizePoint 物件。拖曳陰影寬度為 x,高度為 y

outShadowTouchPointPoint 物件。觸控點是指拖曳陰影內的位置,拖曳時必須位於使用者手指下方。其 X 座標會是 xY 座標會是 y

onDrawShadow()

呼叫 onProvideShadowMetrics() 後,系統會立即呼叫 onDrawShadow() 來建立拖曳陰影。此方法有一個引數,即系統使用您在 onProvideShadowMetrics() 中提供的參數建構的 Canvas 物件。此方法會在提供的 Canvas 上繪製拖曳陰影。

為提升效能,請盡量使用較小的拖曳陰影。如果是單一項目,建議您使用圖示。如果需要選擇多個項目,建議您使用堆疊中的圖示,而不要將整個圖片蓋滿螢幕畫面。

拖曳事件監聽器和回呼方法

View 收到的拖曳事件可能包含實作 View.OnDragListener 的拖曳事件監聽器,或包含檢視畫面的 onDragEvent() 回呼方法。系統會在呼叫方法或監聽器時提供 DragEvent 引數。

在大部分情況下,使用監聽器時最好使用回呼方法。設計 UI 時,您通常不會將 View 類別設為子類別,不過如果您使用回呼方法,則必須建立子類別來覆寫此方法。相較之下,您可以導入一個事件監聽器類別,然後搭配多個不同的 View 物件使用。您也可以將其實作為匿名內嵌類別或 lambda 運算式。如要設定 View 物件的監聽器,請呼叫 setOnDragListener()

或者,您也可以變更 onDragEvent() 的預設實作方式,而不須覆寫該方法。在檢視畫面上設定 OnReceiveContentListener;詳情請參閱 setOnReceiveContentListener()onDragEvent() 方法會根據預設執行下列操作:

  • 回傳 true 值以回應對 startDragAndDrop() 的呼叫。
  • 如果拖放資料在檢視畫面上放置,則呼叫 performReceiveContent()。資料會以 ContentInfo 物件的形式傳送至方法。這個方法會叫用 OnReceiveContentListener

  • 如果拖放資料在檢視畫面上放置,且 OnReceiveContentListener 會取用任何內容,則會回傳 true。

定義 OnReceiveContentListener 以專門處理應用程式的資料。為了回溯相容到 API 級別 24,請使用 OnReceiveContentListener 的 Jetpack 版本。

View 物件可同時有拖曳事件監聽器和回呼方法;在此情況下,系統會先呼叫事件監聽器。除非監聽器回傳 false,否則系統不會呼叫回呼方法。

onDragEvent() 方法與 View.OnDragListener 的組合可類比為用於觸控事件的 onTouchEvent()View.OnTouchListener 組合。