드래그 스타트를 트리거할 수 있는 이벤트에 응답하고 드롭 이벤트를 응답하여 사용하여 뷰에서 드래그 앤 드롭 프로세스를 구현할 수 있습니다.
드래그 시작
사용자는 일반적으로 드래그할 항목을 터치하거나 길게 클릭하여 동작으로 드래그를 시작합니다.
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
이벤트의 경우 getClipData()
, getX()
, getY()
, getResult()
DragEvent
메서드는 유효하지 않습니다.
드래그 중 이벤트 처리
드래그 작업 중에 ACTION_DRAG_STARTED
드래그 이벤트에 대한 응답으로 true
를 반환하는 드래그 이벤트 리스너는 드래그 이벤트를 계속 수신합니다. 드래그 중에 리스너가 수신하는 드래그 이벤트 유형은 드래그 섀도우의 위치와 리스너의 View
의 표시 여부에 따라 다릅니다. 리스너는 주로 드래그 이벤트를 사용하여 View
의 모양을 변경해야 하는지 결정합니다.
드래그 작업 중, DragEvent.getAction()
은 다음 세 가지 값 중 하나를 반환합니다.
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
를 반환합니다. 그 외 모든 경우에서getResult()
는 시스템이ACTION_DROP
이벤트를 전송하지 않는 경우를 포함하여 불리언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; });
드래그 그림자 맞춤설정
View.DragShadowBuilder
의 메서드를 재정의하여 맞춤설정된 myDragShadowBuilder
를 정의할 수 있습니다. 다음 코드 스니펫은 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); } }