lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

드래그 앤 드롭

Android 드래그/드롭 프레임워크를 사용하면 사용자가 그래픽 드래그 앤 드롭 제스처를 사용하여 현재 레이아웃에서 여러 뷰 간에 데이터를 이동하도록 허용할 수 있습니다. 이 프레임워크에는 드래그 이벤트 클래스, 드래그 리스너, 도우미 메서드 및 클래스가 포함되어 있습니다.

이 프레임워크는 데이터 이동을 주 목적으로 디자인되었지만 다른 UI 작업에도 사용할 수 있습니다. 예를 들어, 사용자가 색상 아이콘을 다른 색상 아이콘 위로 드래그하면 색을 섞는 앱을 생성할 수 있습니다. 하지만, 이 항목의 나머지 부분에서는 데이터 이동과 관련하여 이 프레임워크에 대해 설명합니다.

개요

드래그 앤 드롭 작업은 여러분이 데이터 드래그를 시작하는 신호로 인식하는 제스처를 사용자가 수행할 때 시작됩니다. 이에 대한 응답으로, 애플리케이션은 시스템에 드래그가 시작되었음을 알립니다. 시스템은 애플리케이션에 드래그되는 데이터를 표시하도록 다시 호출합니다. 사용자가 손가락을 이용하여 현재 레이아웃 위로 이 표시('드래그 섀도우')를 이동하면 시스템이 레이아웃에 포함된 View 객체와 연결된 드래그 리스너 객체 및 드래그 이벤트 콜백 메서드로 드래그 이벤트를 보냅니다. 사용자가 드래그 섀도우를 놓으면 시스템이 드래그 작업을 종료합니다.

드래그 이벤트 리스너 객체(이하 '리스너')는 View.OnDragListener를 구현하는 클래스에서 생성합니다. View에 대한 드래그 이벤트 리스너 객체는 View 객체의 setOnDragListener() 메서드를 사용하여 설정합니다. 각 View 객체에는 onDragEvent() 콜백 메서드도 있습니다. 드래그 이벤트 리스너와 콜백 메서드 모두에 대한 설명은 드래그 이벤트 리스너 및 콜백 메서드 섹션에 자세히 나와 있습니다.

참고: 간단하게 하기 위해, 다음 섹션에서는 드래그 이벤트를 수신하는 루틴을 '드래그 이벤트 리스너'라고 지칭합니다. 하지만 실제 환경에서는 콜백 메서드일 수도 있습니다.

드래그를 시작할 때는 이동할 데이터와 이 데이터를 설명하는 메타데이터를 시스템에 대한 호출의 일부로 포함해야 합니다. 드래그가 수행되는 동안, 시스템은 레이아웃에 포함된 각 뷰에 대한 드래그 이벤트 리스너 또는 콜백 메서드로 드래그 이벤트를 보냅니다. 이 리스너 또는 콜백 메서드는 메타데이터를 사용하여 데이터가 드롭될 때 데이터를 수락할지를 결정합니다. 사용자가 View 객체 위로 데이터를 드롭하고 해당 View 객체의 리스너 또는 콜백 메서드가 이미 시스템에 드롭을 수락하기를 원한다고 알렸으면 시스템이 드래그 이벤트의 리스너 또는 콜백 메서드로 데이터를 보냅니다.

애플리케이션은 startDrag() 메서드를 호출하여 드래그를 시작하도록 시스템에 알립니다. 그러면 드래그 이벤트 전송 작업을 시작하도록 시스템에 지시됩니다. 이 메서드는 또한 현재 드래그하는 데이터도 전송합니다.

현재 레이아웃에 있는 모든 연결된 뷰에 대해 startDrag()를 호출할 수 있습니다. 시스템은 레이아웃의 전역 설정에 액세스하는 데 이 View 객체만 사용합니다.

애플리케이션이 startDrag()를 호출하고 나면 프로세스의 나머지 부분에서 시스템이 현재 레이아웃에 있는 View 객체에 보내는 이벤트를 사용합니다.

드래그/드롭 프로세스

드래그 앤 드롭 프로세스는 기본적으로 네 개의 단계 또는 상태로 구성됩니다.

시작됨
드래그를 시작하는 사용자의 제스처에 대한 응답으로, 애플리케이션은 startDrag()를 호출하여 드래그를 시작하도록 시스템에 지시합니다. 인수 startDrag()는 드래그되는 데이터, 이 데이터의 메타데이터 및 드래그 섀도우를 그리기 위한 콜백을 제공합니다.

시스템은 먼저 애플리케이션에 다시 호출하여 드래그 섀도우를 가져오는 방식으로 응답합니다. 그런 다음 기기에 드래그 섀도우를 표시합니다.

그 다음에, 시스템은 작업 유형이 ACTION_DRAG_STARTED인 드래그 이벤트를 현재 레이아웃에 있는 모든 View 객체에 대한 드래그 이벤트 리스너로 보냅니다. 가능한 드롭 이벤트를 포함하여 드래그 이벤트 수신을 계속하려면 드래그 이벤트 리스너가 true를 반환해야 합니다. 그러면 리스너가 시스템에 등록됩니다. 등록된 리스너만 드래그 이벤트 수신을 계속합니다. 이때, 리스너는 리스너가 드롭 이벤트를 수락할 수 있음을 표시하기 위해 View 객체의 모양을 변경할 수도 있습니다.

드래그 이벤트 리스너가 false를 반환하면 시스템이 작업 유형이 ACTION_DRAG_ENDED인 드래그 이벤트를 보낼 때까지 현재 작업에 대한 드래그 이벤트를 수신하지 않습니다. false를 전송함으로써 리스너는 드래그 작업에 관심이 없으며 드래그되는 데이터를 수락하는 것을 원치 않는다고 시스템에 알립니다.

계속 중
사용자가 드래그를 계속합니다. 드래그 섀도우가 뷰 객체의 경계 상자를 교차하면 시스템이 하나 이상의 드래그 이벤트를 View 객체의 드래그 이벤트 리스너로 보냅니다(이벤트를 수신하도록 등록된 경우에 해당). 리스너는 이벤트에 대한 응답으로 해당 View 객체의 모양을 변경할 수 있습니다. 예를 들어, 이벤트가 드래그 섀도우가 뷰의 경계 상자에 진입했음을 나타내면(작업 유형 ACTION_DRAG_ENTERED), 리스너가 해당 뷰를 강조표시하여 반응할 수 있습니다.
드롭됨
사용자가 데이터를 수락할 수 있는 뷰의 경계 상자 안에 드래그 섀도우를 놓습니다. 그러면 시스템이 View 객체의 리스너에 작업 유형이 ACTION_DROP인 드래그 이벤트를 보냅니다. 이 드래그 이벤트에는 작업을 시작한 startDrag()에 대한 호출에서 시스템에 전달된 데이터를 포함합니다. 드롭을 수락하기 위한 코드가 성공한 경우 리스너는 시스템에 부울 값 true를 반환해야 합니다.

이 단계는 리스너가 드래그 이벤트를 수신하도록 등록된 뷰의 경계 상자 내에서 사용자가 드래그 섀도우를 드롭한 경우에만 발생합니다. 사용자가 이외의 모든 상황에서 드래그 섀도우를 놓는 경우에는 ACTION_DROP 드래그 이벤트가 전송되지 않습니다.

종료됨
사용자가 드래그 섀도우를 놓은 후 시스템이 필요한 경우 작업 유형이 ACTION_DROP인 드래그 이벤트를 전송한 후에는 시스템이 작업 유형이 ACTION_DRAG_ENDED인 드래그 이벤트를 전송하여 드래그 작업이 종료되었음을 나타냅니다. 이 동작은 사용자가 드래그 섀도우를 놓는 위치에 상관없이 수행됩니다. 이 이벤트는 리스너가 ACTION_DROP 이벤트를 수신한 경우에도 드래그 이벤트를 수신하도록 등록된 모든 리스너에게 전송됩니다.

이 네 가지 단계 각각은 드래그 앤 드롭 작업 디자인 섹션에 자세히 설명되어 있습니다.

드래그 이벤트 리스너 및 콜백 메서드

뷰는 View.OnDragListener를 구현하는 드래그 이벤트 리스너 또는 해당 onDragEvent(DragEvent) 콜백 메서드를 사용하여 드래그 이벤트를 수신합니다. 시스템은 이 메서드나 리스너를 호출할 때 여기에 DragEvent 객체를 전달합니다.

대부분의 경우, 리스너 사용을 선호할 것입니다. UI를 디자인할 때, 보통 View 클래스의 서브클래스를 생성하지 않지만, 콜백 메서드를 사용하면 이 메서드를 재정의하기 위해 서브클래스를 생성해야 합니다. 이와 달리, 리스너는 하나의 리스너 클래스를 구현하여 여러 다양한 View 객체에 사용할 수 있습니다. 또한 익명 인라인 클래스로 이를 구현할 수도 있습니다. View 객체에 대한 리스너를 설정하려면 setOnDragListener()를 호출합니다.

View 객체에 대해 리스너 및 콜백 메서드를 모두 구성할 수 있습니다. 그러면 시스템이 리스너를 먼저 호출합니다. 리스너가 false를 반환하지 않는 한, 시스템은 콜백 메서드를 호출하지 않습니다.

onDragEvent(DragEvent) 메서드와 View.OnDragListener를 함께 사용하는 것은 터치 이벤트에 사용되는 onTouchEvent()View.OnTouchListener를 함께 사용하는 것과 유사합니다.

드래그 이벤트

시스템은 드래그 이벤트를 DragEvent 객체 형태로 전송합니다. 이 객체에는 리스너에게 드래그/드롭 프로세스의 진행 상태를 알리는 작업 유형이 포함되어 있습니다. 이 객체에는 작업 유형에 따라 다른 데이터가 들어 있습니다.

작업 유형을 가져오기 위해 리스너는 getAction()을 호출합니다. 6개의 가능한 값이 있으며, 이들 값은 DragEvent 클래스에서 상수로 정의됩니다. 이들 값은 표 1에 나열되어 있습니다.

DragEvent 객체에는 애플리케이션이 startDrag()에 대한 호출에서 시스템에 제공한 데이터도 들어 있습니다. 이 데이터 중 일부는 특정 작업 유형에만 유효합니다. 각 작업 유형에 유효한 데이터는 표 2에 요약되어 있습니다. 또한, 드래그 앤 드롭 작업 디자인 섹션에도 이러한 데이터가 유효한 이벤트와 함께 설명되어 있습니다.

표 1. DragEvent 작업 유형

getAction() 값 의미
ACTION_DRAG_STARTED View 객체의 드래그 이벤트 리스너는 애플리케이션이 startDrag()를 호출하고 드래그 섀도우를 가져온 직후에 이 이벤트 작업 유형을 수신합니다.
ACTION_DRAG_ENTERED View 객체의 드래그 이벤트 리스너는 드래그 섀도우가 뷰의 경계 상자에 막 진입했을 때 이 이벤트 작업 유형을 수신합니다. 이것이 드래그 섀도우가 경계 상자에 진입할 때 리스너가 수신하는 첫 번째 이벤트 작업 유형입니다. 리스너가 이 작업에 대한 드래그 이벤트를 계속해서 수신하기를 원하는 경우 부울 값 true를 시스템에 반환해야 합니다.
ACTION_DRAG_LOCATION View 객체의 드래그 이벤트 리스너는 드래그 섀도우가 여전히 뷰의 경계 상자 내에 있는 동안 ACTION_DRAG_ENTERED 이벤트를 수신한 후에 이 이벤트 작업 유형을 수신합니다.
ACTION_DRAG_EXITED View 객체의 드래그 이벤트 리스너는 ACTION_DRAG_ENTERED 및 하나 이상의 ACTION_DRAG_LOCATION 이벤트를 수신한 후와 사용자가 뷰의 경계 상자 밖으로 드래그 섀도우를 이동한 후에 이 이벤트 작업 유형을 수신합니다.
ACTION_DROP View 객체의 드래그 이벤트 리스너는 사용자가 View 객체 위에 드래그 섀도우를 놓을 때 이 이벤트 작업 유형을 수신합니다. 이 작업 유형은 리스너가 ACTION_DRAG_STARTED 드래그 이벤트에 대한 응답으로 부울 값 true를 반환한 경우에만 View 객체의 리스너로 전송됩니다. 사용자가 리스너가 등록되지 않은 뷰에 드래그 섀도우를 놓거나 현재 레이아웃에 포함되지 않은 항목에 드래그 섀도우를 놓는 경우에는 이 작업 유형이 전송되지 않습니다.

드롭이 아무런 문제 없이 처리되면 리스너가 부울 값 true를 반환해야 합니다. 그렇지 않은 경우, false를 반환해야 합니다.

ACTION_DRAG_ENDED View 객체의 드래그 이벤트 리스너는 시스템이 드래그 작업을 종료할 때 이 이벤트 작업 유형을 수신합니다. 이 작업 유형이 반드시 ACTION_DROP 이벤트 다음에 발생하는 것은 아닙니다. 시스템이 ACTION_DROP을 전송한 경우, ACTION_DRAG_ENDED 작업 유형을 수신하는 것이 드롭 작업이 성공되었음을 의미하지는 않습니다. 리스너는 getResult()를 호출하여 ACTION_DROP에 대한 응답으로 반환된 값을 가져와야 합니다. ACTION_DROP 이벤트가 전송되지 않은 경우 getResult()false를 반환합니다.

표 2. 작업 유형별 유효한 DragEvent 데이터

getAction() getClipDescription() getLocalState() getX() getY() getClipData() getResult()
ACTION_DRAG_STARTED X X X      
ACTION_DRAG_ENTERED X X X X    
ACTION_DRAG_LOCATION X X X X    
ACTION_DRAG_EXITED X X        
ACTION_DROP X X X X X  
ACTION_DRAG_ENDED X X       X

getAction(), describeContents(), writeToParcel()toString() 메서드는 항상 유효한 데이터를 반환합니다.

메서드가 특정 작업 유형에 대해 유효한 데이터를 포함하지 않는 경우 해당 결과 유형에 따라 null 또는 0을 반환합니다.

드래그 섀도우

드래그 앤 드롭 작업 동안 시스템은 사용자가 드래그하는 이미지를 표시합니다. 데이터 이동 작업의 경우, 이 이미지는 드래그되는 데이터를 나타냅니다. 기타 작업의 경우, 이 이미지는 드래그 작업의 특정 측면을 나타냅니다.

이 이미지를 드래그 섀도우라고 합니다. startDrag()를 사용하여 드래그를 시작할 때 View.DragShadowBuilder 객체에 대해 선언하는 메서드를 사용하여 드래그 섀도우를 생성한 후 이를 시스템에 전달합니다. startDrag()에 대한 응답의 일부로, 시스템은 View.DragShadowBuilder에 정의한 콜백 메서드를 호출하여 드래그 섀도우를 가져옵니다.

View.DragShadowBuilder 클래스에는 다음과 같은 두 가지 생성자가 있습니다.

View.DragShadowBuilder(View)
이 생성자는 애플리케이션의 모든 View 객체를 받습니다. 이 생성자는 View 객체를 View.DragShadowBuilder 객체에 저장하므로 드래그 섀도우를 생성할 때 콜백이 실행되는 동안 View 객체에 액세스할 수 있습니다. 이 생성자는 사용자가 드래그 작업을 시작할 때 선택한 뷰(있는 경우)와 연결할 필요가 없습니다.

이 생성자를 사용하는 경우 View.DragShadowBuilder를 확장하거나 해당 메서드를 재정의할 필요가 없습니다. 기본적으로, 드래그 섀도우는 인수로 전달하는 뷰와 동일한 모양으로 사용자가 화면을 터치하는 위치를 중심으로 하여 그 아래에 표시됩니다.

View.DragShadowBuilder()
이 생성자를 사용할 경우 View.DragShadowBuilder 객체에서 사용할 수 있는 View 객체가 없습니다(이 필드는 null로 설정됨). 이 생성자를 사용하는데 View.DragShadowBuilder를 확장하거나 해당 메서드를 재정의하지 않을 경우, 드래그 섀도우가 표시되지 않습니다. 시스템에서 오류를 표시하지 않습니다.

View.DragShadowBuilder 클래스에는 다음과 같은 두 가지 메서드가 있습니다.

onProvideShadowMetrics()
startDrag()를 호출하면 즉시 시스템이 이 메서드를 호출합니다. 이 메서드를 사용하여 시스템에 드래그 섀도우의 크기와 터치 지점을 전송합니다. 이 메서드에는 두 가지 인수가 있습니다.
dimensions
Point 객체입니다. 드래그 섀도우 너비는 x에 해당하고, 높이는 y에 해당합니다.
touch_point
Point 객체입니다. 터치 지점은 드래그 섀도우 내 위치로, 드래그하는 동안 사용자 손가락 아래에 위치합니다. 해당 X 위치는 x에 해당하고, Y 위치는 y에 해당합니다.
onDrawShadow()
onProvideShadowMetrics()에 대한 호출이 실행된 후 즉시 시스템이 onDrawShadow()를 호출하여 드래그 섀도우를 가져옵니다. 이 메서드는 onProvideShadowMetrics()에 제공하는 매개변수에서 생성되는 Canvas 객체를 단일 인수로 사용합니다. 이를 사용하여 제공된 Canvas 객체에 드래그 섀도우를 그립니다.

성능을 향상시키려면 드래그 섀도우의 크기를 작게 유지해야 합니다. 단일 항목의 경우, 아이콘을 사용할 수 있습니다. 다중 선택 항목의 경우, 화면 전체에 걸쳐 전체 이미지를 표시하기보다는 스택 형태로 아이콘을 사용할 수 있습니다.

드래그 앤 드롭 작업 디자인

이 섹션에서는 드래그를 시작하는 방법, 드래그 도중 이벤트에 응답하는 방법, 드롭 이벤트에 응답하는 방법 및 드래그 앤 드롭 작업을 종료하는 방법을 단계별로 보여줍니다.

드래그 시작

사용자는 일반적으로 View 객체를 길게 누르는 동작인 드래그 제스처를 사용하여 드래그를 시작합니다. 응답 시 다음 작업을 수행해야 합니다.

  1. 필요한 경우, 이동되는 데이터에 대해 ClipDataClipData.Item을 생성합니다. ClipData 객체의 일부로, ClipData 내 ClipDescription 객체에 저장된 메타데이터를 제공합니다. 데이터 이동을 나타내지 않는 드래그 앤 드롭 작업의 경우, 실제 객체 대신 null을 사용할 수 있습니다.

    예를 들어, 다음 코드 스니펫은 ImageView의 태그 또는 레이블을 포함하는 ClipData 객체를 생성함으로써 ImageView를 길게 누르는 동작에 응답하는 방법을 보여줍니다. 이 스니펫 다음에 있는 스니펫은 View.DragShadowBuilder에서 메서드를 재정의하는 방법을 보여줍니다.

    // Create a string for the ImageView label
    private static final String IMAGEVIEW_TAG = "icon bitmap"
    
    // Creates a new ImageView
    ImageView imageView = new ImageView(this);
    
    // Sets the bitmap for the ImageView from an icon bit map (defined elsewhere)
    imageView.setImageBitmap(mIconBitmap);
    
    // Sets the tag
    imageView.setTag(IMAGEVIEW_TAG);
    
        ...
    
    // Sets a long click listener for the ImageView using an anonymous listener object that
    // implements the OnLongClickListener interface
    imageView.setOnLongClickListener(new View.OnLongClickListener() {
    
        // Defines the one method for the interface, which is called when the View is long-clicked
        public boolean onLongClick(View 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(v.getTag());
    
        // Create a new ClipData using the tag as a label, the plain text MIME type, and
        // the already-created item. This will create a new ClipDescription object within the
        // ClipData, and set its MIME type entry to "text/plain"
        ClipData dragData = new ClipData(v.getTag(),ClipData.MIMETYPE_TEXT_PLAIN,item);
    
        // Instantiates the drag shadow builder.
        View.DragShadowBuilder myShadow = new MyDragShadowBuilder(imageView);
    
        // Starts the drag
    
                v.startDrag(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)
                );
    
        }
    }
    
  2. 다음 코드 스니펫은 myDragShadowBuilder를 정의합니다. 이는 작은 회색 사각형으로 TextView 드래그에 대한 드래그 섀도우를 생성합니다.
        private static class MyDragShadowBuilder extends View.DragShadowBuilder {
    
        // The drag shadow image, defined as a drawable thing
        private static Drawable shadow;
    
            // Defines the constructor for myDragShadowBuilder
            public MyDragShadowBuilder(View v) {
    
                // Stores the View parameter passed to myDragShadowBuilder.
                super(v);
    
                // Creates a draggable image that will fill the Canvas provided by the system.
                shadow = new ColorDrawable(Color.LTGRAY);
            }
    
            // Defines a callback that sends the drag shadow dimensions and touch point back to the
            // system.
            @Override
            public void onProvideShadowMetrics (Point size, Point touch) {
                // Defines local variables
                private int width, height;
    
                // Sets the width of the shadow to half the width of the original View
                width = getView().getWidth() / 2;
    
                // Sets the height of the shadow to half the height of the original View
                height = getView().getHeight() / 2;
    
                // The drag shadow is a ColorDrawable. This sets its dimensions to be the same as the
                // Canvas that the system will provide. As a result, the drag shadow will fill the
                // Canvas.
                shadow.setBounds(0, 0, width, height);
    
                // Sets the size parameter's width and height values. These get back to the system
                // through the size parameter.
                size.set(width, height);
    
                // Sets the touch point's position to be in the middle of the drag shadow
                touch.set(width / 2, height / 2);
            }
    
            // Defines a callback that draws the drag shadow in a Canvas that the system constructs
            // from the dimensions passed in onProvideShadowMetrics().
            @Override
            public void onDrawShadow(Canvas canvas) {
    
                // Draws the ColorDrawable in the Canvas passed in from the system.
                shadow.draw(canvas);
            }
        }
    

    참고: View.DragShadowBuilder를 확장할 필요가 없음을 유의하세요. 생성자 View.DragShadowBuilder(View)는 이 생성자에 전달되는 View 인수와 크기가 같으며 터치 지점을 중심으로 하여 기본 드래그 섀도우를 생성합니다.

드래그 시작에 응답

드래그 작업 도중, 시스템은 드래그 이벤트를 현재 레이아웃에 포함된 View 객체의 드래그 이벤트 리스너로 발송합니다. 이 리스너는 이에 대한 응답으로 getAction()을 호출하여 작업 유형을 가져와야 합니다. 드래그 시작 시, 이 메서드는 ACTION_DRAG_STARTED를 반환합니다.

작업 유형이 ACTION_DRAG_STARTED인 이벤트에 대한 응답으로, 리스너는 다음 동작을 수행합니다.

  1. getClipDescription()을 호출하여 ClipDescription을 가져옵니다. ClipDescription에서 MIME 유형 메서드를 사용하여 리스너가 드래그되는 데이터를 수락할 수 있는지 여부를 확인합니다.

    드래그 앤 드롭 작업이 데이터 이동을 나타내지 않는 경우 이 동작은 필요하지 않을 수 있습니다.

  2. 리스너가 드롭을 수락할 수 있으면 true를 반환해야 합니다. 이는 시스템에 드래그 이벤트를 리스너로 계속 보내도록 지시합니다. 드롭을 수락할 수 없으면 false를 반환합니다. 이 경우 시스템은 ACTION_DRAG_ENDED를 전송할 때까지 드래그 이벤트 전송을 중지합니다.

참고로, ACTION_DRAG_STARTED 이벤트에 대해서는 DragEvent 메서드, 즉 getClipData(), getX(), getY()getResult()가 유효하지 않습니다.

드래그 도중 이벤트 처리

드래그 작업 중에 ACTION_DRAG_STARTED 드래그 이벤트에 대한 응답으로 true를 반환한 리스너는 드래그 이벤트를 계속 수신합니다. 드래그 작업 중에 리스너가 수신하는 드래그 이벤트 유형은 드래그 섀도우의 위치와 리스너의 뷰가 표시되는지 여부에 따라 달라집니다.

드래그 작업 중에 리스너는 주로 드래그 이벤트를 사용하여 뷰의 모양을 변경해야 하는지 여부를 결정합니다.

드래그 작업 중에 getAction()은 다음 세 가지 값 중 하나를 반환합니다.

리스너는 이 작업 유형에 응답할 필요가 없습니다. 리스너가 시스템에 값을 반환하는 경우 값이 무시됩니다. 다음은 이러한 작업 유형 각각에 응답하는 데 대한 몇 가지 가이드라인입니다.

  • ACTION_DRAG_ENTERED 또는 ACTION_DRAG_LOCATION에 대한 응답으로, 리스너는 뷰의 모양을 변경하여 드롭을 곧 수신하려고 한다는 것을 나타낼 수 있습니다.
  • 작업 유형이 ACTION_DRAG_LOCATION인 이벤트는 터치 지점의 위치에 해당하는 getX()getY()에 대한 유효한 데이터를 포함합니다. 리스너는 이 정보를 사용하여 뷰에서 터치 지점에 해당하는 부분의 모양을 변경하고자 할 수 있습니다. 또한 이 정보를 사용하여 사용자가 드래그 섀도우를 드롭하는 정확한 위치를 확인할 수도 있습니다.
  • ACTION_DRAG_EXITED에 대한 응답으로, 리스너는 ACTION_DRAG_ENTERED 또는 ACTION_DRAG_LOCATION에 대한 응답으로 적용된 모양 변경 사항을 재설정해야 합니다. 이는 뷰가 더 이상 임박한 드롭 대상이 아님을 사용자에게 나타냅니다.

드롭에 응답

사용자가 애플리케이션에서 특정 뷰에 드래그 섀도우를 놓고, 해당 뷰가 이전에 드래그되는 콘텐츠를 수락할 수 있다고 보고한 경우 시스템이 작업 유형 ACTION_DROP과 함께 해당 뷰에 드래그 이벤트를 발송합니다. 리스너는 다음 동작을 수행해야 합니다.

  1. getClipData()를 호출하여 startDrag()에 대한 호출에서 원래 제공된 ClipData 객체를 가져와서 저장합니다. 드래그 앤 드롭 작업이 데이터 이동을 나타내지 않는 경우 이 동작은 필요하지 않을 수 있습니다.
  2. 부울 값 true를 반환하여 드롭이 성공적으로 처리되었음을 나타내거나 그렇지 않은 경우 부울 값 false를 반환합니다. 반환된 값은 ACTION_DRAG_ENDED 이벤트에 대한 getResult()에서 반환되는 값이 됩니다.

    참고로, 시스템이 ACTION_DROP 이벤트를 전송하지 않을 경우 ACTION_DRAG_ENDED 이벤트에 대한 getResult() 값은 false입니다.

ACTION_DROP 이벤트의 경우, getX()getY()가 드롭될 때 드롭을 수신한 뷰의 좌표계를 사용하여 드래그 지점의 X 및 Y 위치를 반환합니다.

사용자는 리스너가 드래그 이벤트를 수신하지 않는 뷰에 드래그 섀도우를 놓을 수 있습니다. 또한, 애플리케이션의 UI에서 빈 영역이나 애플리케이션 외부의 영역에 드래그 섀도우를 놓을 수도 있습니다. 이 경우 모두, 시스템이 ACTION_DRAG_ENDED 이벤트를 전송하더라도 작업 유형이 ACTION_DROP인 이벤트를 전송하지 않습니다.

드래그 종료에 응답

사용자가 드래그 섀도우를 놓은 후 즉시, 시스템은 작업 유형 ACTION_DRAG_ENDED와 함께 드래그 이벤트를 애플리케이션에 포함된 모든 드래그 이벤트 리스너로 전송합니다. 이는 드래그 작업이 종료되었음을 나타냅니다.

각 리스너는 다음 동작을 수행해야 합니다.

  1. 리스너가 작업이 수행되는 동안 View 객체의 모양을 변경한 경우 뷰를 기본 모양으로 재설정해야 합니다. 이는 사용자에게 작업이 종료되었음을 시각적으로 나타내는 것입니다.
  2. 리스너는 경우에 따라 getResult()를 호출하여 작업에 대한 자세한 정보를 확인할 수 있습니다. 리스너가 작업 유형이 ACTION_DROP인 이벤트에 대한 응답으로 true를 반환한 경우 getResult()는 부울 값 true를 반환합니다. 기타 모든 경우에는 getResult()가 부울 값 false를 반환합니다. 여기에는 시스템이 ACTION_DROP 이벤트를 전송하지 않는 모든 경우가 포함됩니다.
  3. 리스너는 시스템에 부울 값 true를 반환해야 합니다.

드래그 이벤트에 응답: 예제

모든 드래그 이벤트는 드래그 이벤트 메서드나 리스너에 의해 처음 수신됩니다. 다음 코드 스니펫은 리스너의 드래그 이벤트에 대한 응답을 보여주는 간단한 예입니다.

// Creates a new drag event listener
mDragListen = new myDragEventListener();

View imageView = new ImageView(this);

// Sets the drag event listener for the View
imageView.setOnDragListener(mDragListen);

...

protected class myDragEventListener implements View.OnDragListener {

    // This is the method that the system calls when it dispatches a drag event to the
    // listener.
    public boolean onDrag(View v, DragEvent event) {

        // Defines a variable to store the action type for the incoming event
        final int action = event.getAction();

        // Handles each of the expected events
        switch(action) {

            case DragEvent.ACTION_DRAG_STARTED:

                // Determines if this View can accept the dragged data
                if (event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {

                    // As an example of what your application might do,
                    // applies a blue color tint to the View to indicate that it can accept
                    // data.
                    v.setColorFilter(Color.BLUE);

                    // Invalidate the view to force a redraw in the new tint
                    v.invalidate();

                    // returns true to indicate that the View can accept the dragged data.
                    return true;

                }

                // Returns false. During the current drag and drop operation, this View will
                // not receive events again until ACTION_DRAG_ENDED is sent.
                return false;

            case DragEvent.ACTION_DRAG_ENTERED:

                // Applies a green tint to the View. Return true; the return value is ignored.

                v.setColorFilter(Color.GREEN);

                // Invalidate the view to force a redraw in the new tint
                v.invalidate();

                return true;

            case DragEvent.ACTION_DRAG_LOCATION:

                // Ignore the event
                return true;

            case DragEvent.ACTION_DRAG_EXITED:

                // Re-sets the color tint to blue. Returns true; the return value is ignored.
                v.setColorFilter(Color.BLUE);

                // Invalidate the view to force a redraw in the new tint
                v.invalidate();

                return true;

            case DragEvent.ACTION_DROP:

                // Gets the item containing the dragged data
                ClipData.Item item = event.getClipData().getItemAt(0);

                // Gets the text data from the item.
                dragData = item.getText();

                // Displays a message containing the dragged data.
                Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG);

                // Turns off any color tints
                v.clearColorFilter();

                // Invalidates the view to force a redraw
                v.invalidate();

                // Returns true. DragEvent.getResult() will return true.
                return true;

            case DragEvent.ACTION_DRAG_ENDED:

                // Turns off any color tinting
                v.clearColorFilter();

                // Invalidates the view to force a redraw
                v.invalidate();

                // Does a getResult(), and displays what happened.
                if (event.getResult()) {
                    Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG);

                } else {
                    Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG);

                }

                // returns true; the value is ignored.
                return true;

            // An unknown action type was received.
            default:
                Log.e("DragDrop Example","Unknown action type received by OnDragListener.");
                break;
        }

        return false;
    }
};