经常使用今日头条、网易新闻的同学们应该都会注意到用于管理多个频道的可拖动排序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 }