之前一直想写个,但是没时间。现在补上。先上效果图:
先分析一下,想到的两种实现方法:
第一种:
1. 当用户长按选择一个item时,将该item隐藏,然后用WindowManager添加一个新的window,该window与所选择item一模一样,并且跟随用户手指滑动而不断改变位置。
2. 当window的位置坐标在GridView里面时,使用pointToPosition (int x, int y)方法来判断对应的应该是哪个item,在adapter中作出数据集相应的变化,然后做出平移的动画。
3. 当用户手指抬起时,把window移除,使用notifyDataSetChanged()做出GridView更新
第二种:
用getView获取要拖动的view,在Move事件下不断平移得到x新的坐标调用onLayout(true,l,t,r,b);
view.startAnimation; onLayout(true, getLeft(), getTop(), getRight(), getBottom());
1. 当用户长按选择一个item时,将该item隐藏,然后用WindowManager添加一个新的window,该window与所选择item一模一样,并且跟随用户手指滑动而不断改变位置。
2. 当window的位置坐标在GridView里面时,使用pointToPosition (int x, int y)方法来判断对应的应该是哪个item,在adapter中作出数据集相应的变化,然后做出平移的动画。
3. 当用户手指抬起时,把window移除,使用notifyDataSetChanged()做出GridView更新
第二种:
用getView获取要拖动的view,在Move事件下不断平移得到x新的坐标调用onLayout(true,l,t,r,b);
view.startAnimation; onLayout(true, getLeft(), getTop(), getRight(), getBottom());
第一种看到已经有人实现了,但是偶尔会有崩溃的问题。于是我根据他的思路,以自己更容易理解的方式再写了一遍。在此对这位兄弟表示感谢,地址我会贴在文章最后.
第二种有时间再补上。
下面正式开始:
先看一下所有的方法:
DragGridView继承GridView,在onInterceptTouchEvent的ACTION_DOWN时设置长按监听。长按item触发OnItemLongClickListener,
在这里windowManager.addView(dragView, windowLayoutParams);
onTouchEvent方法判断更新操作:
@Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) ev.getRawX(); lastY = (int) ev.getRawY(); windowX = (int) ev.getX(); windowY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE://拖动时判断位置,平移; if (isDraging) { updateWindow(ev, (int) ev.getRawX() - lastX, (int) ev.getRawY() - lastY); lastX = (int) ev.getRawX(); lastY = (int) ev.getRawY(); } break; case MotionEvent.ACTION_UP://拖动时判断位置,平移; if (isDraging) { isDraging = false; closeWindow(ev); } break; } return super.onTouchEvent(ev); }
重要的方法:
updateWindow();
/** * 更新悬浮窗位置; * * @param ev */ private void updateWindow(MotionEvent ev, int deltaX, int deltaY) { mTempPos = pointToPosition((int) ev.getX(), (int) ev.getY()); if (mTempPos != AdapterView.INVALID_POSITION && mTempPos != prePos) { if (mTempPos > prePos) { for (int i = prePos + 1; i <= mTempPos; i++) { Animation moveAnimation; if (i % numColumns == 0) { moveAnimation = getMoveAnimation((numColumns - 1) * horizon_div, -vertical_div); } else { moveAnimation = getMoveAnimation(-horizon_div, 0); } getChildAt(i).startAnimation(moveAnimation);//left. if (i == mTempPos) { lastAniID = moveAnimation.toString(); } moveAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { isMoving = true; } @Override public void onAnimationEnd(Animation animation) { swap(animation); isMoving = false; } @Override public void onAnimationRepeat(Animation animation) { } }); } } else {//mTempPos < prePos for (int i = prePos - 1; i >= mTempPos; i--) { Animation moveAnimation; if (i % numColumns == numColumns - 1) { moveAnimation = getMoveAnimation(-(numColumns - 1) * horizon_div, vertical_div); } else { moveAnimation = getMoveAnimation(horizon_div, 0); } getChildAt(i).startAnimation(moveAnimation);//left. if (i == mTempPos) { lastAniID = moveAnimation.toString(); } moveAnimation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { swap(animation); } @Override public void onAnimationRepeat(Animation animation) { } }); } } ((DragAdapter) getAdapter()).swap(prePos, mTempPos); prePos = mTempPos; } windowLayoutParams.x += deltaX; windowLayoutParams.y += deltaY; windowManager.updateViewLayout(dragView, windowLayoutParams); }prePos为上一次拖动的位置,mTempPos为当前window添加的dragView移到的位置,不相等时出发平移动画,并且置换数据位置。
最后在UP事件时调用closeWindow,移除windowManager的view,显示之前隐藏的item。移除子view的动画。
这么思路整理下来,还是挺简单的,实际写的时候还是有需要注意的点:
1.在子view结束动画后,需要
clearAnimation(),因为Animation.setFillAfter(true);执行动画后,数据有变动了 ,再notify时也不会消除上一次动画的影响,造成错位。
2. 刚开始我使用的是动画结束才真实交换数据位置。但这样但会造成两次动画,因为动画没结束,prePos和mTempPos没变化,会再次触发动画。解决方法就是触发动画的同时真实交换数据位置。
3.在windowmanager添加view时:app主题为actionbar时会偏移,<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">改为NoActionBar。windowLayoutParams.y = DragGridView.this.getTop() + view.getTop();(这个是之前的写法,不适用于在scrollView的情况)
3.需要在scrollView中使用该控件,需要解决显示的长度,和事件冲突。(具体看源码,这里不在解释)
最后附上源码地址:https://github.com/Ulez/WangyiIndicator
参考:http://www.jianshu.com/p/559b631ec330