ใช้การลากและวางด้วยมุมมอง

คุณสามารถใช้กระบวนการลากและวางในมุมมองต่างๆ ได้โดยการตอบสนองต่อเหตุการณ์ ซึ่งอาจทริกเกอร์การลากให้เริ่ม การตอบสนอง และกินเหตุการณ์การลดลง

เริ่มลาก

ผู้ใช้เริ่มลากด้วยท่าทางสัมผัส โดยทั่วไปโดยการแตะหรือคลิก วางรายการที่ต้องการลากค้างไว้

หากต้องการจัดการเรื่องนี้ใน View ให้สร้าง ClipData ออบเจ็กต์และ ClipData.Item ออบเจ็กต์สำหรับ ข้อมูลที่กำลังย้าย ส่วนหนึ่งของ ClipData คือการระบุข้อมูลเมตาที่ ซึ่งจัดเก็บไว้ใน ออบเจ็กต์ 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
    }
}

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;
});

ตอบสนองต่อการเริ่มต้นการลาก

ในระหว่างการดำเนินการลาก ระบบจะจ่ายเหตุการณ์การลากไปยังเหตุการณ์การลาก Listener ของออบเจ็กต์ View ในเลย์เอาต์ปัจจุบัน ผู้ฟังแสดงรีแอ็กชันโดย กำลังโทรหา DragEvent.getAction() เพื่อดูประเภทการดำเนินการ ที่จุดเริ่มต้นของการลาก เมธอดนี้แสดงผล ACTION_DRAG_STARTED

เพื่อตอบสนองต่อเหตุการณ์ที่มีประเภทการดำเนินการเป็น ACTION_DRAG_STARTED เหตุการณ์การลาก Listener ต้องทำดังต่อไปนี้:

  1. โทร DragEvent.getClipDescription() และใช้วิธีประเภท MIME ใน ClipDescription ที่แสดงผลเพื่อดู Listener จะยอมรับข้อมูลที่ถูกลากไปได้หรือไม่

    หากการดำเนินการลากและวางไม่แสดงถึงการเคลื่อนไหวของข้อมูล ไม่จำเป็น

  2. หาก Listener เหตุการณ์การลากยอมรับการลดลงได้ ระบบจะต้องส่งคืน true เพื่อบอก ระบบในการส่งเหตุการณ์การลากไปยัง Listener ต่อไป หากผู้ฟัง ไม่ยอมรับการลดลง ผู้ฟังต้องย้อนกลับไป false และระบบจะหยุด กำลังส่งเหตุการณ์การลากไปยัง Listener จนกว่าระบบจะส่ง ACTION_DRAG_ENDED เพื่อสรุปการดำเนินการลากและวาง

สำหรับเหตุการณ์ ACTION_DRAG_STARTED เมธอด DragEvent ต่อไปนี้จะไม่มี ใช้ได้: getClipData(), getX(), getY() และ getResult()

จัดการเหตุการณ์ในระหว่างการลาก

ในระหว่างการดำเนินการลาก ให้ลาก Listener เหตุการณ์ซึ่งแสดงผล true เพื่อตอบสนองต่อ เหตุการณ์การลากของ ACTION_DRAG_STARTED จะยังคงได้รับเหตุการณ์การลากต่อไป ประเภท ของเหตุการณ์การลากที่ Listener ได้รับระหว่างการลากจะขึ้นอยู่กับตำแหน่งของ เงาการลากและการมองเห็น View ของผู้ฟัง Listener ใช้การลาก เหตุการณ์หลักๆ ในการตัดสินใจว่าจะต้องเปลี่ยนลักษณะที่ปรากฏของ View หรือไม่

ระหว่างการดำเนินการลาก DragEvent.getAction() จะแสดงค่าใดค่าหนึ่งใน 3 ค่าต่อไปนี้

  • ACTION_DRAG_ENTERED: Listener จะได้รับประเภทการทำงานของเหตุการณ์นี้เมื่อจุดติดต่อ ซึ่งก็คือ บนหน้าจอใต้นิ้วหรือเมาส์ของผู้ใช้ ให้ป้อน กรอบล้อมรอบ View ของผู้ฟัง
  • ACTION_DRAG_LOCATION: เมื่อ Listener ได้รับเหตุการณ์ ACTION_DRAG_ENTERED ก็จะมีเหตุการณ์ใหม่ ACTION_DRAG_LOCATION เหตุการณ์ทุกครั้งที่จุดสัมผัสเลื่อนจนถึงจุดนั้น ได้รับเหตุการณ์ ACTION_DRAG_EXITED เมธอด getX() และ getY() แสดงพิกัด X และ Y ของจุดสัมผัส
  • ACTION_DRAG_EXITED: ระบบจะส่งประเภทการทำงานของเหตุการณ์นี้ไปยัง Listener ที่ได้รับก่อนหน้านี้ ACTION_DRAG_ENTERED ระบบจะส่งเหตุการณ์เมื่อจุดสัมผัสเงาการลาก เลื่อนจากภายในกรอบล้อมรอบ View ของผู้ฟังไปยังนอก กรอบล้อมรอบ

Listener เหตุการณ์การลากไม่จำเป็นต้องตอบสนองต่อการดำเนินการใดๆ เหล่านี้ ถ้า ระบบจะไม่สนใจ Listener ที่มีค่าเท่านั้น

แนวทางในการตอบสนองต่อการดำเนินการแต่ละประเภทมีดังนี้

  • เพื่อตอบสนองต่อ ACTION_DRAG_ENTERED หรือ ACTION_DRAG_LOCATION ผู้ฟัง สามารถเปลี่ยนลักษณะที่ปรากฏของ View เพื่อระบุว่ามุมมองนั้นเป็น เป้าหมายการลดลงที่เป็นไปได้
  • เหตุการณ์ที่มีประเภทการดำเนินการ ACTION_DRAG_LOCATION มีข้อมูลที่ถูกต้องสำหรับ getX() และ getY() สอดคล้องกับตำแหน่งของจุดสัมผัส Listener สามารถใช้ข้อมูลนี้เพื่อเปลี่ยนลักษณะที่ปรากฏของ View ได้ หรือเพื่อระบุตำแหน่งที่แน่ชัดที่ผู้ใช้สามารถวาง เนื้อหา
  • ในการตอบสนองต่อ ACTION_DRAG_EXITED ผู้ฟังต้องรีเซ็ตลักษณะที่ปรากฏ การเปลี่ยนแปลงที่มีผลกับเพื่อตอบสนองต่อ ACTION_DRAG_ENTERED หรือ ACTION_DRAG_LOCATION ซึ่งเป็นการบอกให้ผู้ใช้ทราบว่า View ไม่ใช่ เป้าหมายการลดลงที่ใกล้จะมาถึง

ตอบสนองต่อการเปิดตัว

เมื่อผู้ใช้ปล่อยเงาการลากเหนือ View และ View ก่อนหน้านี้ ว่าสามารถยอมรับเนื้อหาที่ลากไปได้ ระบบจะส่ง ลากเหตุการณ์ไปยัง View ด้วยประเภทการดำเนินการ ACTION_DROP

Listener เหตุการณ์การลากต้องทำดังต่อไปนี้

  1. เรียกใช้ getClipData() เพื่อรับออบเจ็กต์ ClipData ที่มีอยู่แต่เดิม ที่ให้ไว้ในการเรียกไปยัง startDragAndDrop() และประมวลผลข้อมูล หากการดำเนินการลากและวางไม่แสดงข้อมูล การเคลื่อนไหวที่ไม่จำเป็น

  2. แสดงผลบูลีน true เพื่อระบุว่าประมวลผลการลดลงเรียบร้อยแล้ว หรือ false หากไม่เป็นเช่นนั้น ค่าที่ส่งกลับมาจะกลายเป็นค่าที่ส่งกลับโดย getResult() สําหรับเหตุการณ์ ACTION_DRAG_ENDED ในท้ายที่สุด หากระบบ ไม่ได้ส่งเหตุการณ์ ACTION_DROP ซึ่งเป็นค่าที่ getResult() แสดงผล สำหรับเหตุการณ์ ACTION_DRAG_ENDED คือ false

สำหรับเหตุการณ์ ACTION_DROP getX() และ getY() ใช้ระบบพิกัดของ View ที่มีการลดลงเพื่อแสดงผลตำแหน่ง X และ Y ของ จุดสัมผัสในขณะที่ตกลงมา

ขณะที่ผู้ใช้ปล่อยเงาการลากทับ View ที่มีเหตุการณ์การลากได้ Listener ไม่ได้รับเหตุการณ์การลาก พื้นที่ว่างเปล่าของ UI ของแอป หรือแม้กระทั่ง บริเวณอื่นนอกแอปพลิเคชันของคุณ Android จะไม่ส่งกิจกรรมที่มีการดำเนินการ ประเภท ACTION_DROP และจะส่งเฉพาะเหตุการณ์ ACTION_DRAG_ENDED

ตอบสนองต่อการสิ้นสุดการลาก

ทันทีหลังจากที่ผู้ใช้ปล่อยเงาการลากแล้ว ระบบจะส่งการลาก เหตุการณ์ที่มีประเภทการดำเนินการเป็น ACTION_DRAG_ENDED สำหรับ Listener เหตุการณ์การลากทั้งหมด ในแอปพลิเคชันของคุณ ซึ่งเป็นการบ่งบอกว่าการดำเนินการลากเสร็จสมบูรณ์แล้ว

Listener เหตุการณ์การลากแต่ละรายการจะต้องทำดังนี้

  1. หาก Listener เปลี่ยนลักษณะที่ปรากฏในระหว่างการดำเนินการ ก็ควรรีเซ็ต กลับไปเป็นลักษณะที่เป็นค่าเริ่มต้น ซึ่งเป็นการบ่งชี้ให้ผู้ใช้เห็นว่า เสร็จสิ้นแล้ว
  2. ผู้ฟังสามารถเลือกโทรหา getResult() เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับ การดำเนินการ หาก Listener แสดงผล true เพื่อตอบสนองต่อเหตุการณ์การดำเนินการ ประเภท ACTION_DROP แล้ว getResult() จะแสดงผลบูลีน true อื่นๆ ทั้งหมด เคส getResult() จะแสดงผลบูลีน false รวมถึงเมื่อระบบ ไม่ส่งเหตุการณ์ ACTION_DROP
  3. ในการระบุว่าการดำเนินการวางเสร็จสมบูรณ์เสร็จสมบูรณ์แล้ว Listener ควรแสดงผลบูลีน true ในระบบ การไม่แสดงผล false จะเป็น สัญญาณภาพที่แสดงเงาตกกระทบที่ย้อนกลับไปยังแหล่งที่มา อาจแนะนำให้ ให้ผู้ใช้ทราบว่าการดำเนินการไม่สำเร็จ

ตอบสนองต่อเหตุการณ์การลาก: ตัวอย่าง

เหตุการณ์การลากทั้งหมดจะได้รับโดยเมธอดเหตุการณ์การลากหรือ Listener ของคุณ ข้อมูลโค้ดต่อไปนี้เป็นตัวอย่างของการตอบสนองต่อเหตุการณ์การลาก:

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;

});

ปรับแต่งเงาการลาก

คุณสามารถกำหนด 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)
    }
}

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);
    }
}