ListView 拖拽Item交换位置

转载请注明出处:http://blog.csdn.net/ganklun/article/details/43448729

最近一个项目中要求用到交换任意两个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、下载地址

https://github.com/GankLun/DragListView

5、总结

总的来说,要实现ListView交换Item项位置,只需三步,第一,XML布局文件里引入我们自定义的DragListView,第二,为dragListView设置拖拽图片资源和事件触发监听,第三,实现onDragItemChange交换位置接口,进行位置交换并且刷新UI界面。很简单,有木有?大家都来试一下吧。

 
 


  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值