2011년 5월 18일 수요일

ListView의 내용을 드래그 앤 드롭으로 순서 변경 하기

ListView의 내용을 드래그 앤 드롭으로 순서를 변경하고 싶을 때 사용한다.

다음은 안드로이드의 Music 앱의 소스를 참고하여 만든 DndListView이다.

아래의 코드를 사용하기 위해서는 안드로이드 버전 1.5 이상부터 가능하다.

  1. /*  * Copyright (C) 2008 The Android Open Source Project  *  * Licensed under the Apache License, Version 2.0 (the "License");  * you may not use this file except in compliance with the License.  * You may obtain a copy of the License at  *  *      http://www.apache.org/licenses/LICENSE-2.0  *  * Unless required by applicable law or agreed to in writing, software  * distributed under the License is distributed on an "AS IS" BASIS,  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  * See the License for the specific language governing permissions and  * limitations under the License.  */  package org.pyframe.tools.view;  import android.content.Context; import android.graphics.Bitmap; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ImageView; import android.widget.ListView;  import com.mytodo.andriod.R;  public class DndListView extends ListView {       private Context mContext;     private ImageView mDragView;     private WindowManager mWindowManager;     private WindowManager.LayoutParams mWindowParams;     private int mDragPos;      // which item is being dragged     private int mFirstDragPos; // where was the dragged item originally     private int mDragPoint;    // at what offset inside the item did the user grab it     private int mCoordOffset;  // the difference between screen coordinates and coordinates in this view     private DragListener mDragListener;     private DropListener mDropListener; //    private RemoveListener mRemoveListener;     private int mUpperBound;     private int mLowerBound;     private int mHeight;     private GestureDetector mGestureDetector; //    private static final int FLING = 0; //    private static final int SLIDE = 1; //    private int mRemoveMode = -1;     private Rect mTempRect = new Rect();     private Bitmap mDragBitmap;     private final int mTouchSlop;     private int mItemHeightNormal;     private int mItemHeightExpanded;      public DndListView(Context context, AttributeSet attrs) {         super(context, attrs); //        SharedPreferences pref = context.getSharedPreferences("Music", 3); //        mRemoveMode = pref.getInt("deletemode", -1);         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();         mContext = context; //        Resources res = getResources(); //        mItemHeightNormal = res.getDimensionPixelSize(R.dimen.normal_height); //        mItemHeightExpanded = res.getDimensionPixelSize(R.dimen.expanded_height);     }          @Override     public boolean onInterceptTouchEvent(MotionEvent ev) {         if (mDragListener != null || mDropListener != null) {             switch (ev.getAction()) {                 case MotionEvent.ACTION_DOWN:                     int x = (int) ev.getX();                     int y = (int) ev.getY();                     int itemnum = pointToPosition(x, y);                     if (itemnum == AdapterView.INVALID_POSITION) {                         break;                     }                     ViewGroup item = (ViewGroup) getChildAt(itemnum - getFirstVisiblePosition());                     mDragPoint = y - item.getTop();                     mCoordOffset = ((int)ev.getRawY()) - y;                     View dragger = item.findViewById(R.id.dragicon);                      //                    item.setBackgroundColor(Color.RED);                                          Rect r = mTempRect;                     dragger.getDrawingRect(r);                     // The dragger icon itself is quite small, so pretend the touch area is bigger                     if (x < r.right * 2) {                         item.setDrawingCacheEnabled(true);                         // Create a copy of the drawing cache so that it does not get recycled                         // by the framework when the list tries to clean up memory                         Bitmap bitmap = Bitmap.createBitmap(item.getDrawingCache());                         startDragging(bitmap, y);                         mDragPos = itemnum;                         mFirstDragPos = mDragPos;                         mHeight = getHeight();                         int touchSlop = mTouchSlop;                         mUpperBound = Math.min(y - touchSlop, mHeight / 3);                         mLowerBound = Math.max(y + touchSlop, mHeight * 2 /3);                         return false;                     }                     mDragView = null;                     break;             }         }         return super.onInterceptTouchEvent(ev);     }          /*      * pointToPosition() doesn't consider invisible views, but we      * need to, so implement a slightly different version.      */     private int myPointToPosition(int x, int y) {         Rect frame = mTempRect;         final int count = getChildCount();         for (int i = count - 1; i >= 0; i--) {             final View child = getChildAt(i);             child.getHitRect(frame);             if (frame.contains(x, y)) {                 return getFirstVisiblePosition() + i;             }         }         return INVALID_POSITION;     }          private int getItemForPosition(int y) {         int adjustedy = y - mDragPoint - 32;         int pos = myPointToPosition(0, adjustedy);         if (pos >= 0) {             if (pos <= mFirstDragPos) {                 pos += 1;             }         } else if (adjustedy < 0) {             pos = 0;         }         return pos;     }          private void adjustScrollBounds(int y) {         if (y >= mHeight / 3) {             mUpperBound = mHeight / 3;         }         if (y <= mHeight * 2 / 3) {             mLowerBound = mHeight * 2 / 3;         }     }      /*      * Restore size and visibility for all listitems      */     private void unExpandViews(boolean deletion) {         for (int i = 0;; i++) {             View v = getChildAt(i);             if (v == null) {                 if (deletion) {                     // HACK force update of mItemCount                     int position = getFirstVisiblePosition();                     int y = getChildAt(0).getTop();                     setAdapter(getAdapter());                     setSelectionFromTop(position, y);                     // end hack                 }                 layoutChildren(); // force children to be recreated where needed                 v = getChildAt(i);                 if (v == null) {                     break;                 }             }             ViewGroup.LayoutParams params = v.getLayoutParams();             params.height = mItemHeightNormal;             v.setLayoutParams(params);             v.setVisibility(View.VISIBLE);         }     }          /* Adjust visibility and size to make it appear as though      * an item is being dragged around and other items are making      * room for it:      * If dropping the item would result in it still being in the      * same place, then make the dragged listitem's size normal,      * but make the item invisible.      * Otherwise, if the dragged listitem is still on screen, make      * it as small as possible and expand the item below the insert      * point.      * If the dragged item is not on screen, only expand the item      * below the current insertpoint.      */     private void doExpansion() {         int childnum = mDragPos - getFirstVisiblePosition();         if (mDragPos > mFirstDragPos) {             childnum++;         }          View first = getChildAt(mFirstDragPos - getFirstVisiblePosition());          for (int i = 0;; i++) {             View vv = getChildAt(i);             if (vv == null) {                 break;             }             int height = mItemHeightNormal;             int visibility = View.VISIBLE;             if (vv.equals(first)) {                 // processing the item that is being dragged                 if (mDragPos == mFirstDragPos) {                     // hovering over the original location                     visibility = View.INVISIBLE;                 } else {                     // not hovering over it                     height = 1;                 }             } else if (i == childnum) {                 if (mDragPos < getCount() - 1) {                     height = mItemHeightExpanded;                 }             }             ViewGroup.LayoutParams params = vv.getLayoutParams();             params.height = height;             vv.setLayoutParams(params);             vv.setVisibility(visibility);         }     }          @Override     public boolean onTouchEvent(MotionEvent ev) {         if (mGestureDetector != null) {             mGestureDetector.onTouchEvent(ev);         }         if ((mDragListener != null || mDropListener != null) && mDragView != null) {             int action = ev.getAction();              switch (action) {                 case MotionEvent.ACTION_UP:                 case MotionEvent.ACTION_CANCEL:                     Rect r = mTempRect;                     mDragView.getDrawingRect(r);                     stopDragging(); //                    if (mRemoveMode == SLIDE && ev.getX() > r.right * 3 / 4) { //                        if (mRemoveListener != null) { //                            mRemoveListener.remove(mFirstDragPos); //                        } //                        unExpandViews(true); //                    } else {                     if (mDropListener != null && mDragPos >= 0 && mDragPos < getCount()) {                         mDropListener.drop(mFirstDragPos, mDragPos);                     }                     unExpandViews(false); //                    }                     break;                                      case MotionEvent.ACTION_DOWN:                 case MotionEvent.ACTION_MOVE:                     int x = (int) ev.getX();                     int y = (int) ev.getY();                     dragView(x, y);                     int itemnum = getItemForPosition(y);                     if (itemnum >= 0) {                         if (action == MotionEvent.ACTION_DOWN || itemnum != mDragPos) {                             if (mDragListener != null) {                                 mDragListener.drag(mDragPos, itemnum);                             }                             mDragPos = itemnum;                             doExpansion();                         }                         int speed = 0;                         adjustScrollBounds(y);                         if (y > mLowerBound) {                             // scroll the list up a bit                             speed = y > (mHeight + mLowerBound) / 2 ? 16 : 4;                         } else if (y < mUpperBound) {                             // scroll the list down a bit                             speed = y < mUpperBound / 2 ? -16 : -4;                         }                         if (speed != 0) {                             int ref = pointToPosition(0, mHeight / 2);                             if (ref == AdapterView.INVALID_POSITION) {                                 //we hit a divider or an invisible view, check somewhere else                                 ref = pointToPosition(0, mHeight / 2 + getDividerHeight() + 64);                             }                             View v = getChildAt(ref - getFirstVisiblePosition());                             if (v!= null) {                                 int pos = v.getTop();                                 setSelectionFromTop(ref, pos - speed);                             }                         }                     }                     break;             }             return true;         }         return super.onTouchEvent(ev);     }          private void startDragging(Bitmap bm, int y) {         stopDragging();          mWindowParams = new WindowManager.LayoutParams();         mWindowParams.gravity = Gravity.TOP;         mWindowParams.x = 0;         mWindowParams.y = y - mDragPoint + mCoordOffset; //         mWindowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;         mWindowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;         mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE                 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;         mWindowParams.format = PixelFormat.TRANSLUCENT;         mWindowParams.windowAnimations = 0;                  ImageView v = new ImageView(mContext);         int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background); //        int backGroundColor = Color.parseColor("#e0103010");         v.setBackgroundColor(backGroundColor);                  v.setImageBitmap(bm);         mDragBitmap = bm;          mWindowManager = (WindowManager)mContext.getSystemService("window");         mWindowManager.addView(v, mWindowParams);         mDragView = v;     }          private void dragView(int x, int y) { //        if (mRemoveMode == SLIDE) { //            float alpha = 1.0f; //            int width = mDragView.getWidth(); //            if (x > width / 2) { //                alpha = ((float)(width - x)) / (width / 2); //            } //            mWindowParams.alpha = alpha; //        }         mWindowParams.y = y - mDragPoint + mCoordOffset;         mWindowManager.updateViewLayout(mDragView, mWindowParams);     }          private void stopDragging() {         if (mDragView != null) {             WindowManager wm = (WindowManager)mContext.getSystemService("window");             wm.removeView(mDragView);             mDragView.setImageDrawable(null);             mDragView = null;         }         if (mDragBitmap != null) {             mDragBitmap.recycle();             mDragBitmap = null;         }     }          public void setDragListener(DragListener l) {         mDragListener = l;     }          public void setDropListener(DropListener l) {         mDropListener = l;     }      //    public void setRemoveListener(RemoveListener l) { //        mRemoveListener = l; //    }      public interface DragListener {         void drag(int from, int to);     }     public interface DropListener {         void drop(int from, int to);     }     public interface RemoveListener {         void remove(int which);     } }

위 코드중 정상적인 컴파일을 위해서 살펴보아야 할 부분이 두군데 있다.

1. 위 코드에 다음과 같은 부분이 있다.

  1. View dragger = item.findViewById(R.id.dragicon);

R.id.dragicon 이 바로 드래그를 할 대상이 되는 View가 된다.

2. 또 다음과 같은 코드가 있다.

  1. int backGroundColor = mContext.getResources().getColor(R.color.dragndrop_background);

이 부분은 드래그 앤 드롭시 백그라운드 색상을 지정해 주는 부분이다.

strings.xml파일에 다음과 같은 항목을 추가해 주어야 한다.

  1. <color name="dragndrop_background">#e0103010</color>

이제 드래그 앤 드롭을 구현하기 위해서 ListActivity는 어떻게 구현해야 하는지 알아보자.

아래와 같이 DragListener와 DropListener를 구현하도록 ListActivity를 만든다.

  1. public class MainActivity extends ListActivity implements DragListener, DropListener {

onCreate메써드에서 드래그 앤 드롭을 사용한다는 정보를 입력한다.

  1. listView = (DndListView) findViewById(android.R.id.list);
  2. listView.setDragListener(this);
    listView.setDropListener(this);

그리고 다음과 같은 메써드를 구현한다.

  1. public void drag(int from, int to) {
  2. // 드래그 이벤트가 발생시 구현해야 할 것들을 기술한다.
    }
  3. public void drop(int fr, int to) {
  4. // 드롭 이벤트 발생시 구현해야 할 것들을 기술한다.
  5. }

Activity의 레이아웃 파일은 다음과 같이 작성해야 한다.

  1. <org.pyframe.tools.view.DndListView
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:drawSelectorOnTop="false"
    android:fastScrollEnabled="true"
    android:cacheColorHint="#00000000"
    android:layout_weight="1.0"
    />

cacheColorHint값을 주어야 드래그 앤 드롭시 선택된 아이템의 백그라운드 색상이 표시된다.

댓글 없음: