воскресенье, 15 июля 2012 г.

Реализация Drag and Drop в Android

Touch-интерфейсы дают нам удивительную возможность "прикоснуться к приложению", манипулировать с элементами на экране самым естественным для человека способом. И нам, разработчикам, грех не использовать такую возможность. Давайте разберёмся, как максимально просто реализовать Drag&Drop в нашем приложении.
В двух словах уточним, какое поведение элемента интерфейса мы хотим получить. Есть пара белых ImageView и один синий прямоугольник. Та из белых картинок, которой мы коснулись будет следовать за пальцем, пока мы её не отпустим. Если мы отпустим её над синим прямоугольником, прямоугольник станет красным, и картинка останется на месте. Если за пределами - картинка вернётся на исходную позицию.
Основная наша цель - сделать всё максимально просто. Кроме Layout-a, описывающего начальное состояние интерфейса, мы напишем всего один класс в сотню строк кода. Итак приступим.

Становимся в исходное положение

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:orientation="vertical">
    <ImageView
            android:id="@+id/ImgDrop"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentTop="true"
            android:layout_centerInParent="true"
            android:layout_marginTop="50dp"
            android:background="#0000ff">
    </ImageView>
    <ImageView
            android:id="@+id/img"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentBottom="true"
            android:src="@android:drawable/alert_light_frame">
    </ImageView>
    <ImageView
            android:id="@+id/img2"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_alignParentBottom="true"
            android:layout_alignParentRight="true"
            android:src="@android:drawable/alert_light_frame">
    </ImageView>
</RelativeLayout>

Как я и описывал в условии задачи: тут синий прямоугольник и две картинки в RelativeLayout-е. Ничего особенного.  

Двигаемся

Вот и наши 100 строк кода:

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
 
public class MyActivity extends Activity implements OnTouchListener {
 
    private View selected_item = null;
    private int offset_x = 0;
    private int offset_y = 0;
    Boolean touchFlag = false;
    boolean dropFlag = false;
    LayoutParams imageParams;
    ImageView imageDrop, image1, image2;
    int eX, eY;
    int topY, leftX, rightX, bottomY;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setContentView(R.layout.main);
        View root = findViewById(android.R.id.content).getRootView();
        imageDrop = (ImageView) findViewById(R.id.ImgDrop);
        image1 = (ImageView) findViewById(R.id.img);
        image2 = (ImageView) findViewById(R.id.img2);
        image1.setOnTouchListener(this);
        image2.setOnTouchListener(this);
        root.setOnTouchListener(new View.OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                if (touchFlag) {
                    System.err.println("Display If  Part ::->" + touchFlag);
                    switch (event.getActionMasked()) {
                        case MotionEvent.ACTION_DOWN:
                            topY = imageDrop.getTop();
                            leftX = imageDrop.getLeft();
                            rightX = imageDrop.getRight();
                            bottomY = imageDrop.getBottom();
                            break;
                        case MotionEvent.ACTION_MOVE:
                            eX = (int) event.getX();
                            eY = (int) event.getY();
                            int x = (int) event.getX() - offset_x;
                            int y = (int) event.getY() - offset_y;
                            int w = getWindowManager().getDefaultDisplay().getWidth() - 50;
                            int h = getWindowManager().getDefaultDisplay().getHeight() - 10;
                            if (x > w) x = w;
                            if (y > h) y = h;
                            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(new ViewGroup.MarginLayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
                            lp.setMargins(x, y, 0, 0);
 
                            if (eX > leftX && eX < rightX && eY > topY && eY < bottomY) {
                                imageDrop.setBackgroundColor(Color.RED);
                                selected_item.bringToFront();
                                dropFlag = true;
                            } else {
                                imageDrop.setBackgroundColor(Color.BLUE);
                            }
                            selected_item.setLayoutParams(lp);
                            break;
                        case MotionEvent.ACTION_UP:
                            touchFlag = false;
                            if (dropFlag) {
                                dropFlag = false;
                            } else {
                                selected_item.setLayoutParams(imageParams);
                            }
                            break;
                        default:
                            break;
                    }
                }
                return true;
            }
        });
    }
 
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                touchFlag = true;
                offset_x = (int) event.getX();
                offset_y = (int) event.getY();
                selected_item = v;
                imageParams = v.getLayoutParams();
                break;
            case MotionEvent.ACTION_UP:
                selected_item = null;
                touchFlag = false;
                break;
            default:
                break;
        }
        return false;
    }
}

Посмотрим на наш код внимательнее. Вся логика реализована тут с помощью двух listener-ов. Первый OnTouchListener, метод onTouch которого реализует непосредственно наше Activity мы навешиваем на "подвижные" картинки. Задача, которую мы тут решаем - определить какая из картинок под пальцем, сохранить её начальные параметры расположения на случай, если нужно будет вернуться и установить флаг начала перемещения.
Второй OnTouchListener вешаем на самый верхний элемент иерархии View нашего Activity. Он определяет координаты "цели", перемещает картинку и проверяет, не попали ли мы уже в цель.  

4 комментария:

  1. Большущее спасибище!!! Как раз искал подобный пример.

    Теперь осталось разобраться. Вы не пишите программ на заказ? Мне нужно типа мозаики. Так как у вас, только передвигать 3-5 кусочков и после того, как они попали на место, то делать запрет на Drag and Drop.

    Если фрилансите, то пож, напишите мне serge_mikhailov##mail.ru

    ОтветитьУдалить
  2. @android:drawable/alert_light_frame что особенного в этой картинке. По какой причине любые другие при перетаскивании изчезают?

    ОтветитьУдалить
  3. Спасибо за пример. У меня такое реализовано, только на JavaScript. Идём дальше пытаемся такое создать дя Android: http://zxworld.h19.ru/Ninephp.php

    ОтветитьУдалить
  4. Спасибо Вам большое, Вы так облегчили мне сейчас работу над магистерской!

    ОтветитьУдалить