프로그래밍/안드로이드

안드로이드 - startDrag메소드를 이용한 드래그 & 드롭 구현하기

가카리 2015. 12. 3. 22:59
반응형

 

드래그 & 드롭은 모바일에서 환경에서 구현하기 위해

 

롱클릭이나 터치 다운 등의 이벤트에서 다음 메소드를 호출한다.

 

    boolean startDrag(ClipData data, View.DragShadowBuilder shadowBuilder, Object myLocalState, int flags)

 

data는 드래그할 데이터이며 클립 데이터 객체를 사용한다. data 안에 교환 대상 데이터는 물론이고 데이터의 특성을 설명하는 메타 데이터도 포함되어 있다.

 

클립보드로 교환할 수 있는 데이터는 드래그 앤 드롭으로도 교환할 수 있다.

 

shadowBuilder는 드래그 중에 보여줄 이미지인 섀도우를 작성하며 시스템은 이 객체의 콜백 메소드를 호출하여 섀도우 이미지를 얻는다.

 

빌더의 생성자로 뷰객체를 전단하면 뷰와 같은 모양의 섀도우를 생성하며 파생한 후 메소드를 재정의하면 임의의 모양으로 그릴 수도 있다.

 

myLocalState는 드래그 앤 드롭 관련 데이터를 가지는 객체이며 드래그 뷰와 드롭 뷰간의 정보 교환에 사용된다.

 

임의의 정보를 전달할 수 있으며 딱히 전달할 대상이 없으면 null로 지정한다. flags는 옵션값이되 현재는 기능이 없으므로 0으로 고정되어 있다.

 

startDrag는 드래그가 정상적으로 시작되었으면 true를 리턴하며 에러로 인해 시작하지 못했으면 false를 리턴한다.

 

startDrag메소드를 호출하여 드래그를 시작하면 시스템은 드래그 중이라는 의미로 섀도우 이미지를 표시하며 손가락이 이동하면 섀도우도 같이 이동한다.

 

드래그 동작 중에 시스템은 관심을 보이는 모든 뷰에게 드래그 이벤트를 보낸다. 드래그 이벤트를 받고 싶은 뷰는 다음 메소드를 호출하여 드래그 리스너를 등록한다.

 

리스너를 등록한 모든 뷰에 대해 onDrag 콜백 메소드가 호출된다.

 

    void View.setOnDragListener(OnDragListener l)

    boolean onDrag(View v, DragEvent event)

 

또는 OnDragEvent 콜백을 재정의할 수도 있는데 콜백을 재정의하려면 뷰를 상속받아야되서 그냥 리스너를 구현하는게 편하다.

 

콜백과 리스너가 둘 다 있으면 리스너가 우선 호출되며 리스너가 false를 리턴해야 다음 순위인 콜백이 호출된다.

 

드래그 이벤트에 관련된 정보는 DragEvent 객체로 전달되며 액션 타입과 데이터가 들어 있다. 액션은 드래그 이벤트의 종류를 지정하며 getAction 메소드로 구한다.

 

드래그를 시작하면 리스너를 등록한 모든 뷰에게 ACTION_DRAG_STARTED 이벤트가 전달된다.

 

계속 이벤트를 받으려면 이 이벤트에 대해 true를 리턴하고 더 이상 드래그에 관심이 없으면 false를 리턴한다. 각 뷰는 DragEvent객체의 메타 데이터를 분석하여 드롭을

 

받을 것인지 결정한다.

 

드래그를 시작한 상태에서 손가락을 계속 움직이면 다음 3개의 이벤트가 손가락 아래의 뷰에게 전달된다.

 

이벤트

설명

ACTION_DRAG_ENTERED 

뷰의 경계 안으로 들어왔다. 이벤트를 계속 받으려면 true를 리턴해야 한다.

ACTION_DRAG_LOCATION 

경계 안에서 이동 중이다.

ACTION_DRAG_EXITED 

경계를 벗어났다.

 

뷰는 이 이벤트를 받았을 때 드롭 가능하다는 것을 표시하기 위해 뷰의 모양을 변경한다 뷰 영역 안에서 손가락을 놓으면 섀도우 이미지는 제거되며 드래그 동작이

 

종료된다. 이때 손가락 아래의 뷰에게 ACTION_DROP 이벤트가 전달되며 이때 다음 메소드로 드래그 대상 데이터와 뷰 내에서 드롭된 좌표를 구한다.

 

    ClipData getClipData()

    ClipDescription getClipDescription()

    float getX()

    float getY()

 

드롭을 받은 뷰는 이 메소드로 드래그된 데이터를 구해 사용한다. 클립 데이터에서 실제 데이터를 꺼내는 방법은 클립보드의 경우와 같다. 설명의 마임 타입을 점검해 보고

 

원하는 타입에 대해서만 드롭을 받아야한다. 드롭을 받아 정상적으로 처리했으면 true를 리턴하고 그렇지 않으면 false를 리턴한다.

 

드롭 처리까지 끝나고 드래그가 완전히 종료되면 모든 뷰에게 ACTION_DRAG_ENDED 이벤트가 전달된다. ACTION_DRAG_STARTED 이벤트에 대해 false를 리턴한 뷰와 현재 숨겨진

 

뷰에게까지 종료 이벤트는 전달된다. 종료 이벤트에서 getResult 메소드는 드롭 동작이 제대로 수정되었는지 조사한다.

 

드래그 중에 모양을 변경한 뷰는 종료 이벤트를 받았을 때 원래 모습으로 복귀한다.

 

다음은 드래그 앤 드롭 예제이다.

 

 

위와 같이 2개의 파일을 수정해야 한다.

 

dragbutton.xml

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="match_parent"

    android:layout_height="match_parent" >

<Button

    android:id="@+id/source"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Source" />

<Button

    android:id="@+id/source2"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Intent" />

<Button

    android:id="@+id/target"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="Target" />

<Button

    android:id="@+id/nodrop"

    android:layout_width="100dp"

    android:layout_height="wrap_content"

    android:text="No Drop" />

</LinearLayout>

 

DragButton.java

 

package com.example.ch27_dragbutton;

 

import android.app.Activity;

import android.content.ClipData;

import android.content.ClipDescription;

import android.net.Uri;

import android.os.Bundle;

import android.view.DragEvent;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

 

public class DragButton extends Activity {

    Button btnSource;

    Button btnTarget;

      

    

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.dragbutton);

      

        btnSource = (Button)findViewById(R.id.source);

        //버턴의 롱클릭 리스너 등록

        btnSource.setOnLongClickListener(new View.OnLongClickListener() {

            

            @Override

            public boolean onLongClick(View v) {

                // TODO Auto-generated method stub

                //롱클릭시 클립데이터를 만듬

                ClipData clip = ClipData.newPlainText("dragtext", "dragtext");

                //드래그할 데이터, 섀도우 지정, 드래그 드롭 관련 데이터를 가지는 객체 지정, 0

                v.startDrag(clip, new View.DragShadowBuilder(v), null, 0);

                return false;

            }

        });

        

        Button btnSource2 = (Button)findViewById(R.id.source2);

        btnSource2.setOnLongClickListener(new View.OnLongClickListener() {

            

            @Override

            public boolean onLongClick(View v) {

                // TODO Auto-generated method stub

                ClipData clip = ClipData.newRawUri("uri",

                        Uri.parse("content://com.example.ch20_contentprovider/word/boy"));

                v.startDrag(clip, new View.DragShadowBuilder(v), null, 0);

                return false;

            }

        });

        

        btnTarget = (Button)findViewById(R.id.target);

        btnTarget.setOnDragListener(mDragListener);

        

    }

    

    View.OnDragListener mDragListener = new View.OnDragListener() {

        

        @Override

        public boolean onDrag(View v, DragEvent event) {

            // TODO Auto-generated method stub

            Button btn;

            //드래그 객체가 버튼인지 확인

            if(v instanceof Button){

                btn = (Button)v;

            }else{

                return false;

            }

            

            

            //이벤트를 받음

            switch(event.getAction()){

            //드래그가 시작되면

            case DragEvent.ACTION_DRAG_STARTED:

                //클립 설명이 텍스트면

                if(event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){

                    btn.setText("Drop OK");//버튼의 글자를 바꿈

                    return true;

                }else{//인텐트의 경우 이쪽으로 와서 드래그를 받을 수가 없다.

                    return false;

                }

            //드래그가 뷰의 경계안으로 들어오면

            case DragEvent.ACTION_DRAG_ENTERED:

                btn.setText("Enter");//버튼 글자 바꿈

                return true;

            //드래그가 뷰의 경계밖을 나가면

            case DragEvent.ACTION_DRAG_EXITED:

                btn.setText("Exit");//버튼 글자 바꿈

                return true;

            //드래그가 드롭되면

            case DragEvent.ACTION_DROP:

                //클립데이터의 값을 가져옴

                String text = event.getClipData().getItemAt(0).getText().toString();

                btn.setText(text);

                return true;

            //드래그 성공 취소 여부에 상관없이 모든뷰에게

            case DragEvent.ACTION_DRAG_ENDED:

                if(event.getResult()){//드래그 성공시

                    Toast.makeText(DragButton.this, "Drag & Drop 완료", 0).show();

                }else{//드래그 실패시

                    btn.setText("Target");

                }

                return true;

            }

            return true;

        }

    };

 

    

}

 

 

실행 화면

  실행하면 다음과 같이 버튼 4개가 보입니다.


Source 버튼을 끌면 Target 이 Drop OK로 바뀐다.



끌어다 놓으면 dragtext로 바뀐다.



Intent버튼을 끌어다 놓으면 변화가 없다.