Görünümlerle sürükle ve bırak işlevini uygulayın

Sürükleme başlatma işlemini tetikleyebilecek ve bırakma etkinliklerini yanıtlayıp tüketmelerine yol açabilecek etkinliklere yanıt vererek, sürükle ve bırak işleminizi görünümlere uygulayabilirsiniz.

Sürükleme başlatın

Kullanıcı, genellikle sürüklemek istediği öğeye dokunup basılı tutarak veya tıklayarak bir hareketle sürükleme başlatır.

Bunu bir View içinde işlemek için taşınan veri için bir ClipData ve ClipData.Item nesnesi oluşturun. ClipData kapsamında, ClipData içindeki bir ClipDescription nesnesinde depolanan meta verileri sağlayın. Veri taşımayı temsil etmeyen bir sürükle ve bırak işlemi için gerçek bir nesne yerine null kullanmak isteyebilirsiniz.

Örneğin bu kod snippet'i, ImageView üzerinde bir dokunma ve basılı tutma hareketine ImageView öğesinin etiketini (veya etiketini) içeren bir ClipData nesnesi oluşturarak nasıl yanıt verileceğini gösterir:

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

Sürükleme başlangıcına yanıt verme

Sürükleme işlemi sırasında sistem, sürükleme etkinliklerini mevcut düzendeki View nesnelerinin sürükleme etkinliği dinleyicilerine gönderir. Dinleyiciler işlem türünü öğrenmek için DragEvent.getAction() çağrısı yaparak tepki verir. Bir sürüklemenin başlangıcında bu yöntem ACTION_DRAG_STARTED değerini döndürür.

ACTION_DRAG_STARTED işlem türüne sahip bir etkinliğe yanıt olarak, bir sürükleme etkinliği işleyicisi aşağıdakileri yapmalıdır:

  1. İşleyicinin sürüklenen verileri kabul edip edemeyeceğini görmek için DragEvent.getClipDescription() yöntemini çağırın ve döndürülen ClipDescription öğesindeki MIME türü yöntemlerini kullanın.

    Sürükle ve bırak işlemi, veri taşımayı temsil etmiyorsa bu işlem gerekli olmayabilir.

  2. Drag etkinliği işleyici bir bırakma işlemini kabul edebiliyorsa sisteme sürükleme etkinliklerini işleyiciye göndermeye devam etmesini bildirmek için true değerini döndürmelidir. İşleyici bir bırakma işlemini kabul edemezse işleyicinin false değerini döndürmesi gerekir ve sistem, sürükle ve bırak işlemini sonlandırmak için ACTION_DRAG_ENDED gönderene kadar sistem tarafından işleyiciye sürükleme etkinlikleri göndermeyi durdurur.

Bir ACTION_DRAG_STARTED etkinliği için şu DragEvent yöntemleri geçerli değildir: getClipData(), getX(), getY() ve getResult().

Sürükleme sırasında etkinlikleri işleme

Sürükleme işlemi sırasında, ACTION_DRAG_STARTED sürükleme etkinliğine yanıt olarak true değerini döndüren sürükleme etkinliği işleyicileri sürükleme etkinliklerini almaya devam eder. Bir işleyicinin sürükleme sırasında aldığı sürükleme etkinliklerinin türleri, sürükleme gölgesinin konumuna ve işleyicinin View görünürlüğüne bağlıdır. İşleyiciler, View öğelerinin görünümünü değiştirip değiştirmeyeceklerine karar vermek için öncelikle sürükleme etkinliklerini kullanır.

Sürükleme işlemi sırasında, DragEvent.getAction() aşağıdaki üç değerden birini döndürür:

  • ACTION_DRAG_ENTERED: Dokunmatik nokta (ekranda kullanıcı parmağının veya farenin altındaki nokta) işleyicinin View sınırlayıcı kutusuna girdiğinde işleyici bu etkinlik işlemi türünü alır.
  • ACTION_DRAG_LOCATION: İşleyici bir ACTION_DRAG_ENTERED etkinliği aldığında, bir ACTION_DRAG_EXITED etkinliği alana kadar temas noktası her hareket ettiğinde yeni bir ACTION_DRAG_LOCATION etkinliği alır. getX() ve getY() yöntemleri, temas noktasının X ve Y koordinatlarını döndürür.
  • ACTION_DRAG_EXITED: Bu etkinlik işlemi türü, daha önce ACTION_DRAG_ENTERED alan bir işleyiciye gönderilir. Sürükleme gölgesi temas noktası, işleyicinin View sınırlayıcı kutusundan sınırlayıcı kutunun dışına hareket ettiğinde etkinlik gönderilir.

Sürükleme etkinliği işleyicinin bu işlem türlerinin hiçbirine tepki vermesi gerekmez. İşleyici sisteme bir değer döndürürse bu değer yoksayılır.

Aşağıda, bu işlem türlerinin her birine yanıt vermeyle ilgili bazı yönergeleri bulabilirsiniz:

  • İşleyici, ACTION_DRAG_ENTERED veya ACTION_DRAG_LOCATION verilerine yanıt olarak, görünümün potansiyel bir düşüş hedefi olduğunu belirtmek için View görünümünü değiştirebilir.
  • ACTION_DRAG_LOCATION işlem türüne sahip bir etkinlik, temas noktasının konumuna karşılık gelen getX() ve getY() için geçerli veriler içerir. İşleyici, temas noktasındaki View görünümünü değiştirmek veya kullanıcının içeriği bırakabileceği tam konumu belirlemek için bu bilgileri kullanabilir.
  • İşleyici, ACTION_DRAG_EXITED özelliğine yanıt olarak ACTION_DRAG_ENTERED veya ACTION_DRAG_LOCATION için geçerli olduğu tüm görünüm değişikliklerini sıfırlamalıdır. Bu, kullanıcıya View öğesinin artık yaklaşan bir düşüş hedefi olmadığını belirtir.

Düşüşe yanıt verin

Kullanıcı bir View üzerine sürükleme gölgesini serbest bıraktığında ve View daha önce sürüklenmekte olan içeriği kabul edebileceğini bildirdiğinde sistem, View öğesine ACTION_DROP işlem türüyle bir sürükleme etkinliği gönderir.

Sürükleme etkinliği işleyici aşağıdakileri yapmalıdır:

  1. startDragAndDrop() çağrısında orijinal olarak sağlanan ClipData nesnesini almak ve verileri işlemek için getClipData() yöntemini çağırın. Sürükle ve bırak işlemi, veri hareketini temsil etmiyorsa bu gerekli değildir.

  2. Düşüşün başarıyla işlendiğini belirtmek için true boole değerini veya işlenmemişse false boole'sini döndürün. Döndürülen değer, nihai ACTION_DRAG_ENDED etkinliği için getResult() tarafından döndürülen değer haline gelir. Sistem bir ACTION_DROP etkinliği göndermezse bir ACTION_DRAG_ENDED etkinliği için getResult() tarafından döndürülen değer false olur.

Bir ACTION_DROP etkinliğinde getX() ve getY(), düşüşü alan View koordinat sistemini kullanarak düşüş anındaki temas noktasının X ve Y konumunu döndürür.

Kullanıcı, sürükleme etkinliği dinleyicisi sürükleme etkinliği almayan bir View, uygulamanızın kullanıcı arayüzünün boş bölgeleri ve hatta uygulamanızın dışındaki alanlar üzerinde sürükleme gölgesini serbest bırakabilse de, Android ACTION_DROP işlem türüne sahip bir etkinlik göndermez ve yalnızca bir ACTION_DRAG_ENDED etkinliği gönderir.

Sürükleme sonucuna yanıt verme

Kullanıcı sürükleme gölgesini serbest bıraktıktan hemen sonra, sistem uygulamanızdaki tüm sürükleme etkinliği işleyicilerine ACTION_DRAG_ENDED işlem türünde bir sürükleme etkinliği gönderir. Bu, sürükleme işleminin tamamlandığını gösterir.

Her sürükleme etkinliği işleyici aşağıdakileri yapmalıdır:

  1. İşleyici, işlem sırasında görünümünü değiştirirse kullanıcıya işlemin tamamlandığını gösteren görsel bir işaret olarak varsayılan görünümüne sıfırlanmalıdır.
  2. İşleyici, işlem hakkında daha fazla bilgi edinmek için isteğe bağlı olarak getResult() yöntemini çağırabilir. Bir işleyici, ACTION_DROP işlem türündeki bir etkinliğe yanıt olarak true değerini döndürürse getResult(), true boole değerini döndürür. Diğer tüm durumlarda getResult(), sistemin ACTION_DROP etkinliği göndermediği durumlar da dahil olmak üzere false boole değerini döndürür.
  3. Bırakma işleminin başarıyla tamamlandığını belirtmek için dinleyici, sisteme true boole değerini döndürmelidir. false öğesi döndürülmediğinde, gölgenin kaynağına geri döndüğünü gösteren görsel bir ipucu, kullanıcıya işlemin başarısız olduğunu gösterebilir.

Sürükleme etkinliklerine yanıt verme: Bir örnek

Tüm sürükleme etkinlikleri, sürükleme etkinliği yönteminiz veya işleyiciniz tarafından alınır. Aşağıdaki kod snippet'i, sürükleme etkinliklerine yanıt vermeye ilişkin bir örnektir:

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;

});

Sürükleme gölgesini özelleştirme

View.DragShadowBuilder içindeki yöntemleri geçersiz kılarak özelleştirilmiş bir myDragShadowBuilder tanımlayabilirsiniz. Aşağıdaki kod snippet'i, TextView için küçük, dikdörtgen ve gri bir sürükleme gölgesi oluşturur:

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