經常使用今日頭條、網易新聞的同學們應該都會注意到用於管理多個頻道的可拖動排序GridView,下面介紹一下可拖動的DragGridView的實現方法。代碼放在GitHub上https://github.com/zhaoyu87/DragGridView,需要的同學可以下載
DragGridView繼承自GridView,當長按選中某個item進行拖動,放手更新GridView順序:
1.重寫onTouchEvent響應拖動事件:被按下時記錄按下坐標;拖動時更新被拖動視圖顯示;放開時更新排序
2.設置OnItemLongClickListener:響應長按選取拖動item,獲取被選中item bitmap,添加到窗口顯示
3.通過view.getDrawingCache()獲取被選中item的bitmap,用於繪制拖動的view
4.使用WindowManager來向窗口添加view,更新view顯示。關於WindowManagerService對窗口的組織方式,博客http://blog.csdn.net/luoshengyang/article/details/8498908有介紹,可以參考。
5. MotionEvent中的getX()為相對於容器的坐標,這里就是GridView;getRawX()為相對於整個屏幕的坐標
DragGridView實現如下,注釋中有更詳細的解釋
1 public class DragGridView extends GridView { 2 private static final int DRAG_IMG_SHOW = 1; 3 private static final int DRAG_IMG_NOT_SHOW = 0; 4 private static final String LOG_TAG = "DragGridView"; 5 private static final float AMP_FACTOR = 1.2f; 6 7 /**被拖動的視圖*/ 8 private ImageView dragImageView; 9 private WindowManager.LayoutParams dragImageViewParams; 10 private WindowManager windowManager; 11 private boolean isViewOnDrag = false; 12 13 /**前一次拖動的位置*/ 14 private int preDraggedOverPositon = AdapterView.INVALID_POSITION; 15 private int downRawX; 16 private int downRawY; 17 18 /**長按選中拖動item*/ 19 private OnItemLongClickListener onLongClickListener = new OnItemLongClickListener(){ 20 21 @Override 22 //長按item開始拖動 23 public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { 24 25 //記錄長按item位置 26 preDraggedOverPositon = position; 27 28 //獲取被長按item的drawing cache 29 view.destroyDrawingCache(); 30 view.setDrawingCacheEnabled(true); 31 //通過被長按item,獲取拖動item的bitmap 32 Bitmap dragBitmap = Bitmap.createBitmap(view.getDrawingCache()); 33 34 //設置拖動item的參數 35 dragImageViewParams.gravity = Gravity.TOP | Gravity.LEFT; 36 //設置拖動item為原item 1.2倍 37 dragImageViewParams.width = (int)(AMP_FACTOR*dragBitmap.getWidth()); 38 dragImageViewParams.height = (int)(AMP_FACTOR*dragBitmap.getHeight()); 39 //設置觸摸點為繪制拖動item的中心 40 dragImageViewParams.x = (downRawX - dragImageViewParams.width/2); 41 dragImageViewParams.y = (downRawY - dragImageViewParams.height/2); 42 dragImageViewParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 43 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 44 | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON 45 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; 46 dragImageViewParams.format = PixelFormat.TRANSLUCENT; 47 dragImageViewParams.windowAnimations = 0; 48 49 //dragImageView為被拖動item的容器,清空上一次的顯示 50 if((int)dragImageView.getTag() == DRAG_IMG_SHOW) { 51 windowManager.removeView(dragImageView); 52 dragImageView.setTag(DRAG_IMG_NOT_SHOW); 53 } 54 55 //設置本次被長按的item 56 dragImageView.setImageBitmap(dragBitmap); 57 58 //添加拖動item到屏幕 59 windowManager.addView(dragImageView, dragImageViewParams); 60 dragImageView.setTag(DRAG_IMG_SHOW); 61 isViewOnDrag = true; 62 63 //設置被長按item不顯示 64 ((GridViewAdapter)getAdapter()).hideView(position); 65 return true; 66 } 67 }; 68 69 public DragGridView(Context context) { 70 super(context); 71 initView(); 72 } 73 74 public DragGridView(Context context, AttributeSet attrs) { 75 super(context, attrs); 76 initView(); 77 } 78 79 public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) { 80 super(context, attrs, defStyleAttr); 81 initView(); 82 } 83 84 public void initView() { 85 setOnItemLongClickListener(onLongClickListener); 86 //初始化顯示被拖動item的image view 87 dragImageView = new ImageView(getContext()); 88 dragImageView.setTag(DRAG_IMG_NOT_SHOW); 89 //初始化用於設置dragImageView的參數對象 90 dragImageViewParams = new WindowManager.LayoutParams(); 91 //獲取窗口管理對象,用於后面向窗口中添加dragImageView 92 windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 93 } 94 95 96 @Override 97 public boolean onTouchEvent(MotionEvent ev) { 98 99 //被按下時記錄按下的坐標 100 if(ev.getAction() == MotionEvent.ACTION_DOWN) { 101 //獲取觸摸點相對於屏幕的坐標 102 downRawX = (int)ev.getRawX(); 103 downRawY = (int)ev.getRawY(); 104 } 105 //dragImageView處於被拖動時,更新dragImageView位置 106 else if((ev.getAction() == MotionEvent.ACTION_MOVE) && (isViewOnDrag == true)) { 107 Log.i(LOG_TAG, "" + ev.getRawX() + " " + ev.getRawY()); 108 //設置觸摸點為dragImageView中心 109 dragImageViewParams.x = (int)(ev.getRawX() - dragImageView.getWidth()/2); 110 dragImageViewParams.y = (int)(ev.getRawY() - dragImageView.getHeight()/2); 111 //更新窗口顯示 112 windowManager.updateViewLayout(dragImageView, dragImageViewParams); 113 //獲取當前觸摸點的item position 114 int currDraggedPosition = pointToPosition((int)ev.getX(), (int)ev.getY()); 115 //如果當前停留位置item不等於上次停留位置的item,交換本次和上次停留的item 116 if((currDraggedPosition != AdapterView.INVALID_POSITION) && (currDraggedPosition != preDraggedOverPositon)) { 117 ((GridViewAdapter)getAdapter()).swapView(preDraggedOverPositon, currDraggedPosition); 118 preDraggedOverPositon = currDraggedPosition; 119 } 120 } 121 //釋放dragImageView 122 else if((ev.getAction() == MotionEvent.ACTION_UP) && (isViewOnDrag == true)) { 123 ((GridViewAdapter)getAdapter()).showHideView(); 124 if((int)dragImageView.getTag() == DRAG_IMG_SHOW) { 125 windowManager.removeView(dragImageView); 126 dragImageView.setTag(DRAG_IMG_NOT_SHOW); 127 } 128 isViewOnDrag = false; 129 } 130 return super.onTouchEvent(ev); 131 } 132 }
GridViewAdapter繼承自BaseAdapter:
1.提供showHideView、hideView兩個方法用於顯示和隱藏選中Item的Text
2.提供swapView方法用於拖動過程中更新GridView中item顯示
1 public class GridViewAdapter extends BaseAdapter { 2 private Context context; 3 private List<String> strList; 4 private int hidePosition = AdapterView.INVALID_POSITION; 5 6 public GridViewAdapter(Context context, List<String> strList) { 7 this.context = context; 8 this.strList = strList; 9 } 10 11 @Override 12 public int getCount() { 13 return strList.size(); 14 } 15 16 @Override 17 public String getItem(int position) { 18 return strList.get(position); 19 } 20 21 @Override 22 public long getItemId(int position) { 23 return position; 24 } 25 26 @Override 27 public View getView(int position, View convertView, ViewGroup parent) { 28 TextView view; 29 if(convertView == null) { 30 view = new TextView(context); 31 } 32 else { 33 view = (TextView)convertView; 34 } 35 36 //hide時隱藏Text 37 if(position != hidePosition) { 38 view.setText(strList.get(position)); 39 } 40 else { 41 view.setText(""); 42 } 43 view.setId(position); 44 45 return view; 46 } 47 48 public void hideView(int pos) { 49 hidePosition = pos; 50 notifyDataSetChanged(); 51 } 52 53 public void showHideView() { 54 hidePosition = AdapterView.INVALID_POSITION; 55 notifyDataSetChanged(); 56 } 57 58 public void removeView(int pos) { 59 strList.remove(pos); 60 notifyDataSetChanged(); 61 } 62 63 //更新拖動時的gridView 64 public void swapView(int draggedPos, int destPos) { 65 //從前向后拖動,其他item依次前移 66 if(draggedPos < destPos) { 67 strList.add(destPos+1, getItem(draggedPos)); 68 strList.remove(draggedPos); 69 } 70 //從后向前拖動,其他item依次后移 71 else if(draggedPos > destPos) { 72 strList.add(destPos, getItem(draggedPos)); 73 strList.remove(draggedPos+1); 74 } 75 hidePosition = destPos; 76 notifyDataSetChanged(); 77 } 78 }