Android 可拖拽的GridView效果實現, 長按可拖拽和item實時交換

Android 可拖拽的GridView效果實現, 長按可拖拽和item實時交換


在Android開發中,我們常常用到ListView和GridView,而有的時候系統的ListView,GridView並不能滿足我們的需求,所以我們需要自己定義一個ListView或者GridView,我的上一篇文章中就是自定義的一個左右滑動刪除item的例子,大家有興趣的可以去看看 Android 使用Scroller實現絢麗的ListView左右滑動刪除Item效果,今天這篇文章就給大家來自定義GridView的控件,GridView主要是來顯示網格的控件,在Android的開發中使用很普通,相對於TextView,Button這些控件來說要來的複雜些,今天給大家帶來長按GridView的item,然後將其拖拽其他item上面,使得GridView的item發生交換,比較典型的就是我們的Launcher,網上有很多關於GridView的拖動的Demo,但是大部分都是相同的,而且存在一些Bug,而且大部分都是點擊GridView的item然後進行拖動,或者item之間不進行實時交換,今天給大家更加詳細的介紹GridView拖拽,並且將Demo做的更完美,大家更容易接受,也許很多人聽到這個感覺實現起來很複雜,就關掉的這篇文章,其實告訴大家,只要知道了思路就感覺一點都不復雜了,不信大家可以接着往下看看,首先還是跟大家說說實現的思路

  1. 根據手指按下的X,Y座標來獲取我們在GridView上面點擊的item
  2. 手指按下的時候使用Handler和Runnable來實現一個定時器,假如定時時間爲1000毫秒,在1000毫秒內,如果手指擡起了移除定時器,沒有擡起並且手指點擊在GridView的item所在的區域,則表示我們長按了GridView的item
  3. 如果我們長按了item則隱藏item,然後使用WindowManager來添加一個item的鏡像在屏幕用來代替剛剛隱藏的item
  4. 當我們手指在屏幕移動的時候,更新item鏡像的位置,然後在根據我們移動的X,Y的座標來獲取移動到GridView的哪一個位置
  5. 到GridView的item過多的時候,可能一屏幕顯示不完,我們手指拖動item鏡像到屏幕下方,要觸發GridView想上滾動,同理,當我們手指拖動item鏡像到屏幕上面,觸發GridView向下滾動
  6. GridView交換數據,刷新界面,移除item的鏡像

看完上面的這些思路你是不是找到了些感覺了呢,心裏癢癢的想動手試試吧,好吧,接下來就帶大家根據思路來實現可拖拽的GridView,新建一個項目就叫DragGridView
新建一個類DragGridView繼承GridView,先來看看DragGridView的代碼,然後在根據代碼進行相關的講解

1
2
3
4
5
6
7
8
9
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
package  com.example.draggridview;
 
import  android.app.Activity;
import  android.content.Context;
import  android.graphics.Bitmap;
import  android.graphics.PixelFormat;
import  android.graphics.Rect;
import  android.os.Handler;
import  android.os.Vibrator;
import  android.util.AttributeSet;
import  android.view.Gravity;
import  android.view.MotionEvent;
import  android.view.View;
import  android.view.WindowManager;
import  android.widget.AdapterView;
import  android.widget.GridView;
import  android.widget.ImageView;
 
/**
*
* @author xiaanming
*
*/
public  class  DragGridView  extends  GridView{
      /**
        * DragGridView的item長按響應的時間, 默認是1000毫秒,也可以自行設置
        */
      private  long  dragResponseMS =  1000 ;
 
      /**
        * 是否可以拖拽,默認不可以
        */
      private  boolean  isDrag =  false ;
 
      private  int  mDownX;
      private  int  mDownY;
      private  int  moveX;
      private  int  moveY;
      /**
        * 正在拖拽的position
        */
      private  int  mDragPosition;
 
     /**
       * 剛開始拖拽的item對應的View
       */
     private  View mStartDragItemView =  null ;
 
     /**
       * 用於拖拽的鏡像,這裏直接用一個ImageView
       */
     private  ImageView mDragImageView;
 
     /**
       * 震動器
       */
     private  Vibrator mVibrator;
 
     private  WindowManager mWindowManager;
     /**
       * item鏡像的佈局參數
       */
     private  WindowManager.LayoutParams mWindowLayoutParams;
 
     /**
       * 我們拖拽的item對應的Bitmap
       */
     private  Bitmap mDragBitmap;
 
     /**
       * 按下的點到所在item的上邊緣的距離
       */
     private  int  mPoint2ItemTop ;
 
     /**
       * 按下的點到所在item的左邊緣的距離
       */
     private  int  mPoint2ItemLeft;
 
     /**
       * DragGridView距離屏幕頂部的偏移量
       */
     private  int  mOffset2Top;
 
     /**
       * DragGridView距離屏幕左邊的偏移量
       */
     private  int  mOffset2Left;
 
     /**
       * 狀態欄的高度
       */
     private  int  mStatusHeight;
 
     /**
       * DragGridView自動向下滾動的邊界值
       */
     private  int  mDownScrollBorder;
 
     /**
       * DragGridView自動向上滾動的邊界值
       */
     private  int  mUpScrollBorder;
 
     /**
       * DragGridView自動滾動的速度
       */
     private  static  final  int  speed =  20 ;
 
     /**
       * item發生變化回調的接口
       */
     private  OnChanageListener onChanageListener;
 
     public  DragGridView(Context context) {
          this (context,  null );
     }
 
     public  DragGridView(Context context, AttributeSet attrs) {
          this (context, attrs,  0 );
     }
 
     public  DragGridView(Context context, AttributeSet attrs,  int  defStyle) {
          super (context, attrs, defStyle);
          mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
          mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
          mStatusHeight = getStatusHeight(context);  //獲取狀態欄的高度
 
     }
 
     private  Handler mHandler =  new  Handler();
 
     //用來處理是否爲長按的Runnable
     private  Runnable mLongClickRunnable =  new  Runnable() {
 
          @Override
          public  void  run() {
               isDrag =  true //設置可以拖拽
               mVibrator.vibrate( 50 );  //震動一下
               mStartDragItemView.setVisibility(View.INVISIBLE); //隱藏該item
 
               //根據我們按下的點顯示item鏡像
               createDragImage(mDragBitmap, mDownX, mDownY);
         }
     };
 
     /**
       * 設置回調接口
       * @param onChanageListener
       */
     public  void  setOnChangeListener(OnChanageListener onChanageListener){
          this .onChanageListener = onChanageListener;
     }
 
     /**
       * 設置響應拖拽的毫秒數,默認是1000毫秒
       * @param dragResponseMS
       */
     public  void  setDragResponseMS( long  dragResponseMS) {
          this .dragResponseMS = dragResponseMS;
     }
 
     @Override
     public  boolean  dispatchTouchEvent(MotionEvent ev) {
          switch (ev.getAction()){
               case  MotionEvent.ACTION_DOWN:
                    mDownX = ( int ) ev.getX();
                    mDownY = ( int ) ev.getY();
 
                    //根據按下的X,Y座標獲取所點擊item的position
                    mDragPosition = pointToPosition(mDownX, mDownY);
 
                    if (mDragPosition == AdapterView.INVALID_POSITION){
                          return  super .dispatchTouchEvent(ev);
                    }
 
                    //使用Handler延遲dragResponseMS執行mLongClickRunnable
                    mHandler.postDelayed(mLongClickRunnable, dragResponseMS);
 
                    //根據position獲取該item所對應的View
                    mStartDragItemView = getChildAt(mDragPosition - getFirstVisiblePosition());
 
                    //下面這幾個距離大家可以參考我的博客上面的圖來理解下
                    mPoint2ItemTop = mDownY - mStartDragItemView.getTop();
                    mPoint2ItemLeft = mDownX - mStartDragItemView.getLeft();
 
                    mOffset2Top = ( int ) (ev.getRawY() - mDownY);
                    mOffset2Left = ( int ) (ev.getRawX() - mDownX);
 
                    //獲取DragGridView自動向上滾動的偏移量,小於這個值,DragGridView向下滾動
                    mDownScrollBorder = getHeight() / 4 ;
                    //獲取DragGridView自動向下滾動的偏移量,大於這個值,DragGridView向上滾動
                    mUpScrollBorder = getHeight() *  3 / 4 ;
 
                    //開啓mDragItemView繪圖緩存
                    mStartDragItemView.setDrawingCacheEnabled( true );
                    //獲取mDragItemView在緩存中的Bitmap對象
                    mDragBitmap = Bitmap.createBitmap(mStartDragItemView.getDrawingCache());
                    //這一步很關鍵,釋放繪圖緩存,避免出現重複的鏡像
                    mStartDragItemView.destroyDrawingCache();
 
                    break ;
               case  MotionEvent.ACTION_MOVE:
                    int  moveX = ( int )ev.getX();
                    int  moveY = ( int ) ev.getY();
 
                    //如果我們在按下的item上面移動,只要不超過item的邊界我們就不移除mRunnable
                    if (!isTouchInItem(mStartDragItemView, moveX, moveY)){
                           mHandler.removeCallbacks(mLongClickRunnable);
                    }
                    break ;
               case  MotionEvent.ACTION_UP:
                    mHandler.removeCallbacks(mLongClickRunnable);
                    mHandler.removeCallbacks(mScrollRunnable);
                    break ;
          }
          return  super .dispatchTouchEvent(ev);
     }
 
    /**
      * 是否點擊在GridView的item上面
      * @param itemView
      * @param x
      * @param y
      * @return
      */
    private  boolean  isTouchInItem(View dragView,  int  x,  int  y){
         if (dragView ==  null ){
                return  false ;
         }
         int  leftOffset = dragView.getLeft();
         int  topOffset = dragView.getTop();
         if (x < leftOffset || x > leftOffset + dragView.getWidth()){
               return  false ;
         }
 
         if (y < topOffset || y > topOffset + dragView.getHeight()){
               return  false ;
         }
 
         return  true ;
    }
 
    @Override
    public  boolean  onTouchEvent(MotionEvent ev) {
         if (isDrag && mDragImageView !=  null ){
               switch (ev.getAction()){
                    case  MotionEvent.ACTION_MOVE:
                         moveX = ( int ) ev.getX();
                         moveY = ( int ) ev.getY();
                         //拖動item
                         onDragItem(moveX, moveY);
                         break ;
                    case  MotionEvent.ACTION_UP:
                         onStopDrag();
                         isDrag =  false ;
                         break ;
               }
               return  true ;
          }
          return  super .onTouchEvent(ev);
     }
 
     /**
       * 創建拖動的鏡像
       * @param bitmap
       * @param downX
       *          按下的點相對父控件的X座標
       * @param downY
       *          按下的點相對父控件的X座標
       */
     private  void  createDragImage(Bitmap bitmap,  int  downX ,  int  downY){
           mWindowLayoutParams =  new  WindowManager.LayoutParams();
           mWindowLayoutParams.format = PixelFormat.TRANSLUCENT;  //圖片之外的其他地方透明
           mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
           mWindowLayoutParams.x = downX - mPoint2ItemLeft + mOffset2Left;
           mWindowLayoutParams.y = downY - mPoint2ItemTop + mOffset2Top - mStatusHeight;
           mWindowLayoutParams.alpha =  0 .55f;  //透明度
           mWindowLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
           mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
           mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE ;
 
           mDragImageView =  new  ImageView(getContext());
           mDragImageView.setImageBitmap(bitmap);
           mWindowManager.addView(mDragImageView, mWindowLayoutParams);
     }
 
      /**
        * 從界面上面移動拖動鏡像
        */
      private  void  removeDragImage(){
           if (mDragImageView !=  null ){
                 mWindowManager.removeView(mDragImageView);
                 mDragImageView =  null ;
           }
     }
 
     /**
       * 拖動item,在裏面實現了item鏡像的位置更新,item的相互交換以及GridView的自行滾動
       * @param x
       * @param y
       */
     private  void  onDragItem( int  moveX,  int  moveY){
           mWindowLayoutParams.x = moveX - mPoint2ItemLeft + mOffset2Left;
           mWindowLayoutParams.y = moveY - mPoint2ItemTop + mOffset2Top - mStatusHeight;
           mWindowManager.updateViewLayout(mDragImageView, mWindowLayoutParams);  //更新鏡像的位置
           onSwapItem(moveX, moveY);
 
          //GridView自動滾動
          mHandler.post(mScrollRunnable);
     }
 
     /**
       * 當moveY的值大於向上滾動的邊界值,觸發GridView自動向上滾動
       * 當moveY的值小於向下滾動的邊界值,觸犯GridView自動向下滾動
       * 否則不進行滾動
       */
     private  Runnable mScrollRunnable =  new  Runnable() {
 
           @Override
           public  void  run() {
                int  scrollY;
                if (moveY > mUpScrollBorder){
                      scrollY = speed;
                      mHandler.postDelayed(mScrollRunnable,  25 );
                }
相關文章
相關標籤/搜索