拖拽(1) listview拖拽

转载 2016年06月02日 11:23:22

关于ListView拖拽移动位置,想必大家并不陌生,比较不错的软件都用到如此功能了.如:搜狐,网易,百度等,但是相比来说还是百度的用户体验较好,不偏心了,下面看几个示例:

    

               

首先说一下:拖拽ListView的item就不应该可以任意移动,只应该在ListView所在的范围内,而网易的你看看我都可以移动到状态栏了,虽然你做了处理,但是用户体验我个人感觉不好,在看看百度的,不仅控制了移动范围,更不错的百度的移动起来会时时的换位,看起来相当的形象,所以我认为这样相当的棒.

说明一点,我没有那么有才,我也是看别人代码,然后自己整理下.在这里就简单记载一下.

首先对touch事件的处理,从应用中,我们可以得出,在我们点击后面拖拉图标后,就会创建一个item的影像视图.并且可以移动该影像,而此时的ListView不应该有touch事件.

?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<font color="#000"><font face="Arial">/***
     * touch事件拦截
     */
    @Override
    publicbooleanonInterceptTouchEvent(MotionEvent ev) {
        // 按下
        if(ev.getAction() == MotionEvent.ACTION_DOWN) {
            intx = (int) ev.getX();// 获取相对与ListView的x坐标
            inty = (int) ev.getY();// 获取相应与ListView的y坐标
            dragSrcPosition = dragPosition = pointToPosition(x, y);
            // 无效不进行处理
            if(dragPosition == AdapterView.INVALID_POSITION) {
                returnsuper.onInterceptTouchEvent(ev);
            }
 
            // 获取当前位置的视图(可见状态)
            ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
                    - getFirstVisiblePosition());
 
            // 获取到的dragPoint其实就是在你点击指定item项中的高度.
            dragPoint = y - itemView.getTop();
            // 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
            dragOffset = (int) (ev.getRawY() - y);
 
            // 获取可拖拽的图标
            View dragger = itemView.findViewById(R.id.iv_drag_list_item_2);
 
            // 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 false;
        }
 
        returnsuper.onInterceptTouchEvent(ev);
    }
</font></font>


这个方法的作用很简单:当我们摁下的如果是可拖拽的图标,那么进行初始化该Item的映像试图.

而在这里如果大家对WindowManager和WindowManager.LayoutParams不熟悉的朋友先去参考下这篇文章,要对WindowManager有一定的了解,简单的会应用.

接下来我们看onTouchEvent事件:



?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
     * 触摸事件处理
     */
    @Override
    publicbooleanonTouchEvent(MotionEvent ev) {
        // item的view不为空,且获取的dragPosition有效
        if(dragImageView != null&& dragPosition != INVALID_POSITION) {
            intaction = ev.getAction();
            switch(action) {
            caseMotionEvent.ACTION_UP:
                intupY = (int) ev.getY();
                stopDrag();
                onDrop(upY);
                break;
            caseMotionEvent.ACTION_MOVE:
                intmoveY = (int) ev.getY();
                onDrag(moveY);
 
                break;
            caseMotionEvent.ACTION_DOWN:
                break;
            default:
                break;
            }
            returntrue;// 取消ListView滑动.
        }
 
        returnsuper.onTouchEvent(ev);
    }

简单说明:首先在Touch中,我们要进行判断,是否点击的是拖动图标,如果是的话,那么对ACTION_MOVE and ACTION_UP相应事件进行处理,并且返回true or false.作用:取消ListView自身的Touch事件.如果不是的话,执行ListView 本身的Touch事件.

大致就介绍这么多,具体的实现,还是大家看源码吧,我注释的还算清晰,只要大家仔细看的话,一定可以掌握的,为什么这么说呢,技术只有在掌握了情况下才可以进行拓展.

对了,提醒大家要理解这三句话:


getRawX()和getRawY():获得的是相对屏幕的位置.

getX()和getY():获得的永远是相对view的触摸位置 坐标(这两个值不会超过view的长度和宽度)。

getLeft , getTop, getBottom,getRight, 这个指的是该控件相对于父控件的距离.

源码:
?
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
packagecom.jj.drag;
 
importandroid.content.Context;
importandroid.graphics.Bitmap;
importandroid.os.AsyncTask;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.Gravity;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.ViewConfiguration;
importandroid.view.ViewGroup;
importandroid.view.WindowManager;
importandroid.widget.AbsListView;
importandroid.widget.AbsListView.OnScrollListener;
importandroid.widget.AdapterView;
importandroid.widget.ImageView;
importandroid.widget.ListView;
 
importcom.jj.drag.MainActivity.DragListAdapter;
 
/***
 * 自定义拖拽ListView
 *
 * @author zhangjia
 *
 */
publicclassDragListView extendsListView {
 
    privateWindowManager windowManager;// windows窗口控制类
    privateWindowManager.LayoutParams windowParams;// 用于控制拖拽项的显示的参数
 
    privateintscaledTouchSlop;// 判断滑动的一个距离,scroll的时候会用到(24)
 
    privateImageView dragImageView;// 被拖拽的项(item),其实就是一个ImageView
    privateintdragSrcPosition;// 手指拖动项原始在列表中的位置
    privateintdragPosition;// 手指点击准备拖动的时候,当前拖动项在列表中的位置.
 
    privateintdragPoint;// 在当前数据项中的位置
    privateintdragOffset;// 当前视图和屏幕的距离(这里只使用了y方向上)
 
    privateintupScrollBounce;// 拖动的时候,开始向上滚动的边界
    privateintdownScrollBounce;// 拖动的时候,开始向下滚动的边界
 
    privatefinalstatic int step = 1;// ListView 滑动步伐.
 
    privateintcurrent_Step;// 当前步伐.
 
    /***
     * 构造方法
     *
     * @param context
     * @param attrs
     */
    publicDragListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 
    /***
     * touch事件拦截
     */
    @Override
    publicbooleanonInterceptTouchEvent(MotionEvent ev) {
        // 按下
        if(ev.getAction() == MotionEvent.ACTION_DOWN) {
            intx = (int) ev.getX();// 获取相对与ListView的x坐标
            inty = (int) ev.getY();// 获取相应与ListView的y坐标
            dragSrcPosition = dragPosition = pointToPosition(x, y);
            // 无效不进行处理
            if(dragPosition == AdapterView.INVALID_POSITION) {
                returnsuper.onInterceptTouchEvent(ev);
            }
 
            // 获取当前位置的视图(可见状态)
            ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
                    - getFirstVisiblePosition());
 
            // 获取到的dragPoint其实就是在你点击指定item项中的高度.
            dragPoint = y - itemView.getTop();
            // 这个值是固定的:其实就是ListView这个控件与屏幕最顶部的距离(一般为标题栏+状态栏).
            dragOffset = (int) (ev.getRawY() - y);
 
            // 获取可拖拽的图标
            View dragger = itemView.findViewById(R.id.iv_drag_list_item_2);
 
            // 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);// 初始化影像
            }
        }
 
        returnsuper.onInterceptTouchEvent(ev);
    }
 
    /**
     * 触摸事件处理
     */
    @Override
    publicbooleanonTouchEvent(MotionEvent ev) {
        // item的view不为空,且获取的dragPosition有效
        if(dragImageView != null&& dragPosition != INVALID_POSITION) {
            intaction = ev.getAction();
            switch(action) {
            caseMotionEvent.ACTION_UP:
                intupY = (int) ev.getY();
                stopDrag();
                onDrop(upY);
                break;
            caseMotionEvent.ACTION_MOVE:
                intmoveY = (int) ev.getY();
                onDrag(moveY);
                break;
            caseMotionEvent.ACTION_DOWN:
                break;
            default:
                break;
            }
            returntrue;// 取消ListView滑动.
        }
 
        returnsuper.onTouchEvent(ev);
    }
 
    /**
     * 准备拖动,初始化拖动项的图像
     *
     * @param bm
     * @param y
     */
    privatevoidstartDrag(Bitmap bm, inty) {
        // stopDrag();
        /***
         * 初始化window.
         */
        windowParams = newWindowManager.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 = newImageView(getContext());
        imageView.setImageBitmap(bm);
        windowManager = (WindowManager) getContext().getSystemService("window");
        windowManager.addView(imageView, windowParams);
        dragImageView = imageView;
 
    }
 
    /**
     * 拖动执行,在Move方法中执行
     *
     * @param y
     */
    publicvoidonDrag(inty) {
        intdrag_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的问题
        inttempPosition = pointToPosition(0, y);
        if(tempPosition != INVALID_POSITION) {
            dragPosition = tempPosition;
 
        }
        doScroller(y);
    }
 
    /***
     * ListView的移动.
     * 要明白移动原理:当映像移动到下端的时候,ListView向上滑动,当映像移动到上端的时候,ListView要向下滑动。正好和实际的相反.
     *
     */
 
    publicvoiddoScroller(inty) {
        Log.e("jj","y="+ y);
        Log.e("jj","upScrollBounce="+ upScrollBounce);
        // ListView需要下滑
        if(y < upScrollBounce) {
            current_Step = step + (upScrollBounce - y) / 10;// 时时步伐
        }// ListView需要上滑
        elseif(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);
 
    }
 
    /**
     * 停止拖动,删除影像
     */
    publicvoidstopDrag() {
        if(dragImageView != null) {
            windowManager.removeView(dragImageView);
            dragImageView = null;
        }
    }
 
    /**
     * 拖动放下的时候
     *
     * @param y
     */
    publicvoidonDrop(inty) {
 
        // 为了避免滑动到分割线的时候,返回-1的问题
        inttempPosition = pointToPosition(0, y);
        if(tempPosition != INVALID_POSITION) {
            dragPosition = tempPosition;
        }
 
        // 超出边界处理(如果向上超过第二项Top的话,那么就放置在第一个位置)
        if(y < getChildAt(0).getTop()) {
            // 超出上边界
            dragPosition = 0;
            // 如果拖动超过最后一项的最下边那么就防止在最下边
        }elseif(y > getChildAt(getChildCount() - 1).getBottom()) {
            // 超出下边界
            dragPosition = getAdapter().getCount() - 1;
        }
 
        // 数据交换
        if(dragPosition < getAdapter().getCount()) {
            DragListAdapter adapter = (DragListAdapter) getAdapter();
            adapter.update(dragSrcPosition, dragPosition);
        }
 
    }
 
}

下面我说下适配器:
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/***
     * 自定义适配器
     *
     * @author zhangjia
     *
     */
    classDragListAdapterextendsBaseAdapter {
        privateArrayList<String> arrayTitles;
        privateArrayList<Integer> arrayDrawables;
        privateContext context;
 
        publicDragListAdapter(Context context, ArrayList<String> arrayTitles,
                ArrayList<Integer> arrayDrawables) {
            this.context = context;
            this.arrayTitles = arrayTitles;
            this.arrayDrawables = arrayDrawables;
        }
 
        @Override
        publicView getView(intposition, View convertView, ViewGroup parent) {
            View view = convertView;
            /***
             * 在这里尽可能每次都进行实例化新的,这样在拖拽ListView的时候不会出现错乱.
             * 具体原因不明,不过这样经过测试,目前没有发现错乱。虽说效率不高,但是做拖拽LisView足够了。
             */
            view = LayoutInflater.from(context).inflate(
                    R.layout.drag_list_item,null);
 
            TextView textView = (TextView) view
                    .findViewById(R.id.tv_drag_list_item_text);
            ImageView imageView = (ImageView) view
                    .findViewById(R.id.iv_drag_list_item_1);
            imageView.setImageResource(arrayDrawables.get(position));
            textView.setText(arrayTitles.get(position));
            returnview;
        }
 
        /***
         * 动态修改ListVIiw的方位.
         *
         * @param start
         *            点击移动的position
         * @param down
         *            松开时候的position
         */
        publicvoidupdate(intstart,int