뷰로 드래그 앤 드롭 구현

이벤트에 응답하여 뷰에서 드래그 앤 드롭 프로세스를 구현할 수 있음 드래그 시작과 응답 및 소비 드롭 이벤트를 트리거할 수 있습니다.

드래그 시작

사용자는 동작으로 드래그를 시작합니다. 동작은 일반적으로 터치 또는 클릭한 다음 드래그하려는 항목을 길게 터치합니다.

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인 이벤트에 대한 응답으로 드래그 이벤트 리스너는 다음을 실행해야 합니다.

  1. 전화걸기 DragEvent.getClipDescription() 드림 반환된 ClipDescription에서 MIME 유형 메서드를 사용하여 리스너가 드래그되는 데이터를 수락할 수 있는지 여부

    드래그 앤 드롭 작업이 데이터 이동을 나타내지 않으면 필요하지 않습니다.

  2. 드래그 이벤트 리스너가 드롭을 허용할 수 있으면 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_DROPView로 이벤트를 드래그합니다.

드래그 이벤트 리스너는 다음을 실행해야 합니다.

  1. getClipData()를 호출하여 원래 ClipData 객체를 가져옵니다. 가 startDragAndDrop() 데이터를 처리합니다 드래그 앤 드롭 작업이 데이터를 나타내지 않는 경우 이러한 작업은 필요하지 않습니다.

  2. 불리언 true를 반환하여 드롭이 성공적으로 처리되었음을 나타냅니다. 그렇지 않은 경우 false입니다. 반환된 값은 최종 ACTION_DRAG_ENDED 이벤트에 관해 getResult()에서 반환한 값이 됩니다. 시스템에서 ACTION_DROP 이벤트를 전송하지 않음(getResult()에서 반환한 값) ACTION_DRAG_ENDED 이벤트의 값은 false입니다.

ACTION_DROP 이벤트의 경우 getX()getY()는 다음 좌표계를 사용합니다. 드롭을 수신하여 XY 위치를 반환하기 위해 View 터치 포인트가 없습니다.

사용자가 드래그 이벤트가 있는 View 위에 드래그 섀도우를 놓을 수 있습니다. 드래그 이벤트, 앱 UI의 빈 영역이 수신되지 않거나 Android는 작업이 포함된 이벤트를 전송하지 않습니다 ACTION_DROP을 입력하고 ACTION_DRAG_ENDED 이벤트만 전송합니다.

드래그 종료에 응답

사용자가 드래그 섀도우를 놓으면 즉시 시스템에서 드래그 섀도우를 모든 드래그 이벤트 리스너에 대한 작업 유형이 ACTION_DRAG_ENDED인 이벤트 사용할 수 있습니다 이는 드래그 작업이 완료되었음을 나타냅니다.

각 드래그 이벤트 리스너는 다음을 실행해야 합니다.

  1. 작업 중에 리스너의 모양이 바뀌면 리스너가 기본 모양으로 돌아가 완료됩니다
  2. 리스너에서 선택적으로 getResult()를 호출하여 작업에 관한 자세한 정보를 확인할 수 있습니다. 리스너가 작업 이벤트에 대한 응답으로 true를 반환하는 경우 ACTION_DROP 유형이면 getResult()는 불리언 true을 반환합니다. 기타 모든 getResult()는 시스템이 불리언 false를 반환합니다. ACTION_DROP 이벤트를 전송하지 않습니다.
  3. 드롭 작업이 성공적으로 완료되었음을 나타내기 위해 리스너는 시스템에 부울 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);
    }
}