پیاده‌سازی کشیدن و رها کردن با نماها

می‌توانید فرایند کشیدن و رها کردن را با پاسخ دادن به رویدادهایی که ممکن است شروع کشیدن را راه‌اندازی کنند و پاسخ دادن و مصرف کردن رویدادهای رها کردن در نماها پیاده‌سازی کنید.

شروع کشیدن

کاربر با اشاره‌ای، معمولاً با لمس کردن یا کلیک کردن و نگه داشتن روی موردی که می‌خواهد بکشد، کشیدن را شروع می‌کند.

برای مدیریت این مورد در View، یک شیء ClipData و یک شیء ClipData.Item برای داده‌هایی که منتقل می‌شوند ایجاد کنید. به‌عنوان بخشی از ClipData، فراداده‌ای را ارائه دهید که در شیء ClipDescription در ClipData ذخیره شده است. برای عملیات کشیدن و رها کردن که نشان‌دهنده انتقال داده نیست، شاید بخواهید به‌جای شیء واقعی از null استفاده کنید.

برای مثال، این تکه‌کد نشان می‌دهد که چگونه می‌توان به اشاره لمس کردن و نگه داشتن در ImageView با ایجاد شیء ClipData که حاوی برچسب (یا برچسب) ImageView است پاسخ داد:

کاتلین

// 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() را فراخوانی کنید و از روش‌های نوع MIME در ClipDescription برگردانده‌شده استفاده کنید تا ببینید آیا شنونده می‌تواند داده‌های درحال کشیدن را بپذیرد یا نه.

    اگر عملیات کشیدن و رها کردن نشان‌دهنده انتقال داده نباشد، ممکن است این کار ضروری نباشد.

  2. اگر شنونده رویداد کشیدن بتواند رها کردن را بپذیرد، باید true را برگرداند تا به سیستم بگوید به ارسال رویدادهای کشیدن به شنونده ادامه دهد. اگر شنونده نتواند رها کردن را بپذیرد، شنونده باید false را برگرداند و سیستم ارسال رویدادهای کشیدن به شنونده را متوقف می‌کند تا زمانی که سیستم ACTION_DRAG_ENDED را برای پایان دادن به عملیات کشیدن و رها کردن ارسال کند.

برای رویداد ACTION_DRAG_STARTED، روش‌های DragEvent زیر معتبر نیستند: getClipData()، getX()، getY()، و getResult().

مدیریت رویدادها درطول کشیدن

درطول کنش کشیدن، شنوندگان رویداد کشیدن که در پاسخ به رویداد کشیدن ACTION_DRAG_STARTED، true را برمی‌گردانند، همچنان رویدادهای کشیدن را دریافت می‌کنند. انواع رویدادهای کشیدن که شنونده درطول کشیدن دریافت می‌کند به مکان سایه کشیدن و نمایان بودن 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 دیگر هدف رها کردن قریب‌الوقوع نیست.

پاسخ دادن به ارسال

When the user releases the drag shadow over a View, and the View previously reports that it can accept the content being dragged, the system dispatches a drag event to the View with the action type ACTION_DROP.

شنونده رویداد کشیدن باید کارهای زیر را انجام دهد:

  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 که شنونده رویداد کشیدن آن رویدادهای کشیدن را دریافت نمی‌کند، مناطق خالی واسط کاربر برنامه شما، یا حتی روی مناطق خارج از برنامه شما رها کند، Android رویدادی با نوع کنش ACTION_DROP ارسال نخواهد کرد و فقط رویداد ACTION_DRAG_ENDED را ارسال خواهد کرد.

پاسخ دادن به پایان کشیدن

بلافاصله پس‌از اینکه کاربر سایه کشیدن را رها می‌کند، سیستم یک رویداد کشیدن با نوع کنش ACTION_DRAG_ENDED به همه شنوندگان رویداد کشیدن در برنامه شما ارسال می‌کند. این نشان می‌دهد که عملیات کشیدن به‌پایان رسیده است.

هر شنونده رویداد کشیدن باید کارهای زیر را انجام دهد:

  1. اگر شنونده درطول عملیات ظاهر خود را تغییر دهد، باید به ظاهر پیش‌فرض خود بازگردد تا به‌عنوان نشانه‌ای بصری به کاربر نشان دهد که عملیات به‌پایان رسیده است.
  2. شنونده می‌تواند به‌طور اختیاری با getResult() تماس بگیرد تا اطلاعات بیشتری درباره عملیات کسب کند. اگر شنونده‌ای در پاسخ به رویدادی از نوع کنش ACTION_DROP، مقدار true را برگرداند، getResult() مقدار بولی true را برمی‌گرداند. در همه موارد دیگر، getResult() مقدار بولی false را برمی‌گرداند، ازجمله وقتی سیستم رویداد ACTION_DROP را ارسال نمی‌کند.
  3. برای نشان دادن تکمیل موفقیت‌آمیز عملیات رها کردن، شنونده باید بولی true را به سیستم برگرداند. با برنگرداندن false، یک نشانه بصری که نشان می‌دهد سایه به منبع خود برمی‌گردد ممکن است به کاربر نشان دهد که عملیات ناموفق بوده است.

پاسخ دادن به رویدادهای کشیدن: یک مثال

همه رویدادهای کشیدن توسط روش رویداد کشیدن یا شنونده شما دریافت می‌شوند. تکه‌کد زیر نمونه‌ای از پاسخ دادن به رویدادهای کشیدن است:

کاتلین

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;

});

سفارشی‌سازی سایه کشیدن

می‌توانید با لغو کردن روش‌ها در View.DragShadowBuilder، myDragShadowBuilder سفارشی‌شده‌ای را تعریف کنید. تکه‌کد زیر یک سایه کشیدن خاکستری، مستطیلی، و کوچک برای TextView ایجاد می‌کند:

کاتلین

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