Google berkomitmen untuk mendorong terwujudnya keadilan ras bagi komunitas Kulit Hitam. Lihat caranya.

Tarik lalu lepas

Dengan framework tarik/lepas Android, Anda dapat mengizinkan pengguna memindahkan data dari satu View ke View lainnya menggunakan gestur grafis tarik lalu lepas. Framework ini mencakup class peristiwa tarik, pemroses tarik, serta class dan metode helper.

Meskipun framework ini utamanya didesain untuk pemindahan data, Anda dapat menggunakannya untuk tindakan UI lainnya. Misalnya, Anda dapat membuat aplikasi yang menggabungkan warna saat pengguna menarik sebuah ikon warna ke atas ikon warna yang lain. Namun, bagian selanjutnya dari topik ini akan menjelaskan framework dari segi pemindahan data.

Anda juga akan melihat referensi terkait berikut ini:

Ringkasan

Operasi tarik lalu lepas dimulai saat pengguna membuat gestur yang Anda kenali sebagai sinyal untuk mulai menarik data. Sebagai respons, aplikasi Anda memberi tahu sistem bahwa operasi tarik akan dimulai. Sistem melakukan callback ke aplikasi Anda untuk mendapatkan representasi dari data yang ditarik. Saat jari pengguna memindahkan representasi ini ("bayangan tarik") ke atas tata letak saat ini, sistem akan mengirimkan peristiwa tarik ke objek pemroses dan metode callback peristiwa tarik yang terkait dengan objek View dalam tata letak itu. Setelah pengguna melepas bayangan tarik, sistem akan mengakhiri operasi tarik tersebut.

Objek pemroses peristiwa tarik ("pemroses") dibuat dari class yang mengimplementasikan View.OnDragListener. Objek pemroses peristiwa tarik untuk sebuah View ditetapkan dengan metode setOnDragListener() objek View tersebut. Setiap objek View juga memiliki metode callback onDragEvent(). Keduanya dijelaskan secara lebih mendetail di bagian Pemroses dan metode callback peristiwa tarik.

Catatan: Agar lebih praktis, bagian-bagian di bawah menyebut rutinitas yang menerima peristiwa tarik sebagai "pemroses peristiwa tarik", meskipun sebenarnya itu adalah metode callback.

Saat memulai operasi tarik, Anda menyertakan data yang Anda pindahkan dan metadata yang menjelaskan data ini sebagai bagian dari panggilan ke sistem. Selama operasi tarik, sistem akan mengirim peristiwa tarik ke pemroses atau metode callback peristiwa tarik dari setiap View dalam tata letak. Pemroses atau metode callback dapat menggunakan metadata ini untuk memutuskan apakah ingin menerima data saat data tersebut dilepas. Jika pengguna melepas data di atas sebuah objek View, dan pemroses atau metode callback objek View itu telah memberi tahu sistem bahwa ia ingin menerima data yang dilepas tersebut, sistem akan mengirimkan data itu ke pemroses atau metode callback dalam peristiwa tarik.

Aplikasi Anda meminta sistem untuk memulai operasi tarik dengan memanggil metode startDrag(). Tindakan ini akan memberi tahu sistem untuk mulai mengirimkan peristiwa tarik. Metode ini juga mengirimkan data yang sedang Anda tarik.

Anda dapat memanggil startDrag() untuk View terkait apa pun dalam tata letak saat ini. Sistem hanya menggunakan objek View untuk mendapatkan akses ke setelan global dalam tata letak Anda.

Setelah aplikasi Anda memanggil startDrag(), proses selanjutnya akan menggunakan peristiwa yang dikirim oleh sistem ke objek View dalam tata letak Anda saat ini.

Catatan: Jika aplikasi berjalan dalam mode multi-aplikasi, pengguna dapat menarik lalu melepaskan data dari satu aplikasi ke aplikasi lainnya. Untuk informasi selengkapnya, lihat Mendukung operasi tarik lalu lepas.

Proses tarik/lepas

Pada dasarnya, ada empat langkah atau status dalam proses tarik lalu lepas:

Dimulai
Sebagai respons terhadap gestur pengguna untuk memulai proses tarik, aplikasi Anda akan memanggil startDrag() untuk meminta sistem memulai proses tarik. Argumen startDrag() menyediakan data yang akan ditarik, metadata untuk data ini, dan callback untuk menggambar bayangan tarik.

Sistem mula-mula merespons dengan melakukan callback ke aplikasi Anda untuk mendapatkan bayangan tarik. Kemudian, sistem akan menampilkan bayangan tarik di perangkat.

Selanjutnya, sistem mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_STARTED ke pemroses peristiwa tarik untuk semua objek View dalam tata letak saat ini. Untuk terus menerima peristiwa tarik, termasuk kemungkinan peristiwa lepas, pemroses peristiwa tarik harus menampilkan true. Tindakan ini mendaftarkan pemroses ke sistem. Hanya pemroses terdaftar yang akan terus menerima peristiwa tarik. Pada tahap ini, pemroses juga dapat mengubah tampilan objek View mereka untuk menunjukkan bahwa pemroses dapat menerima peristiwa lepas.

Jika pemroses peristiwa tarik menampilkan false, maka ia tidak akan menerima peristiwa tarik untuk operasi saat ini hingga sistem mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED. Dengan mengirimkan false, pemroses memberi tahu sistem bahwa ia tidak tertarik dengan operasi tarik itu dan tidak ingin menerima data yang ditarik.

Melanjutkan
Pengguna melanjutkan proses tarik. Saat bayangan tarik memotong kotak pembatas dari sebuah objek View, sistem akan mengirimkan satu atau beberapa peristiwa tarik ke pemroses peristiwa tarik untuk objek View tersebut (jika terdaftar untuk menerima peristiwa). Pemroses dapat memilih untuk mengubah tampilan objek View-nya sebagai respons terhadap peristiwa tersebut. Misalnya, jika peristiwa itu mengindikasikan bahwa bayangan tarik telah memasuki kotak pembatas View (jenis tindakan ACTION_DRAG_ENTERED), pemroses dapat bereaksi dengan menyorot View-nya.
Dilepas
Pengguna melepas bayangan tarik di dalam kotak pembatas View yang dapat menerima data. Sistem mengirimkan peristiwa tarik ke pemroses objek View dengan jenis tindakan ACTION_DROP. Peristiwa tarik berisi data yang diteruskan ke sistem dalam panggilan ke startDrag() yang memulai operasi ini. Pemroses diharapkan menampilkan boolean true ke sistem jika kode untuk menerima peristiwa lepas berhasil.

Perhatikan bahwa langkah ini hanya terjadi jika pengguna melepas bayangan tarik di dalam kotak pembatas dari sebuah View yang pemrosesnya terdaftar untuk menerima peristiwa tarik. Jika pengguna melepas bayangan tarik dalam situasi lain, tidak ada peristiwa tarik ACTION_DROP yang dikirim.

Selesai
Setelah pengguna melepas bayangan tarik, dan setelah sistem mengirim (jika perlu) peristiwa tarik dengan jenis tindakan ACTION_DROP, sistem akan mengirimkan peristiwa tarik dengan jenis tindakan ACTION_DRAG_ENDED untuk menunjukkan bahwa operasi tarik telah selesai. Hal ini dilakukan di mana pun pengguna melepas bayangan tarik. Peristiwa ini dikirim ke setiap pemroses yang terdaftar untuk menerima peristiwa tarik, meskipun pemroses tersebut menerima peristiwa ACTION_DROP.

Masing-masing dari keempat langkah tersebut dijelaskan secara lebih mendetail di bagian Mendesain Operasi Tarik lalu Lepas.

Pemroses dan metode callback peristiwa tarik

View menerima peristiwa tarik dengan pemroses peristiwa tarik yang mengimplementasikan View.OnDragListener, atau dengan metode callback onDragEvent(DragEvent)-nya. Saat memanggil metode atau pemroses, sistem akan meneruskannya ke objek DragEvent.

Dalam sebagian besar kasus, sebaiknya Anda menggunakan pemroses. Saat mendesain UI, biasanya Anda tidak membuat subclass dari class View, tetapi penggunaan metode callback akan memaksa Anda melakukan hal ini agar dapat mengganti metode. Sebagai perbandingan, Anda dapat mengimplementasikan satu class pemroses lalu menggunakannya dengan beberapa objek View yang berbeda. Anda juga dapat mengimplementasikannya sebagai class inline anonim. Untuk menetapkan pemroses bagi objek View, panggil setOnDragListener().

Anda dapat menggunakan pemroses dan juga metode callback untuk objek View. Jika ini terjadi, sistem akan memanggil pemroses terlebih dahulu. Sistem tidak memanggil metode callback kecuali jika pemroses menampilkan false.

Gabungan dari metode onDragEvent(DragEvent) dan View.OnDragListener serupa dengan gabungan dari onTouchEvent() dan View.OnTouchListener yang digunakan dengan peristiwa sentuh.

Peristiwa tarik

Sistem mengirim peristiwa tarik dalam bentuk objek DragEvent. Objek ini berisi jenis tindakan yang memberi tahu pemroses apa yang terjadi dalam proses tarik/lepas. Objek ini berisi data lain, bergantung pada jenis tindakannya.

Untuk mendapatkan jenis tindakan, pemroses akan memanggil getAction(). Ada enam kemungkinan nilai, yang ditentukan oleh konstanta dalam class DragEvent. Nilai ini tercantum dalam tabel 1.

Objek DragEvent juga berisi data yang diberikan oleh aplikasi Anda ke sistem dalam panggilan ke startDrag(). Sebagian data ini hanya dapat digunakan untuk jenis tindakan tertentu. Data yang dapat digunakan untuk setiap jenis tindakan dirangkum dalam tabel 2. Hal ini juga dijelaskan secara mendetail beserta peristiwa yang dapat menggunakan data tersebut di bagian Mendesain Operasi Tarik lalu Lepas.

Tabel 1. Jenis tindakan DragEvent

Nilai getAction() Arti
ACTION_DRAG_STARTED Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini tepat setelah aplikasi memanggil startDrag() dan mendapatkan bayangan tarik.
ACTION_DRAG_ENTERED Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini saat bayangan tarik baru saja memasuki kotak pembatas View. Ini adalah jenis tindakan peristiwa pertama yang diterima pemroses saat bayangan tarik memasuki kotak pembatas. Jika pemroses ingin terus menerima peristiwa tarik untuk operasi ini, pemroses harus menampilkan boolean true ke sistem.
ACTION_DRAG_LOCATION Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini setelah menerima peristiwa ACTION_DRAG_ENTERED sementara bayangan tarik masih berada dalam kotak pembatas View.
ACTION_DRAG_EXITED Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini setelah menerima ACTION_DRAG_ENTERED dan minimal satu peristiwa ACTION_DRAG_LOCATION, dan setelah pengguna memindahkan bayangan tarik ke luar kotak pembatas View.
ACTION_DROP Pemroses peristiwa tarik objek View menerima jenis tindakan ini saat pengguna melepas bayangan tarik ke atas objek View. Jenis tindakan ini hanya dikirim ke pemroses objek View jika pemroses menampilkan boolean true sebagai respons terhadap peristiwa tarik ACTION_DRAG_STARTED. Jenis tindakan ini tidak dikirim jika pengguna melepas bayangan tarik di atas View yang pemrosesnya tidak terdaftar, atau jika pengguna melepas bayangan tarik di atas apa pun yang bukan bagian dari tata letak saat ini.

Pemroses diharapkan menampilkan boolean true jika berhasil memproses peristiwa lepas. Jika tidak, proses harus menampilkan false.

ACTION_DRAG_ENDED Pemroses peristiwa tarik objek View menerima jenis tindakan peristiwa ini saat sistem mengakhiri operasi tarik. Jenis tindakan ini tidak perlu didahului oleh peristiwa ACTION_DROP. Jika sistem mengirim ACTION_DROP, penerimaan jenis tindakan ACTION_DRAG_ENDED tidak mengimplikasikan bahwa operasi lepas berhasil. Pemroses harus memanggil getResult() untuk mendapatkan nilai yang ditampilkan sebagai respons terhadap ACTION_DROP. Jika peristiwa ACTION_DROP tidak terkirim, maka getResult() akan menampilkan false.

Tabel 2. Data DragEvent yang valid menurut jenis tindakan

NIlai getAction() NIlai getClipDescription() NIlai getLocalState() NIlai getX() NIlai getY() NIlai getClipData() NIlai 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

Metode getAction(), describeContents(), writeToParcel(), dan toString() selalu menampilkan data yang valid.

Jika sebuah metode tidak berisi data yang valid untuk jenis tindakan tertentu, metode tersebut akan menampilkan null atau 0, bergantung pada jenis hasilnya.

Bayangan tarik

Selama operasi tarik lalu lepas, sistem akan menampilkan gambar yang ditarik oleh pengguna. Untuk pemindahan data, gambar ini mewakili data yang sedang ditarik. Untuk operasi lainnya, gambar ini mewakili beberapa aspek operasi tarik.

Gambar ini disebut bayangan tarik. Bayangan tarik dibuat dengan metode yang Anda deklarasikan untuk objek View.DragShadowBuilder, lalu diteruskan ke sistem saat Anda memulai peristiwa tarik menggunakan startDrag(). Sebagai bagian dari respons terhadap startDrag(), sistem memanggil metode callback yang telah Anda tentukan di View.DragShadowBuilder untuk mendapatkan bayangan tarik.

Class View.DragShadowBuilder memiliki dua constructor:

View.DragShadowBuilder(View)
Constructor ini menerima objek View apa pun dari aplikasi Anda. Constructor menyimpan objek View dalam objek View.DragShadowBuilder, sehingga selama callback, Anda dapat mengaksesnya saat membuat bayangan tarik. Constructor ini tidak harus dikaitkan dengan View (jika ada) yang dipilih pengguna untuk memulai operasi tarik.

Jika menggunakan constructor ini, Anda tidak perlu memperluas View.DragShadowBuilder atau mengganti metodenya. Secara default, Anda akan mendapatkan bayangan tarik yang tampilannya sama dengan View yang Anda teruskan sebagai argumen, yang berpusat di bawah lokasi tempat pengguna menyentuh layar.

View.DragShadowBuilder()
Jika Anda menggunakan constructor ini, tidak ada objek View yang tersedia di View.DragShadowBuilder objek (kolom ini ditetapkan ke null). Jika menggunakan constructor ini, dan tidak memperluas View.DragShadowBuilder atau mengganti metodenya, Anda akan mendapatkan bayangan tarik yang tak terlihat. Sistem tidak menampilkan error.

Class View.DragShadowBuilder memiliki dua metode:

onProvideShadowMetrics()
Sistem akan memanggil metode ini segera setelah Anda memanggil startDrag(). Gunakan metode ini untuk mengirimkan dimensi dan titik kontak bayangan tarik ke sistem. Metode ini memiliki dua argumen:
dimension
Objek Point. Lebar bayangan tarik masuk di x dan tingginya masuk di y.
touch_point
Objek Point. Touch point (titik kontak) adalah lokasi dalam bayangan tarik yang berada di bawah jari pengguna selama operasi tarik. Posisi X-nya masuk di x sedangkan posisi Y-nya masuk di y
onDrawShadow()
Segera setelah panggilan ke onProvideShadowMetrics(), sistem akan memanggil onDrawShadow() untuk mendapatkan bayangan tarik itu sendiri. Metode ini memiliki argumen tunggal, objek Canvas yang dibuat oleh sistem dari parameter yang Anda masukkan dalam onProvideShadowMetrics(). Gunakan argumen ini untuk menggambar bayangan tarik dalam objek Canvas yang disediakan.

Untuk meningkatkan performa, sebaiknya ukuran bayangan tarik dipertahankan sekecil mungkin. Untuk satu item, sebaiknya Anda menggunakan ikon. Untuk beberapa pilihan, Anda dapat menggunakan ikon dalam stack, bukan gambar penuh yang menyebar ke layar.

Mendesain Operasi Tarik lalu Lepas

Bagian ini menampilkan langkah-langkah cara memulai peristiwa tarik, cara merespons peristiwa selama operasi tarik, cara merespons peristiwa lepas, dan cara mengakhiri operasi tarik lalu lepas.

Memulai operasi tarik

Pengguna memulai operasi tarik dengan gestur menarik, biasanya dengan menekan lama objek View. Sebagai respons, Anda harus melakukan hal berikut:

  1. Jika diperlukan, buat ClipData dan ClipData.Item untuk data yang sedang dipindahkan. Sebagai bagian dari objek ClipData, berikan metadata yang tersimpan di objek ClipDescription dalam ClipData. Untuk operasi tarik lalu lepas yang tidak merepresentasikan perpindahan data, Anda dapat menggunakan null alih-alih objek yang sebenarnya.

    Misalnya, cuplikan kode ini menunjukkan cara merespons gestur tekan lama pada ImageView dengan membuat objek ClipData yang berisi tag atau label sebuah ImageView. Setelah cuplikan ini, cuplikan berikutnya menunjukkan cara mengganti metode dalam View.DragShadowBuilder:

    Kotlin

        const val IMAGEVIEW_TAG = "icon bitmap"
        ...
        val imageView = ImageView(this).apply {
            setImageBitmap(iconBitmap)
            tag = IMAGEVIEW_TAG
            imageView.setOnLongClickListener { v: View ->
                // 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 will create a new ClipDescription object within the
                // ClipData, and set its MIME type entry to "text/plain"
                val dragData = ClipData(
                        v.tag as? CharSequence,
                        arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
                        item)
    
                // Instantiates the drag shadow builder.
                val myShadow = MyDragShadowBuilder(this)
    
                // 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)
                )
            }
        }
        

    Java

        // 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(iconBitmap);
    
        // 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(),
                new String[] { ClipDescription.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. Cuplikan kode berikut menentukan myDragShadowBuilder. Cuplikan ini menghasilkan bayangan tarik untuk menarik TextView sebagai kotak kecil berwarna abu-abu:

    Kotlin

        private class MyDragShadowBuilder(v: View) : View.DragShadowBuilder(v) {
    
            private val shadow = ColorDrawable(Color.LTGRAY)
    
            // Defines a callback that sends the drag shadow dimensions and touch point back to the
            // system.
            override fun onProvideShadowMetrics(size: Point, touch: Point) {
                // Sets the width of the shadow to half the width of the original View
                val width: Int = view.width / 2
    
                // Sets 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. 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 fun onDrawShadow(canvas: Canvas) {
                // Draws the ColorDrawable in 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 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);
                }
            }
        

    Catatan: Ingatlah bahwa Anda tidak perlu memperluas View.DragShadowBuilder. Constructor View.DragShadowBuilder(View) menghasilkan bayangan tarik default yang ukurannya sama dengan argumen View yang diteruskan ke constructor tersebut, dengan titik kontak yang berada di tengah-tengah bayangan tarik.

Merespons awal operasi tarik

Selama operasi tarik, sistem mengirim peristiwa tarik ke pemroses peristiwa tarik untuk objek View dalam tata letak saat ini. Pemroses ini harus bereaksi dengan memanggil getAction() untuk mendapatkan jenis tindakan. Pada awal proses tarik, metode ini menampilkan ACTION_DRAG_STARTED.

Sebagai respons terhadap peristiwa dengan jenis tindakan ACTION_DRAG_STARTED, pemroses harus melakukan tindakan berikut:

  1. Memanggil getClipDescription() untuk mendapatkan ClipDescription. Gunakan metode jenis MIME di ClipDescription untuk melihat apakah pemroses dapat menerima data yang sedang ditarik.

    Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini mungkin tidak diperlukan.

  2. Jika dapat menerima operasi lepas, pemroses harus menampilkan true. Tindakan ini akan memberi tahu sistem untuk terus mengirim peristiwa tarik ke pemroses. Jika tidak dapat menerima peristiwa lepas, pemroses harus menampilkan false, dan sistem akan berhenti mengirimkan peristiwa tarik hingga ACTION_DRAG_ENDED terkirim.

Perhatikan bahwa untuk peristiwa ACTION_DRAG_STARTED, metode DragEvent berikut tidak valid: getClipData(), getX(), getY(), dan getResult().

Menangani peristiwa selama operasi tarik

Selama proses tarik, pemroses yang menampilkan true sebagai respons terhadap peristiwa tarik ACTION_DRAG_STARTED terus menerima peristiwa tarik. Jenis peristiwa tarik yang diterima pemroses selama operasi tarik bergantung pada lokasi bayangan tarik dan visibilitas View pemroses.

Selama operasi tarik, biasanya pemroses menggunakan peristiwa tarik untuk memutuskan apakah perlu mengubah tampilan View-nya atau tidak.

Selama operasi tarik, getAction() menampilkan satu dari tiga nilai berikut:

Pemroses tidak perlu bereaksi terhadap salah satu jenis tindakan ini. Jika pemroses menampilkan nilai ke sistem, nilai tersebut akan diabaikan. Berikut adalah beberapa panduan untuk merespons setiap jenis tindakan:

  • Sebagai respons terhadap ACTION_DRAG_ENTERED atau ACTION_DRAG_LOCATION, pemroses dapat mengubah tampilan View untuk mengindikasikan bahwa ia akan segera menerima operasi lepas.
  • Peristiwa dengan jenis tindakan ACTION_DRAG_LOCATION berisi data yang valid untuk getX() dan getY(), yang bersesuaian dengan lokasi titik kontak. Pemroses dapat menggunakan informasi ini untuk mengubah tampilan bagian tersebut dari View yang berada di titik kontak. Pemroses juga dapat menggunakan informasi ini untuk menentukan posisi persis di mana pengguna akan melepas bayangan tarik.
  • Sebagai respons terhadap ACTION_DRAG_EXITED, pemroses perlu menyetel ulang perubahan tampilan apa pun yang diterapkannya sebagai respons terhadap ACTION_DRAG_ENTERED atau ACTION_DRAG_LOCATION. Hal ini menunjukkan kepada pengguna bahwa View tersebut tidak lagi menjadi target operasi lepas yang akan segera terjadi.

Merespons operasi lepas

Jika pengguna melepas bayangan tarik di atas sebuah View dalam aplikasi, dan View itu sebelumnya melaporkan bahwa ia dapat menerima konten yang sedang ditarik, maka sistem akan mengirimkan peristiwa tarik ke View tersebut dengan jenis tindakan ACTION_DROP. Pemroses harus melakukan hal berikut:

  1. Memanggil getClipData() untuk mendapatkan objek ClipData yang awalnya disediakan dalam panggilan ke startDrag() dan menyimpannya. Jika operasi tarik lalu lepas tidak merepresentasikan perpindahan data, tindakan ini mungkin tidak diperlukan.
  2. Menampilkan boolean true untuk mengindikasikan bahwa operasi lepas berhasil diproses, atau boolean false jika operasi lepas tidak berhasil. Nilai yang ditampilkan menjadi nilai yang ditampilkan oleh getResult() untuk peristiwa ACTION_DRAG_ENDED.

    Perhatikan bahwa jika sistem tidak mengirimkan peristiwa ACTION_DROP, nilai getResult() untuk peristiwa ACTION_DRAG_ENDED adalah false.

Untuk peristiwa ACTION_DROP, getX(), dan getY() menampilkan posisi X dan Y titik tarik pada saat dilepas, menggunakan sistem koordinat View yang menerima operasi lepas.

Sistem memungkinkan pengguna melepas bayangan tarik di atas View yang pemrosesnya tidak menerima peristiwa tarik. Sistem juga memungkinkan pengguna melepas bayangan tarik di area kosong dalam UI aplikasi, atau area di luar aplikasi Anda. Dalam semua kasus ini, sistem tidak mengirimkan peristiwa dengan jenis tindakan ACTION_DROP, tetapi tetap mengirimkan peristiwa ACTION_DRAG_ENDED.

Merespons akhir operasi tarik

Segera setelah pengguna melepas bayangan tarik, sistem akan mengirimkan peristiwa tarik ke semua pemroses peristiwa tarik dalam aplikasi Anda, dengan jenis tindakan ACTION_DRAG_ENDED. Ini menandakan bahwa operasi tarik telah berakhir.

Setiap pemroses harus melakukan berikut ini:

  1. Jika sudah mengubah tampilan objek View-nya selama operasi, pemroses harus menyetel ulang View ke tampilan default-nya. Ini adalah indikasi visual bagi pengguna bahwa operasi telah selesai.
  2. Jika diinginkan, pemroses dapat memanggil getResult() untuk mengetahui operasi tersebut lebih lanjut. Jika pemroses menampilkan true sebagai respons terhadap peristiwa dengan jenis tindakan ACTION_DROP, maka getResult() akan menampilkan boolean true. Dalam semua kasus lainnya, getResult() akan menampilkan boolean false, termasuk kasus apa pun ketika sistem tidak mengirimkan peristiwa ACTION_DROP.
  3. Pemroses harus menampilkan boolean true ke sistem.

Merespons peristiwa tarik: contoh

Semua peristiwa tarik awalnya diterima oleh metode atau pemroses peristiwa tarik. Cuplikan kode berikut adalah contoh sederhana dari bereaksi terhadap peristiwa tarik di sebuah pemroses:

Kotlin

    // Creates a new drag event listener
    private val dragListen = View.OnDragListener { v, event ->

        // Handles each of the expected events
        when (event.action) {
            DragEvent.ACTION_DRAG_STARTED -> {
                // Determines if this View can accept the dragged data
                if (event.clipDescription.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 as? ImageView)?.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.
                    true
                } else {
                    // Returns false. During the current drag and drop operation, this View will
                    // not receive events again until ACTION_DRAG_ENDED is sent.
                    false
                }
            }
            DragEvent.ACTION_DRAG_ENTERED -> {
                // Applies a green tint to the View. Return true; the return value is ignored.
                (v as? ImageView)?.setColorFilter(Color.GREEN)

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

            DragEvent.ACTION_DRAG_LOCATION ->
                // Ignore the event
                true
            DragEvent.ACTION_DRAG_EXITED -> {
                // Re-sets the color tint to blue. Returns true; the return value is ignored.
                (v as? ImageView)?.setColorFilter(Color.BLUE)

                // Invalidate the view to force a redraw in the new tint
                v.invalidate()
                true
            }
            DragEvent.ACTION_DROP -> {
                // Gets the item containing the dragged data
                val item: ClipData.Item = event.clipData.getItemAt(0)

                // Gets the text data from the item.
                val dragData = item.text

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

                // Turns off any color tints
                (v as? ImageView)?.clearColorFilter()

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

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

            DragEvent.ACTION_DRAG_ENDED -> {
                // Turns off any color tinting
                (v as? ImageView)?.clearColorFilter()

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

                // Does a getResult(), and displays what happened.
                when(event.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()

                // returns true; the value is ignored.
                true
            }
            else -> {
                // An unknown action type was received.
                Log.e("DragDrop Example", "Unknown action type received by OnDragListener.")
                false
            }
        }
    }
    ...
    val imageView = ImageView(this)

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

Java

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

    View imageView = new ImageView(this);

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

    ...

    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).show();

                    // 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).show();

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

                    }

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

Izin tarik dalam mode multi-aplikasi

Perangkat yang menjalankan Android 7.0 (API level 24) atau yang lebih tinggi mendukung mode multi-aplikasi, yang memungkinkan pengguna memindahkan data dari satu aplikasi ke aplikasi lain menggunakan operasi tarik lalu lepas:

  • Aplikasi sumber: Aplikasi yang awalnya berisi data. Di situlah operasi tarik dimulai.
  • Aplikasi target: Aplikasi yang menerima data. Di situlah operasi tarik berakhir.

Saat memulai operasi tarik lalu lepas, aplikasi sumber harus menetapkan tanda DRAG_FLAG_GLOBAL untuk menunjukkan bahwa pengguna dapat menarik data ke aplikasi lain.

Karena data berpindah melintasi batas aplikasi, aplikasi membagikan akses ke data menggunakan URI konten:

  • Aplikasi sumber harus menetapkan salah satu dari tanda DRAG_FLAG_GLOBAL_URI_READ dan DRAG_FLAG_GLOBAL_URI_WRITE, atau dua-duanya, yang bergantung pada akses baca/tulis ke data yang seharusnya dimiliki oleh aplikasi target.
  • Aplikasi target harus memanggil requestDragAndDropPermissions() segera sebelum menangani data yang ditarik pengguna ke dalam aplikasi. Jika aplikasi target tidak lagi memerlukan akses ke data tarik, aplikasi tersebut dapat memanggil release() pada objek yang ditampilkan dari requestDragAndDropPermissions(). Jika tidak, izin akan dilepas saat aktivitas yang memuatnya diakhiri.

Cuplikan kode berikut menunjukkan cara melepas akses baca saja ke data tarik segera setelah operasi tarik lalu lepas berlangsung. Contoh yang lebih lengkap dapat dilihat dalam sampel DragAndDropAcrossApps, yang tersedia di GitHub.

SourceDragAndDropActivity

Kotlin

    // Drag a file stored under an "images/" directory within internal storage.
    val internalImagesDir = File(context.filesDir, "images")
    val imageFile = File(internalImagesDir, file-name)
    val uri: Uri = FileProvider.getUriForFile(
            context, file-provider-content-authority, imageFile)

    // Container for where the image originally appears in the source app.
    val srcImageView = findViewById(R.id.my-image-id)

    val listener = DragStartHelper.OnDragStartListener = { view, _ ->
        val clipData = ClipData(clip-description, ClipData.Item(uri))

        // Must include DRAG_FLAG_GLOBAL to allow for dragging data between apps.
        // This example provides read-only access to the data.
        val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
        return@OnDragStartListener view.startDragAndDrop(
                clipData, drag-shadow-builder, null, flags)
    }

    // Detect and start the drag event.
    DragStartHelper(srcImageView, listener).apply {
        attach()
    }
    

Java

    // Drag a file stored under an "images/" directory within internal storage.
    File internalImagesDir = new File(context.filesDir, "images");
    File imageFile = new File(internalImagesDir, file-name);
    final Uri uri = FileProvider.getUriForFile(
            context, file-provider-content-authority, imageFile);

    // Container for where the image originally appears in the source app.
    ImageView srcImageView = findViewById(R.id.my-image-id);

    DragStartHelper.OnDragStartListener listener =
            new DragStartHelper.OnDragStartListener() {
                @Override
                public boolean onDragStart(View v, DragStartHelper helper) {
                    ClipData clipData = new ClipData(
                            clip-description, new ClipData.Item(uri));
                    // Must include DRAG_FLAG_GLOBAL to allow for dragging data
                    // between apps. This example provides read-only access
                    // to the data.
                    int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
                    return v.startDragAndDrop(clipData, drag-shadow-builder, null, flags);
                }
            };

    // Detect and start the drag event.
    DragStartHelper helper = new DragStartHelper(srcImageView, listener);
    helper.attach();
    

TargetDragAndDropActivity

Kotlin

    // Container for where the image is to be dropped in the target app.
    val targetImageView = findViewById<ImageView>(R.id.my-image-id)

    targetImageView.setOnDragListener { view, event ->
        when (event.action) {
            ACTION_DROP ->
                val imageItem: ClipData.Item = event.clipData.getItemAt(0)
                val uri = imageItem.uri

                // Request permission to access the image data being dragged into
                // the target activity's ImageView element.
                val dropPermissions = requestDragAndDropPermissions(event)
                (view as ImageView).setImageURI(uri)

                // Release the permission immediately afterwards because it's
                // no longer needed.
                dropPermissions.release()
                return@setOnDragListener true

            // Implement logic for other DragEvent cases here.
        }
    }
    

Java

    // Container for where the image is to be dropped in the target app.
    ImageView targetImageView = findViewById(R.id.my-image-id);

    targetImageView.setOnDragListener(
            new View.OnDragListener() {
                @Override
                public boolean onDrag(View view, DragEvent dragEvent) {
                    switch (dragEvent.getAction()) {
                        case ACTION_DROP:
                            ClipData.Item imageItem =
                                    dragEvent.getClipData().getItemAt(0);
                            Uri uri = imageItem.getUri();

                            // Request permission to access the image data being
                            // dragged into the target activity's ImageView element.

                            DragAndDropPermissions dropPermissions =
                                    requestDragAndDropPermissions(dragEvent);
                            ((ImageView)view).setImageURI(uri);

                            // Release the permission immediately afterwards because
                            // it's no longer needed.
                            dropPermissions.release();
                            return true;

                        // Implement logic for other DragEvent cases here.
                    }
                }
            });