이벤트에 응답하여 뷰에서 드래그 앤 드롭 프로세스를 구현할 수 있음 드래그 시작과 응답 및 소비 드롭 이벤트를 트리거할 수 있습니다.
드래그 시작
사용자는 동작으로 드래그를 시작합니다. 동작은 일반적으로 터치 또는 클릭한 다음 드래그하려는 항목을 길게 터치합니다.
View
에서 이를 처리하려면 다음을 만듭니다.
ClipData
객체 및
다음에 대한 ClipData.Item
객체:
이동할 필요가 없습니다 ClipData
의 일부로 다음과 같은 메타데이터를 제공합니다.
스테이트리스(Stateless) 컨테이너를
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 } }
자바
// 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()
는 다음 좌표계를 사용합니다.
드롭을 수신하여 X 및 Y 위치를 반환하기 위해 View
터치 포인트가 없습니다.
사용자가 드래그 이벤트가 있는 View
위에 드래그 섀도우를 놓을 수 있습니다.
드래그 이벤트, 앱 UI의 빈 영역이 수신되지 않거나
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 } } }
자바
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) } }
자바
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); } }