经常使用网易新闻的童鞋都知道在网易新闻中有一个新闻栏目管理,其中GridView的item是可以拖拽的,效果十分炫酷。具体效果如下图:
是不是也想自己也想实现出相同的效果呢?那就一起来往下看吧。
首先我们来梳理一下思路:
- 当用户长按选择一个item时,将该item隐藏,然后用WindowManager添加一个新的window,该window与所选择item一模一样,并且跟随用户手指滑动而不断改变位置。
- 当window的位置坐标在GridView里面时,使用
pointToPosition (int x, int y)
方法来判断对应的应该是哪个item,在adapter中作出数据集相应的变化,然后做出平移的动画。 - 当用户手指抬起时,把window移除,使用
notifyDataSetChanged()
做出GridView更新。
讲完了思路后,我们就来实践一下吧,把这个控件取名为DragGridView。
1 | public DragGridView(Context context) { |
手指在Item上长按时
首先在构造器中得到WindowManager对象以及设置长按监听器,所以只有长按item才能拖拽。
1 | @Override |
然后在onInterceptTouchEvent(MotionEvent ev)
中得到手指下落时的ev.getRawX()
和ev.getRawY()
,以备后面的计算使用。至于getRawX()
和getX()
的区别这里就不再讲述了,如果有不懂的可以自行百度。
下面就是onItemLongClick(AdapterView<?> parent, View view, int position, long id)
方法了,我们在DragGridView中定义了两种模式:MODE_DRAG
和MODE_NORMAL
,分别对应着item拖拽和item不拖拽:
1 | @Override |
在onItemLongClick()中先判断了一下模式,只有在MODE_NORMAL
的情况下才会添加window。然后计算出mX和mY。可能有些童鞋在mX和mY的计算上看不懂,我给出了一个图示:
其中红点是手指按下的坐标,也就是(mWindowX,mWindowY)这个点;绿边框为DragGridView,因为DragGridView有可能会有margin值;所以this.getLeft()就是绿边框到屏幕的距离,而view.getLeft()就是长按的Item的左边到绿边框的距离。这几个值相减就得到了mX。同理,mY也是这样得到的。
然后来看看initWindow();
这个方法:
1 | /** |
在initWindow()
中,我们先创建了一个dragView,而dragView里面的内容与长按的Item的内容完全一致。然后创建WindowManager.LayoutParams
的对象,把dragView添加到window上去。同时,也要把长按的Item隐藏了。在这里别忘了需要申请显示悬浮窗的权限:
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
手指滑动时
在initWindow()
之后,我们就要考虑当手指滑动时window也要跟着动了,我们重写onTouchEvent(MotionEvent ev)
来监听滑动事件,可以看到下面的updateWindow(ev)
方法。
1 | @Override |
这里贴出updateWindow(ev)
方法:
1 | /** |
在这里,mX和mY就派上用场了。根据ev.getRawX()
和ev.getRawY()
分别减去mX
和mY
就得到了移动中layoutParams.x和layoutParams.y。再调用updateViewLayout (View view, ViewGroup.LayoutParams params)
就出现了window跟随手指滑动而滑动的效果。最后根据 pointToPosition(int x, int y)
返回的值来执行itemMove(dropPosition);
。
1 | /** |
上面的代码主要是根据dropPosition使要改变位置的Item来做出平移动画,当最后一个要改变位置的Item平移动画完成之后,在adapter中完成数据集的交换。
1 | /** |
手指抬起时
在上面onTouchEvent(MotionEvent ev)
方法中,可以看到手指抬起时调用了closeWindow(ev.getX(), ev.getY());
,那就一起来看看:
1 | /** |
可以看出主要做的事情就是移除了window,并且也是调用了exchangePosition(int originalPosition, int nowPosition, boolean isMove)
,不同的是第三个参数isMove传入了false,这样所有的Item都显示出来了。
讲了这么多,来看看最后的效果吧:
和网易新闻的效果不相上下吧,完整的源码太长就不贴出了,下面提供源码下载:
GitHub: