Làm sao để kéo thả một View trong android?

Tìm hiểu về Drag/Drop Process

Drag/drop framework trong android được hỗ trợ từ android 11 trở lên, cho phép người dùng có thể kéo thả các view dễ dàng. Nó bao gồm 3 thành phần quan trọng:

  • Lớp Drag event.
  • Lớp Drag listeners.
  • Helper methods

Process của nó được chia làm 4 trạng thái khác nhau đó là:

  • Start: Nó được bắt đầu khi người dùng bắt đầu kéo và ứng dụng bắt đầu gọi tới method startDrag().
  • Continuing: Trạng thái khi người sử dụng vẫn đang kéo view.
  • Dropped: Trạng thái khi người dùng nhả item đã kéo trong box view.
  • Ended: Trạng thái kết thúc, nó là trạng thái kế tiếp của trạng thái Dropped.

DragEvent Class

Trong class này có các thành phần Constants:

  • ACTION_DRAG_STARTED: báo hiệu khởi đầu cho 1 progress drag.
  • ACTION_DRAG_ENTERED: Báo hiệu cho điểm drag đã vào bounding box của view.
  • ACTION_DRAG_LOCATION: Được báo hiệu ngay sau ACTION_DRAG_ENTERED nếu shadow của view vẫn còn ở trong bounding box. shadow ở đây chính là cái bóng của view, bạn drag tới đâu cái shadow này sẽ di chuyển tới đó.
  • ACTION_DRAG_EXITED: Báo hiệu khi người dùng di chuyển(drag) cái bóng ra khỏi view.
  • ACTION_DROP: Báo hiệu người sử dụng đã drop(thả) view trong bounding box của View.
  • ACTION_DRAG_ENDED: Báo hiệu kết thúc một progress kéo thả.

Các thành phần Methods:

  • int getAction(): Dùng để kiểm tra action value.
  • ClipData getClipData(): Trả về ClipData object gửi đến hệ thống là một phần của các cuộc gọi đến method startDrag().
  • ClipDescription getClipDescription(): Trả về ClipDescription chứa trong ClipData.
  • boolean getResult(): Trả về kết quả của kéo thả.
  • float getX(), float getY(): trả về toạ độ X, Y của điểm kéo.
  • String toString(): Trả về chuỗi String đại diện của DragEvent object.

Các Listening cho sự kiện Drag: setOnDragListener.

Ví dụ

Sau đây chúng ta cùng làm một ví dụ để thấy rõ hơn DragEvent hoạt động như thế nào: Bước đầu tiên bao giờ cũng là create project. Sau khi đã tạo mới một project chúng ta bắt đầu code cho class MainActivity.java:

package com.framgia.mrtung.changeimage;

import android.content.ClipData;
import android.content.ClipDescription;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;

public class MainActivity extends AppCompatActivity {

    private ImageView img;
    private String msg = "REPORT";
    private View.DragShadowBuilder dragShadowBuilder;
    private ClipData data;
    private android.widget.RelativeLayout.LayoutParams layoutParams;

    private int x_cord = 0;
    private int y_cord = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        img = (ImageView) findViewById(R.id.imgView);
        img.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                ClipData.Item item = new ClipData.Item((CharSequence) v.getTag());
                String[] type = {ClipDescription.MIMETYPE_TEXT_PLAIN};

                ClipData dragData = new ClipData(v.getTag().toString(), type, item);
                View.DragShadowBuilder myShadow = new View.DragShadowBuilder(img);

                v.startDrag(dragData, myShadow, null, 0);
                return true;
            }
        });

        img.setOnDragListener(new View.OnDragListener() {
            @Override
            public boolean onDrag(View v, DragEvent event) {
                switch (event.getAction()) {
                    case DragEvent.ACTION_DRAG_STARTED:
                        layoutParams = (RelativeLayout.LayoutParams) v.getLayoutParams();
                        break;
                    case DragEvent.ACTION_DRAG_ENTERED:
                        x_cord = (int) event.getX();
                        y_cord = (int) event.getY();
                        break;

                    case DragEvent.ACTION_DRAG_EXITED:
                        x_cord = (int) event.getX();
                        y_cord = (int) event.getY();
                        layoutParams.leftMargin = x_cord;
                        layoutParams.topMargin = y_cord;
                        v.setLayoutParams(layoutParams);
                        break;

                    case DragEvent.ACTION_DRAG_LOCATION:
                        x_cord = (int) event.getX();
                        y_cord = (int) event.getY();
                        break;

                    case DragEvent.ACTION_DRAG_ENDED:
                        break;

                    case DragEvent.ACTION_DROP:
                        break;
                    default:
                        break;
                }
                return true;
            }

        });

        img.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    img.startDrag(data, dragShadowBuilder, img, 0);
                    return true;
                } else {
                    return false;
                }
            }
        });

        data = ClipData.newPlainText("", "");
        dragShadowBuilder = new View.DragShadowBuilder(img);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Tiếp đó là file main_activity.xml. File layout chính của chúng ta:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imgView"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:src="@drawable/icon" />

</RelativeLayout>

Trên đây là 2 file chính mà chúng ta cần quan tâm tới, còn các file khác như hình ảnh trong drawable, value trong string.xml, tên package, tên app các bạn tự import theo ý thích của mình nhé.

Giờ bạn có thể build và chạy thử nó. Hoặc nếu muốn xem một vòng đời của event này bạn có thể đặt log hoặc debug để xem chi tiết hơn nhé.

Do thời gian nghiên cứu có hạn, nên mình cũng chỉ nghiên cứu được tới đây, rất mong được ý kiến đóng góp để mình có thể có những hiểu biết nhiều hơn nữa về nó.ơ

Link download apk demo: https://drive.google.com/file/d/0B7td9WR1ZtQ0QjM4eTM5Q0VJNWc/view?usp=sharing