最近在一直在用今日头条,发现在我的频道编辑时的拖拽排序体验非常有意思,这种拖拽功能其实在支付宝等app上也频繁使用,于是打算自己研究一下,网上虽然有很多类似于此类功能的博客,但实现的都不是特别完美,效果总有瑕疵,今天我分享一个完美体验版,大家用了就知道!
老规矩,先上效果:
准备工作
要想实现这个效果,首先你要了解这几个方面的知识,有欠缺的同学赶紧先补一下:
* 使用WindowManager添加悬浮窗口
* onTouchEvent触摸事件的处理
* 简单的TranslateAnimation平移动画
* GridView api的熟练使用
原理分析
首先它是一个gridview,里面放了很多item,至于item你可以自己随意布局,例子中我只用了一个textview。
1、触发gridview长按事件,用windowmanager添加一个悬浮view,并占位隐藏原来的item,这个悬浮窗就是我们长按的item的一个图片副本,并且将悬浮view定位到自己的手指触摸点,将这个悬浮view设置放大倍数及透明度;
2、监听手指的移动,实时改变悬浮view的位置;
3、当移动距离超过自己的position时,用TranslateAnimation动画平移从起始位置到目标位置之间的item;
4、当手机抬起时,改变adapter中数据的位置,刷新gridview,并释放悬浮view;
下面我们一步一步的来撸代码
//首先展示一下需要的成员变量
private WindowManager.LayoutParams mWindowParams;
private WindowManager mWindowManager;
//被拖拽的item图片副本
private ImageView mDragImageView;
//按下时手指的坐标
private float downX, downY;
//是否正在拖拽
private boolean isDraging = false;
//是否正在进行移动item动画,防止高频率触发动画而发生抖动
private boolean isMoving = false;
//被拖拽和未被拖拽的标记
private static final int NOT_DRAG_ITEM = 0x0;
private static final int SHOW_DRAG_ITEM = 0x1;
//拖动时副本放大倍数
private static final float DRAG_SCALE = 1.2F;
//记录拖拽的item位置
private int mDragItemPosition;
//记录最后一个item动画toString格式
private String lastAnim;
在构造方法中初始化必要的一些变量:
public DragGridView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
//设置item长按事件
setOnItemLongClickListener(this);
mWindowParams = new WindowManager.LayoutParams();
mDragImageView = new ImageView(getContext());
//标记未被拖拽
mDragImageView.setTag(NOT_DRAG_ITEM);
//获取窗口管理对象,用于后面向窗口中添加dragImageView
mWindowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
}
在长按事件中创建副本,隐藏拖拽item
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
mDragItemPosition = position;
//清空item之前的图片缓存
view.destroyDrawingCache();
//开启图片缓存
view.setDrawingCacheEnabled(true);
//创建一个item的图片副本
Bitmap dragBitmap = Bitmap.createBitmap(view.getDrawingCache());
mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
//定义副本的长和宽
mWindowParams.width = (int) (DRAG_SCALE * dragBitmap.getWidth());
mWindowParams.height = (int) (DRAG_SCALE * dragBitmap.getHeight());
//定义副本的位置
mWindowParams.x = (int) (downX - dragBitmap.getWidth() / 2);
mWindowParams.y = (int) (downY - dragBitmap.getHeight() / 2);
//定义副本的附加参数:不能点击、不能获取焦点、悬浮窗的形式
mWindowParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
//定义副本支持透明格式
mWindowParams.format = PixelFormat.TRANSLUCENT;
mWindowParams.windowAnimations = 0;
//如果之前有这个副本先移除
if ((int) mDragImageView.getTag() == SHOW_DRAG_ITEM) {
mWindowManager.removeView(mDragImageView);
mDragImageView.setTag(NOT_DRAG_ITEM);
}
//将图片副本放入imageview
mDragImageView.setImageBitmap(dragBitmap);
//设置已有副本标记
mDragImageView.setTag(SHOW_DRAG_ITEM);
//设置imageView透明度
mDragImageView.setAlpha(0.7f);
//添加这个imageview到悬浮窗
mWindowManager.addView(mDragImageView, mWindowParams);
//此时状态变为可拖拽
isDraging = true;
//通知adapter隐藏拖拽的item
((DragAdapter) getAdapter()).hideItem(position);
//将拖拽item的图片缓存功能关闭,释放内存
view.setDrawingCacheEnabled(false);
return true;
}
监听手指的移动来移动item副本,并执行动画:
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//记录按下x、y轴位置
downX = ev.getRawX();
downY = ev.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (isDraging) {
//移动时实时刷新副本的坐标位置
mWindowParams.x = (int) (ev.getRawX() - mDragImageView.getWidth() / 2);
mWindowParams.y = (int) (ev.getRawY() - mDragImageView.getHeight() / 2);
//改变副本的位置
mWindowManager.updateViewLayout(m