1. 程式人生 > >ListView 拖拽Item交換位置

ListView 拖拽Item交換位置

最近一個專案中要求用到交換任意兩個ListView裡面Item位置,需要實現這個功能。因此,本屌也是通過學習相關例子,重繪封裝了一番,並且實現了這個功能。下面,就一起來看看是怎麼樣實現的吧。

1、XML佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    android:orientation="vertical" >

    <com.czl.struct.widget.DragListView
        android:id="@+id/dragLvi"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="#FFFFFF"
        android:cacheColorHint="@android:color/transparent"
        android:divider="#DCD7CF"
        android:dividerHeight="1dp"
        android:fadingEdgeLength="0dip"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:listSelector="@android:color/transparent"
        android:scrollbars="vertical"
        android:typeface="sans" >
    </com.czl.struct.widget.DragListView>

</LinearLayout>

是不是和平時引入ListView沒有任何區別呢,只不過把LIstView換成了我們自定義的DragListView。

2、自定義DragListView

現在我們需要實現我們自定義的DragListView,並且實現相關交換位置的介面即可。先看一下需要實現的介面,程式碼如下所示:

public interface DragItemChangeListener {
    public void onDragItemChange(int dragSrcPosition,int dragPosition);
}

是不是很簡單啊,我們只需要為DragListView設定這個拖拽事件監聽即可。該事件回撥方法看名字也很容易理解,就是當位置被交換時,做相應回撥處理即可,其中第一個引數dragSrcPosition是交換前的位置,dragPosition是交換後的位置。接下來貼出DragListView的程式碼:

public class DragListView extends ListView {

    private WindowManager              windowManager;         // windows視窗控制類   

    private WindowManager.LayoutParams windowParams;          // 用於控制拖拽項的顯示的引數     

    private ImageView                  dragImageView;         // 被拖拽的項(item),其實就是一個ImageView   

    private int                        dragSrcPosition;       // 手指拖動項原始在列表中的位置   

    private int                        dragPosition;          // 手指點選準備拖動的時候,當前拖動項在列表中的位置.   

    private int                        dragPoint;             // 在當前資料項中的位置   

    private int                        dragOffset;            // 當前檢視和螢幕的距離(這裡只使用了y方向上)   

    private int                        upScrollBounce;        // 拖動的時候,開始向上滾動的邊界   

    private int                        downScrollBounce;      // 拖動的時候,開始向下滾動的邊界   

    private final static int           step = 1;              // ListView 滑動步伐.   

    private int                        current_Step;          // 當前步伐.   

    private int                        dragImageSourceId;

    private DragItemChangeListener     dragItemChangeListener;

    /*** 
     * 構造方法 
     *  
     * @param context 
     * @param attrs 
     */
    public DragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setDragImageSourceId(int dragImageSourceId) {
        this.dragImageSourceId = dragImageSourceId;
    }

    public void setDragItemChangeListener(DragItemChangeListener dragItemChangeListener) {
        this.dragItemChangeListener = dragItemChangeListener;
    }

    /*** 
     * touch事件攔截 
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        // 按下   
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            int x = (int) ev.getX();// 獲取相對與ListView的x座標   
            int y = (int) ev.getY();// 獲取相應與ListView的y座標   
            dragSrcPosition = dragPosition = pointToPosition(x, y);
            // 無效不進行處理   
            if (dragPosition == AdapterView.INVALID_POSITION) {
                return super.onInterceptTouchEvent(ev);
            }

            // 獲取當前位置的檢視(可見狀態)   
            ViewGroup itemView = (ViewGroup) getChildAt(dragPosition - getFirstVisiblePosition());

            // 獲取到的dragPoint其實就是在你點選指定item項中的高度.   
            dragPoint = y - itemView.getTop();
            // 這個值是固定的:其實就是ListView這個控制元件與螢幕最頂部的距離(一般為標題欄+狀態列).   
            dragOffset = (int) (ev.getRawY() - y);

            // 獲取可拖拽的圖示   
            View dragger = itemView.findViewById(dragImageSourceId);

            // x > dragger.getLeft() - 20這句話為了更好的觸控(-20可以省略)   
            if (dragger != null && x > dragger.getLeft() - 20) {

                upScrollBounce = getHeight() / 3;// 取得向上滾動的邊際,大概為該控制元件的1/3   
                downScrollBounce = getHeight() * 2 / 3;// 取得向下滾動的邊際,大概為該控制元件的2/3   

                itemView.setDrawingCacheEnabled(true);// 開啟cache.   
                Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根據cache建立一個新的bitmap物件.   
                startDrag(bm, y);// 初始化影像   
            }
        }

        return super.onInterceptTouchEvent(ev);
    }

    /** 
     * 觸控事件處理 
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // item的view不為空,且獲取的dragPosition有效   
        if (dragImageView != null && dragPosition != INVALID_POSITION) {
            int action = ev.getAction();
            switch (action) {
            case MotionEvent.ACTION_UP:
                int upY = (int) ev.getY();
                stopDrag();
                onDrop(upY);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveY = (int) ev.getY();
                onDrag(moveY);
                break;
            case MotionEvent.ACTION_DOWN:
                break;
            default:
                break;
            }
            return true;// 取消ListView滑動.   
        }

        return super.onTouchEvent(ev);
    }

    /** 
     * 準備拖動,初始化拖動項的影象 
     *  
     * @param bm 
     * @param y 
     */
    private void startDrag(Bitmap bm, int y) {
        // stopDrag();   
        /*** 
         * 初始化window. 
         */
        windowParams = new WindowManager.LayoutParams();
        windowParams.gravity = Gravity.TOP;
        windowParams.x = 0;
        windowParams.y = y - dragPoint + dragOffset;
        windowParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
        windowParams.height = WindowManager.LayoutParams.WRAP_CONTENT;

        windowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE// 不需獲取焦點   
                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE// 不需接受觸控事件   
                | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON// 保持裝置常開,並保持亮度不變。   
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;// 窗口占滿整個螢幕,忽略周圍的裝飾邊框(例如狀態列)。此視窗需考慮到裝飾邊框的內容。   

        windowParams.format = PixelFormat.TRANSLUCENT;// 預設為不透明,這裡設成透明效果.   
        windowParams.windowAnimations = 0;// 視窗所使用的動畫設定   

        ImageView imageView = new ImageView(getContext());
        imageView.setImageBitmap(bm);
        windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        windowManager.addView(imageView, windowParams);
        dragImageView = imageView;

    }

    /** 
     * 拖動執行,在Move方法中執行 
     *  
     * @param y 
     */
    public void onDrag(int y) {
        int drag_top = y - dragPoint;// 拖拽view的top值不能<0,否則則出界.   
        if (dragImageView != null && drag_top >= 0) {
            windowParams.alpha = 0.5f;// 透明度   
            windowParams.y = y - dragPoint + dragOffset;// 移動y值.//記得要加上dragOffset,windowManager計算的是整個螢幕.(標題欄和狀態列都要算上)   
            windowManager.updateViewLayout(dragImageView, windowParams);// 時時移動.   
        }
        // 為了避免滑動到分割線的時候,返回-1的問題   
        int tempPosition = pointToPosition(0, y);
        if (tempPosition != INVALID_POSITION) {
            dragPosition = tempPosition;

        }
        doScroller(y);
    }

    /*** 
     * ListView的移動. 
     * 要明白移動原理:當映像移動到下端的時候,ListView向上滑動,當映像移動到上端的時候,ListView要向下滑動。正好和實際的相反. 
     *  
     */

    public void doScroller(int y) {
        // ListView需要下滑   
        if (y < upScrollBounce) {
            current_Step = step + (upScrollBounce - y) / 10;// 時時步伐   
        }// ListView需要上滑   
        else if (y > downScrollBounce) {
            current_Step = -(step + (y - downScrollBounce)) / 10;// 時時步伐   
        } else {
            current_Step = 0;
        }

        // 獲取你拖拽滑動到位置及顯示item相應的view上(注:可顯示部分)(position)   
        View view = getChildAt(dragPosition - getFirstVisiblePosition());
        // 真正滾動的方法setSelectionFromTop()   
        setSelectionFromTop(dragPosition, view.getTop() + current_Step);

    }

    /** 
     * 停止拖動,刪除影像 
     */
    public void stopDrag() {
        if (dragImageView != null) {
            windowManager.removeView(dragImageView);
            dragImageView = null;
        }
    }

    /** 
     * 拖動放下的時候 
     *  
     * @param y 
     */
    public void onDrop(int y) {

        // 為了避免滑動到分割線的時候,返回-1的問題   
        int tempPosition = pointToPosition(0, y);
        if (tempPosition != INVALID_POSITION) {
            dragPosition = tempPosition;
        }

        // 超出邊界處理(如果向上超過第二項Top的話,那麼就放置在第一個位置)   
        if (y < getChildAt(0).getTop()) {
            // 超出上邊界   
            dragPosition = 0;
            // 如果拖動超過最後一項的最下邊那麼就防止在最下邊   
        } else if (y > getChildAt(getChildCount() - 1).getBottom()) {
            // 超出下邊界   
            dragPosition = getAdapter().getCount() - 1;
        }

        // 資料交換   
        if (dragPosition < getAdapter().getCount()) {

            dragItemChangeListener.onDragItemChange(dragSrcPosition, dragPosition);
        }

    }

}

 其中setDragItemChangeListener(DragItemChangeListener dragItemChangeListener) 就是我們要注入介面實現的地方。setDragImageSourceId(int dragImageSourceId) 方法可以設定您想拖拽的圖示資源。接下來萬事俱備,只欠東風了。

3、引入DragListView

<pre name="code" class="java">廢話不說,直接上程式碼:

<pre name="code" class="java">public class DragListActivity extends BaseActivity implements DragItemChangeListener {
    private DragListView                       dragListView;

    private ArrayList<HashMap<String, Object>> list;

    private DemoAdapter                        demoAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_drag_list);
        init();
        findViewById();
    }

    @Override
    public void init() {
        list = new ArrayList<HashMap<String, Object>>();
        for (int i = 0; i < 20; i++) {
            HashMap<String, Object> map = new HashMap<String, Object>();
            map.put("numbTV", "D138026594664912200" + i);
            map.put("adrTv", "浙江省杭州市西湖區小和山");
            map.put("timeTv", "2013-09-27 15:23");
            map.put("callTv", "18778900578");
            list.add(map);
        }
    }

    @Override
    public void findViewById() {
        dragListView = (DragListView) findViewById(R.id.dragLvi);
        demoAdapter = new DemoAdapter(DragListActivity.this, list);
        dragListView.setAdapter(demoAdapter);
        dragListView.setDragImageSourceId(R.id.imageView1);
        dragListView.setDragItemChangeListener(this);

    }

    @Override
    public void Message(android.os.Message msg) {

    }

    @Override
    public void onDragItemChange(int dragSrcPosition, int dragPosition) {
        HashMap<String, Object> map = list.get(dragSrcPosition);
        list.remove(dragSrcPosition);
        list.add(dragPosition, map);
        demoAdapter.notifyDataSetChanged();
    }

}

介面卡就和平時使用的BaseAdapter沒有任何區別,這裡就不再贅述了,首先我們初始化了20條資料,然後我們重點關注setDragImageSourceId()和setItemChangeListener()這兩行程式碼,聰明的你肯定已經完全明白了吧,這裡就是我們設定自定義拖拽圖示以及新增交換位置監聽的實現。onDragItemChange()就是我們回撥後具體交換位置重新整理UI介面的程式碼啦。

4、下載地址

5、總結

總的來說,要實現ListView交換Item項位置,只需三步,第一,XML佈局檔案裡引入我們自定義的DragListView,第二,為dragListView設定拖拽圖片資源和事件觸發監聽,第三,實現onDragItemChange交換位置介面,進行位置交換並且重新整理UI介面。很簡單,有木有?大家都來試一下吧。