android ListView和GridView拖拽移位具體實現及拓展

關於ListView拖拽移動位置,想必大家並不陌生,比較不錯的軟件都用到如此功能了.如:搜狐,網易,百度等,但是相比來說還是百度的用戶體驗較好,不偏心了,下面看幾個示例:
QQ截圖20121218160913.png
2012-12-18 16:09 上傳
下載附件 (73.45 KB)

首先說一下:拖拽ListView的item就不應該可以任意移動,只應該在ListView所在的範圍內,而網易的你看看我都可以移動到狀態欄了,雖然你做了處理,但是用戶體驗我個人感覺不好,在看看百度的,不僅控制了移動範圍,更不錯的百度的移動起來會時時的換位,看起來相當的形象,所以我認爲這樣相當的棒.
說明一點,我沒有那麼有才,我也是看別人代碼,然後自己整理下.在這裏就簡單記載一下.
首先對touch事件的處理,從應用中,我們可以得出,在我們點擊後面拖拉圖標後,就會創建一個item的影像視圖.並且可以移動該影像,而此時的ListView不應該有touch事件.
onInterceptTouchEvent方法.
代碼片段,雙擊複製
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
/***
         * 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(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;
                }
  
                return super.onInterceptTouchEvent(ev);
        }


這個方法的作用很簡單:當我們摁下的如果是可拖拽的圖標,那麼進行初始化該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
        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);
        }


簡單說明:首先在Touch中,我們要進行判斷,是否點擊的是拖動圖標,如果是的話,那麼對ACTION_MOVE and ACTION_UP相應事件進行處理,並且返回true or false.作用:取消ListView自身的Touch事件.如果不是的話,執行ListView 本身的Touch事件.

大致就介紹這麼多,具體的實現,還是大家看源碼吧,我註釋的還算清晰,只要大家仔細看的話,一定可以掌握的,爲什麼這麼說呢,技術只有在掌握了情況下纔可以進行拓展.

對了,提醒大家要理解這三句話:


getRawX()和getRawY():獲得的是相對屏幕的位置.

getX()和getY():獲得的永遠是相對view的觸摸位置 座標(這兩個值不會超過view的長度和寬度)。

getLeft , getTop, getBottom,getRight, 這個指的是該控件相對於父控件的距離.
源碼:
代碼片段,雙擊複製
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
package com.jj.drag;
  
import android.content.Context;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
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.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
  
import com.jj.drag.MainActivity.DragListAdapter;
  
/***
 * 自定義拖拽ListView
 
 * @author zhangjia
 
 */
public class DragListView extends ListView {
  
        private WindowManager windowManager;// windows窗口控制類
        private WindowManager.LayoutParams windowParams;// 用於控制拖拽項的顯示的參數
  
        private int scaledTouchSlop;// 判斷滑動的一個距離,scroll的時候會用到(24)
  
        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;// 當前步伐.
  
        /***
         * 構造方法
         
         * @param context
         * @param attrs
         */
        public DragListView(Context context, AttributeSet attrs) {
                super(context, attrs);
        }
  
        /***
         * 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(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 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("window");
                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) {
                Log.e("jj", "y=" + y);
                Log.e("jj", "upScrollBounce=" + upScrollBounce);
                // 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()) {
                        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
         
         */
        class DragListAdapter extends BaseAdapter {
                private ArrayList<String> arrayTitles;
                private ArrayList<Integer> arrayDrawables;
                private Context context;
  
                public DragListAdapter(Context context, ArrayList<String> arrayTitles,
                                ArrayList<Integer> arrayDrawables) {
                        this.context = context;
                        this.arrayTitles = arrayTitles;
                        this.arrayDrawables = arrayDrawables;
                }
  
                @Override
                public View getView(int position, 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));
                        return view;
                }
  
                /***
                 * 動態修改ListVIiw的方位.
                 
                 * @param start
                 *            點擊移動的position
                 * @param down
                 *            鬆開時候的position
                 */
                public void update(int start, int down) {
                        // 獲取刪除的東東.
                        String title = arrayTitles.get(start);
                        int drawable_id = arrayDrawables.get(start);
  
                        arrayTitles.remove(start);// 刪除該項
                        arrayDrawables.remove(start);// 刪除該項
  
                        arrayTitles.add(down, title);// 添加刪除項
                        arrayDrawables.add(down, drawable_id);// 添加刪除項
  
                        notifyDataSetChanged();// 刷新ListView
                }
  
                @Override
                public int getCount() {
  
                        return Title.length;
                }
  
                @Override
                public Object getItem(int position) {
                        return Title[position];
                }
  
                @Override
                public long getItemId(int position) {
                        return position;
                }
  
        }

這裏不過多解釋了,相信大家都看的明白.如果疑問請留言.

展示下運行效果:

1354206807_6198.gif
2012-12-18 16:12 上傳
下載附件 (303.75 KB)


效果看起來還行吧,如果覺得不錯的話,記得要贊一個哦.

下面我們接着修改,模擬百度嘛,誰讓百度這麼牛叉呢.

思路:點中拖拉圖標的時候,每次移動只要dragPosition發生改變,也就是我移動到了下一個位置,那麼此時我就進行交換執行update.並且除了第一次移動外,在每次交換後要除去映射源的顯示,這樣用戶覺得這裏的空位就是就是爲我準備的,比較人性化.

實現起來並不複雜,前提是你得掌握上面的操作.

源碼如下;

代碼片段,雙擊複製
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
package com.jj.drag;
  
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
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.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
  
import com.jj.drag.MainActivity.DragListAdapter;
  
public class DragListView extends ListView {
  
        private WindowManager windowManager;// windows窗口控制類
        private WindowManager.LayoutParams windowParams;// 用於控制拖拽項的顯示的參數
  
        private int scaledTouchSlop;// 判斷滑動的一個距離,scroll的時候會用到(24)
  
        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 temChangId;// 臨時交換id
  
        private boolean isLock;// 是否上鎖.
  
        public void setLock(boolean isLock) {
                this.isLock = isLock;
        }
  
        public DragListView(Context context, AttributeSet attrs) {
                super(context, attrs);
                scaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        }
  
        /***
         * touch事件攔截 在這裏我進行相應攔截,
         */
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
                // 按下
                if (ev.getAction() == MotionEvent.ACTION_DOWN && !isLock) {
                        int x = (int) ev.getX();// 獲取相對與ListView的x座標
                        int y = (int) ev.getY();// 獲取相應與ListView的y座標
                        temChangId = 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(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.setBackgroundColor(Color.BLUE);
                                itemView.setDrawingCacheEnabled(true);// 開啓cache.
                                Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());// 根據cache創建一個新的bitmap對象.
                                startDrag(bm, y);// 初始化影像
                        }
                        return false;
                }
  
                return super.onInterceptTouchEvent(ev);
        }
  
        /**
         * 觸摸事件處理
         */
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
                // item的view不爲空,且獲取的dragPosition有效
                if (dragImageView != null && dragPosition != INVALID_POSITION
                                && !isLock) {
                        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("window");
                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;
                        windowManager.updateViewLayout(dragImageView, windowParams);// 時時移動.
                }
                // 爲了避免滑動到分割線的時候,返回-1的問題
                int tempPosition = pointToPosition(0, y);
                if (tempPosition != INVALID_POSITION) {
                        dragPosition = tempPosition;
                }
  
                onChange(y);// 時時交換
  
                doScroller(y);// listview移動.
        }
  
        /***
         * ListView的移動.
         * 要明白移動原理:當我移動到下端的時候,ListView向上滑動,當我移動到上端的時候,ListView要向下滑動。正好和實際的相反.
         
         */
  
        public void doScroller(int y) {
                // Log.e("jj", "y=" + y);
                // Log.e("jj", "upScrollBounce=" + upScrollBounce);
                // 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;
                }
        }
  
        /***
         * 拖動時時change
         */
        private void onChange(int y) {
                // 數據交換
                if (dragPosition < getAdapter().getCount()) {
                        DragListAdapter adapter = (DragListAdapter) getAdapter();
                        adapter.isHidden = false;
                        if (dragPosition != temChangId) {
                                adapter.update(temChangId, dragPosition);
                                temChangId = dragPosition;// 將點擊最初所在位置position付給臨時的,用於判斷是否換位.
                        }
                }
  
                // 爲了避免滑動到分割線的時候,返回-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;
                }
  
        }
  
        /**
         * 拖動放下的時候
         
         * @param y
         */
        public void onDrop(int y) {
                // 數據交換
                if (dragPosition < getAdapter().getCount()) {
                        DragListAdapter adapter = (DragListAdapter) getAdapter();
                        adapter.isHidden = false;
                        adapter.notifyDataSetChanged();// 刷新.
                }
        }
  
}


因爲我們要時時交換位置,所以將原先的拖動方法onDrop方法移動到onChange中.具體的還是看源碼吧.

另外的就是對適配器的修改,因爲你要對特殊的item進行隱藏之類的操作,這些代碼我就不寫了,我會將案例上傳網上,不懂的可以下載源碼.

好了還是我們來觀看下效果吧.

1354244742_6656.gif
2012-12-18 16:13 上傳
下載附件 (339.28 KB)

怎麼樣,這個效果看起來要比上面那個效果更人性化點吧,我的操作或許有點快,不信的話,你自己手機體驗一下吧.

關於ListView拖拽就說到這裏,如有不足請大家自己創新.


下面我們接着對GridView的拖拽簡單說明.因爲這些在項目中我們都會用到,所以既然做到就做全面點吧.好了大家接着往下看吧.
首先說明,原理一樣,都是拖動映像,記錄拖動位置,然後調用notifyDataSetChanged更新UI.
而GridView不同的是你要根據x,y值共同獲取點擊的position和移動至的position,而ListView爲不涉及x座標.

嗯,最初的原始移動我就不給大家展示了,效果也不是很友好,我直接展示時時更新的那種方法.效果類是與上面那個時時更新ListView一樣。

原理也一樣.下面我們直接看代碼吧.

代碼片段,雙擊複製
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package com.jj.draggrid;
  
import java.util.logging.Handler;
  
import com.jj.draggrid.MainActivity.DragGridAdapter;
  
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;
  
/***
 * 自定義拖拽GridView
 
 * @author zhangjia
 
 */
  
public class DragGridView extends GridView {
  
        private WindowManager windowManager;// windows窗口控制類
        private WindowManager.LayoutParams windowParams;// 用於控制拖拽項的顯示的參數
  
        private int scaledTouchSlop;// 判斷滑動的一個距離,scroll的時候會用到(24)
  
        private ImageView dragImageView;// 被拖拽的項(item),其實就是一個ImageView
        private int dragSrcPosition;// 手指拖動項原始在列表中的位置
        private int dragPosition;// 手指點擊準備拖動的時候,當前拖動項在列表中的位置.
  
        private int dragPointX;// 在當前數據項中的位置
        private int dragPointY;// 在當前數據項中的位置
        private int dragOffsetX;// 當前視圖和屏幕的距離(這裏只使用了x方向上)
        private int dragOffsetY;// 當前視圖和屏幕的距離(這裏只使用了y方向上)
  
        private int upScrollBounce;// 拖動的時候,開始向上滾動的邊界
        private int downScrollBounce;// 拖動的時候,開始向下滾動的邊界
  
        private int temChangId;// 臨時交換id
  
        private boolean isDoTouch = false;// touch是否可用
  
        private boolean isHide = false;// 是否隱藏
  
        private Handler handler;
  
        public void setDoTouch(boolean isDoTouch) {
                this.isDoTouch = isDoTouch;
        }
  
        public DragGridView(Context context, AttributeSet attrs) {
                super(context, attrs);
        }
  
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
  
                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                        int x = (int) ev.getX();
                        int y = (int) ev.getY();
  
                        temChangId = dragSrcPosition = dragPosition = pointToPosition(x, y);
  
                        if (dragPosition == AdapterView.INVALID_POSITION) {
                                return super.onInterceptTouchEvent(ev);
                        }
  
                        ViewGroup itemView = (ViewGroup) getChildAt(dragPosition
                                        - getFirstVisiblePosition());
  
                        dragPointX = x - itemView.getLeft();
                        dragPointY = y - itemView.getTop();
                        dragOffsetX = (int) (ev.getRawX() - x);
                        dragOffsetY = (int) (ev.getRawY() - y);
  
                        View dragger = itemView.findViewById(R.id.drag_grid_item);
  
                        /***
                         * 判斷是否選中拖動圖標
                         */
                        if (dragger != null && dragPointX > dragger.getLeft()
                                        && dragPointX < dragger.getRight()
                                        && dragPointY > dragger.getTop()
                                        && dragPointY < dragger.getBottom() + 20) {
  
                                upScrollBounce = getHeight() / 4;
                                downScrollBounce = getHeight() * 3 / 4;
  
                                itemView.setDrawingCacheEnabled(true);
                                Bitmap bm = Bitmap.createBitmap(itemView.getDrawingCache());
                                startDrag(bm, x, y);// 初始話映像
  
                                dragger.setVisibility(View.INVISIBLE);// 隱藏該項.
                        }
                }
  
                return super.onInterceptTouchEvent(ev);
        }
  
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
  
                if (dragImageView != null && dragPosition != INVALID_POSITION
                                && isDoTouch) {
                        int action = ev.getAction();
                        switch (action) {
                        /***
                         
                         */
                        case MotionEvent.ACTION_UP:
                                int upX = (int) ev.getX();
                                int upY = (int) ev.getY();
                                stopDrag();// 刪除映像
                                onDrop(upX, upY);// 鬆開
                                // isDoTouch = false;
                                break;
                        /***
                         * 拖拽item
                         
                         */
                        case MotionEvent.ACTION_MOVE:
                                int moveX = (int) ev.getX();
                                int moveY = (int) ev.getY();
                                onDrag(moveX, moveY);// 拖拽
                                break;
                        case MotionEvent.ACTION_DOWN:
                                int downX = (int) ev.getX();
                                int downY = (int) ev.getY();
                                onHide(downX, downY);// 隱藏該項
                                break;
                        default:
                                break;
                        }
                        return true;
                }
  
                return super.onTouchEvent(ev);
        }
  
        /**
         * 準備拖動,初始化拖動項的圖像
         
         * @param bm
         * @param y
         */
        public void startDrag(Bitmap bm, int x, int y) {
  
                windowParams = new WindowManager.LayoutParams();
                windowParams.gravity = Gravity.TOP | Gravity.LEFT;
                windowParams.x = x - dragPointX + dragOffsetX;
                windowParams.y = y - dragPointY + dragOffsetY;
                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.windowAnimations = 0;
  
                ImageView imageView = new ImageView(getContext());
                imageView.setImageBitmap(bm);
                windowManager = (WindowManager) getContext().getSystemService("window");
                windowManager.addView(imageView, windowParams);
                dragImageView = imageView;
  
        }
  
        /***
         * 拖動時時change
         */
        private void onChange(int x, int y) {
                // 獲取適配器
                DragGridAdapter adapter = (DragGridAdapter) getAdapter();
                // 數據交換
                if (dragPosition < getAdapter().getCount()) {
                        // 不相等的情況下要進行換位,相等的情況下說明正在移動
                        if (dragPosition != temChangId) {
                                adapter.update(temChangId, dragPosition);// 進行換位
                                temChangId = dragPosition;// 將點擊最初所在位置position付給臨時的,用於判斷是否換位.
                        }
  
                }
  
                // 爲了避免滑動到分割線的時候,返回-1的問題
                int tempPosition = pointToPosition(x, y);
                if (tempPosition != INVALID_POSITION) {
                        dragPosition = tempPosition;
                }
  
        }
  
        /***
         * 拖動執行,在Move方法中執行
         
         * @param x
         * @param y
         */
        public void onDrag(int x, int y) {
                // 移動
                if (dragImageView != null) {
                        windowParams.alpha = 0.8f;
                        windowParams.x = x - dragPointX + dragOffsetX;
                        windowParams.y = y - dragPointY + dragOffsetY;
                        windowManager.updateViewLayout(dragImageView, windowParams);
                }
  
                onChange(x, y);// 時時交換
  
                // 滾動
                if (y < upScrollBounce || y > downScrollBounce) {
                        // 使用setSelection來實現滾動
                        setSelection(dragPosition);
                }
  
        }
  
        /***
         * 隱藏該選項
         */
        private void onHide(int x, int y) {
                // 獲取適配器
                DragGridAdapter adapter = (DragGridAdapter) getAdapter();
                // 爲了避免滑動到分割線的時候,返回-1的問題
                int tempPosition = pointToPosition(x, y);
                if (tempPosition != INVALID_POSITION) {
                        dragPosition = tempPosition;
                }
                adapter.setIsHidePosition(dragPosition);
  
        }
  
        /**
         * 停止拖動,刪除影像
         */
        public void stopDrag() {
                if (dragImageView != null) {
                        windowManager.removeView(dragImageView);
                        dragImageView = null;
                }
        }
  
        /***
         * 拖動放下的時候
         
         * @param x
         * @param y
         */
        public void onDrop(int x, int y) {
  
                DragGridAdapter adapter = (DragGridAdapter) getAdapter();
                adapter.setIsHidePosition(-1);// 不進行隱藏
  
        }
  
}


相信大家只要ListView拖拽弄白後,這個GridView也會輕易弄出來,其實拖拽就是對座標的考察。

向大家展示一下效果:

1354518890_6212.gif
2012-12-18 16:14 上傳
下載附件 (203.68 KB)


但是有個不足的地方,網上一些例子都是長按可以拖拽,而點擊則執行點擊事件.其實實現起來也不是很複雜,可是在實現的過程中,遇到了詭異糾結的問題,鬱悶了一天,結果目前先放棄,以後哪天在搞搞吧.糾結的問題就是錯位.

我說下我的思路:首先,我們在自定義GridView中創建一個控制是否可以Touch拖拽的變量,而這個變量的值我們通過對GridView的setOnItemClickListener和setOnItemLongClickListener來獲取,

如:
代碼片段,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
gv_main.setOnItemClickListener(new OnItemClickListener() {
  
                        @Override
                        public void onItemClick(AdapterView<?> parent, View view,
                                        int position, long id) {
                                gv_main.setDoTouch(false);
                                Toast.makeText(MainActivity.this,
                                                adapter.getItemId(position) + "", 1).show();
                        }
                });
  
                gv_main.setOnItemLongClickListener(new OnItemLongClickListener() {
  
                        @Override
                        public boolean onItemLongClick(AdapterView<?> parent, View view,
                                        int position, long id) {
                                gv_main.setDoTouch(true);
                                return true;
                        }
                });


這樣我們就實現了長按可以拖拽的效果了,可是遇到個變態的問題,不過這個思路沒有錯,肯定可以實現.

就先說到這裏,其實通過這個例子,我們還可以拓展實現ListView上滑動的時候,到達Title時,Title停留在頂部,當下一個Titile滑動到這裏的時候,那麼代替前面那個TItle
發佈了30 篇原創文章 · 獲贊 12 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章