package com.freelib.multiitem.helper; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.OrientationHelper; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.freelib.multiitem.adapter.holder.BaseViewHolder; import com.freelib.multiitem.listener.OnItemDragListener; /** * Created by free46000 on 2016/8/19. * 面板拖动辅助类 -跨RecyclerView拖动 * <p> * <b>计算横向和竖向的RecyclerView滚动</b>{@link #scrollIfNecessary(RecyclerView, int, int)}<br> * 大量参考了`ItemTouchHelper`的源码,根据用户触摸位置计算是否需要滚动,和滚动的方向与距离 详见ItemDragListener的calcXXXScrollDistance() calcScrollXXXDirect(); * 采用定时Runnable形式,保证持续的滚动; * 滚动时调用Item位置计算方法,使得在滚动过程中也可以更换Item位置 * <p> * <b>Item位置更换计算</b>{@link #moveIfNecessary(float, float)}<br> * 触摸位置都为相对屏幕位置,方便后续计算 * 根据触摸位置horizontalRecycler.findChildViewUnder(x, y)找到垂直recyclerView的位置,若找到位置继续<br> * 根据上一次垂直recyclerView所在的位置,判断是否为第一次选中或者是切换recyclerView的操作,此处可通过itemDragListener回调拦截此次操作的结果<br> * 如果需要切换recyclerView的位置,此时需要对被拖动的Item进行remove,并在新的recyclerView中add进去<br> * 根据触摸位置recyclerView.findChildViewUnder(itemX, itemY)找到itemView的位置<br> * 根据上一次itemView所在的位置,判断是否需要移动itemView位置的操作,此处可通过itemDragListener回调拦截此次操作的结果<br> * 如果需要移动动itemView位置则需要把recyclerView滚动到合适的位置,防recyclerView乱跳<br> */ public class ItemDragHelper { public static final int NONE = -1; private OnItemDragListener dragListener = new EmptyDragListener(); private RecyclerView horizontalRecycler; private DragFloatViewHelper floatViewHelper; private boolean isDrag; private int lastRecyclerPos = NONE; private RecyclerView lastRecyclerView; private int lastItemPos = NONE; private float lastTouchRawX; private float lastTouchRawY; private int itemViewHeight; /** * 横向滚动的RecyclerView * * @param horizontalRecycler */ public ItemDragHelper(@NonNull RecyclerView horizontalRecycler) { this.horizontalRecycler = horizontalRecycler; floatViewHelper = new DragFloatViewHelper(); } /** * 开始拖拽 * * @param viewHolder 选中的Item的ViewHolder */ public void startDrag(@NonNull BaseViewHolder viewHolder) { View itemView = viewHolder.itemView; int itemPosition = viewHolder.getItemPosition(); dragListener.setItemViewHolder(viewHolder); if (!dragListener.onItemSelected(itemView, itemPosition)) { return; } isDrag = true; initParams(); lastItemPos = itemPosition; dragListener.onDragStart(); floatViewHelper.createView(itemView, lastTouchRawX, lastTouchRawY, dragListener.getScale()); dragListener.onDrawFloatView(floatViewHelper.getFloatView()); RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) itemView.getLayoutParams(); itemViewHeight = itemView.getHeight() + params.topMargin + params.bottomMargin; } private void initParams() { lastRecyclerPos = NONE; lastRecyclerView = null; } /** * 设置拖拽回调Listener * * @param onItemDragListener 回调Listener */ public void setOnItemDragListener(@NonNull OnItemDragListener onItemDragListener) { this.dragListener = onItemDragListener; } public RecyclerView getHorizontalRecycler() { return horizontalRecycler; } /** * 必须完整处理Touch事件,可以在Activity或者外层的ViewGroup或可以拦截Touch事件的地方回调都可以 * * @param event touch的event * @return true表示消耗掉事件 */ public boolean onTouch(@NonNull MotionEvent event) { lastTouchRawX = event.getRawX(); lastTouchRawY = event.getRawY(); if (!isDrag) { return false; } //如果drag过程没有MOVE事件,lastVerticalPos和lastRecyclerView是不能被正确初始化的,这样dragFinish回调就会有问题 // if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { floatViewHelper.updateView((int) lastTouchRawX, (int) lastTouchRawY); moveIfNecessary(lastTouchRawX, lastTouchRawY); scrollRunnableStart(); // } if (event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { stopDrag(); } return true; } private void scrollRunnableStart() { if (lastRecyclerView != null) { lastRecyclerView.removeCallbacks(scrollRunnable); scrollRunnable.run(); lastRecyclerView.invalidate(); } } private void stopDrag() { if (isDrag) { dragListener.onDragFinish(lastRecyclerView, lastRecyclerPos, lastItemPos); floatViewHelper.removeView(); } isDrag = false; } /** * 当用户拖动的时候,计算是否需要移动Item * * @param touchRawX float event.getRawX() * @param touchRawY float event.getRawY() */ private boolean moveIfNecessary(float touchRawX, float touchRawY) { boolean result = true; //找到当前触摸点下的recyclerView float[] location = getInsideLocation(horizontalRecycler, touchRawX, touchRawY); View view = horizontalRecycler.findChildViewUnder(location[0], location[1]); int recyclerPos = getPositionByChildView(view); // System.out.println("find_parent_out:lastRecPos:" + lastRecyclerPos + "-recPos:" + recyclerPos + "=loc0:" + location[0] + "=loc1:" + location[1]); RecyclerView recyclerView = findRecyclerView(view); //没有找到所属位置或者目标RecyclerView,则不继续 if (recyclerPos == NONE || recyclerView == null) { return false; } //找到当前触摸点下的itemView location = getInsideLocation(recyclerView, touchRawX, touchRawY); float itemX = location[0]; float itemY = location[1]; View itemView = recyclerView.findChildViewUnder(itemX, itemY); int itemPos = getTargetItemPos(itemView, itemY, lastRecyclerPos, recyclerPos); // System.out.println("find_parent_out:lastItemPos:" + lastItemPos + "==itemPos:" + itemPos + "==childX:" + itemX + "===childY:" + itemY); if (isSelectedRecyclerView(lastRecyclerPos, recyclerPos)) { dragListener.onRecyclerSelected(recyclerView, recyclerPos); lastRecyclerPos = recyclerPos; lastRecyclerView = recyclerView; } else if (isChangeRecyclerView(lastRecyclerPos, recyclerPos)) { itemPos = calcItemPositionWhenChangeRecycler(recyclerView, itemView, itemPos, itemX, itemY); // System.out.println("find_parent_inside:" + itemPos + "==" + lastRecyclerPos + "-" + recyclerPos + "==" + "childX:" + itemX + "childY:" + itemY); if (itemPos != NONE) { //当recycler change的时候,返回Recycler Position recyclerPos = dragListener.onRecyclerChangedRecyclerPosition(lastRecyclerView, recyclerView, lastItemPos, itemPos, lastRecyclerPos, recyclerPos); //当recycler切换的时候,返回Item位置,有的Recycler不管touch在什么位置都要替换指定的item位置 itemPos = dragListener.onRecyclerChangedItemPosition(lastRecyclerView, recyclerView, lastItemPos, itemPos, lastRecyclerPos, recyclerPos); boolean isChanged = dragListener.onRecyclerChanged(lastRecyclerView, recyclerView, lastItemPos, itemPos, lastRecyclerPos, recyclerPos); if (!isChanged) { return result; } // System.out.println("find_parent:" + lastRecyclerPos + "-" + verticalPos); //在切换recycle view并且触摸到子recycle view的item的时候才真正去改变值 lastRecyclerPos = recyclerPos; lastRecyclerView = recyclerView; //因为切换父控件,所以需要重置为当前itemPos,不然上个的最后位置有可能超过当前的大小抛出错误 lastItemPos = itemPos; } } if (itemPos == NONE) { return result; } if (isItemNeedChange(itemView, lastItemPos, itemPos, itemY)) { itemPos = dragListener.onItemChangedPosition(recyclerView, lastItemPos, itemPos, lastRecyclerPos); boolean isChanged = dragListener.onItemChanged(recyclerView, lastItemPos, itemPos, lastRecyclerPos); if (!isChanged) { return result; } scrollToRightPositionWhenItemChanged(recyclerView, itemView, itemPos); // System.out.println("find:" + lastRecyclerPos + "-" + recyclerPos + "======" + lastItemPos + "-" + itemPos); lastItemPos = itemPos; } return result; } /** * 当item位置变换,滚动recycler到正确的位置 * TODO: 2017/2/21 0021 整理更优雅的写法 还有scrollToPosition(0)是否必要? */ private void scrollToRightPositionWhenItemChanged(RecyclerView recyclerView, View itemView, int itemPos) { final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof ItemTouchHelper.ViewDropHandler) { OrientationHelper helper = OrientationHelper.createVerticalHelper(layoutManager); int start = helper.getDecoratedStart(itemView); int end = helper.getDecoratedEnd(itemView); ((LinearLayoutManager) layoutManager).scrollToPositionWithOffset( itemPos, lastItemPos > itemPos ? start : end - itemViewHeight); // System.out.println(lastItemPos + "-" + childPos + "OrientationHelperOrientationHelper:" // + height + "==" + itemViewHeight + "=||=" + start + "===" + end + "||||||" + myStart + "===" + itemTargetView.getHeight() ); } if (lastItemPos == 0 || itemPos == 0) { recyclerView.scrollToPosition(0); } } /** * 获取当前点击的位置在RecyclerView内部的坐标 (Y坐标范围0+padding到height-padding)? */ private float[] getInsideLocation(RecyclerView recyclerView, float touchRawX, float touchRawY) { float[] result = new float[2]; int[] location = new int[2]; recyclerView.getLocationOnScreen(location); result[0] = touchRawX - location[0]; result[1] = touchRawY - location[1]; // System.out.println("getInsideLocation:" + result[0] + "-" + result[1] + "==" + "X:" + touchRawX + "Y:" + touchRawY); result[0] = result[0] / dragListener.getScale(); result[1] = result[1] / dragListener.getScale(); int minY = recyclerView.getPaddingTop(); int maxY = recyclerView.getHeight() - recyclerView.getPaddingBottom(); result[1] = Math.min(Math.max(result[1], minY), maxY); return result; } /** * 当在两个RecyclerView中切换时,计算目标RecyclerView中Child位置 (若目标RecyclerView为空返回0) */ private int calcItemPositionWhenChangeRecycler(RecyclerView verticalRecycler, View itemView, int itemPos, float childX, float childY) { //若目标RecyclerView为空返回0 if (itemPos == NONE && verticalRecycler.getAdapter().getItemCount() == 0) { itemPos = 0; } else if (itemView != null) { int top = itemView.getTop(); if ((top + itemView.getHeight() * dragListener.getMoveLimit()) < childY) { //证明滑动位置在targetView的下部,所以要插入到当前位置+1 itemPos++; } } return itemPos; } /** * 从view中获取需要操作的RecyclerView * * @param view View */ protected RecyclerView findRecyclerView(View view) { if (view == null) { return null; } if (view instanceof RecyclerView) { return (RecyclerView) view; } else if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; RecyclerView recyclerView; for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) { recyclerView = findRecyclerView(viewGroup.getChildAt(i)); if (recyclerView != null) { return recyclerView; } } } return null; } /** * 滚动Runnable,为了可持续滚动 */ private final Runnable scrollRunnable = new Runnable() { @Override public void run() { float[] horLocation = getInsideLocation(horizontalRecycler, lastTouchRawX, lastTouchRawY); float[] verLocation = getInsideLocation(lastRecyclerView, lastTouchRawX, lastTouchRawY); boolean isHorizontalScroll = scrollIfNecessary(horizontalRecycler, (int) horLocation[0], (int) horLocation[1]); boolean isVerticalScroll = scrollIfNecessary(lastRecyclerView, (int) verLocation[0], (int) verLocation[1]); if (isDrag && (isHorizontalScroll || isVerticalScroll)) { //it might be lost during scrolling moveIfNecessary(lastTouchRawX, lastTouchRawY); lastRecyclerView.removeCallbacks(scrollRunnable); ViewCompat.postOnAnimation(lastRecyclerView, this); } } }; /** * 当用户滚动到边缘的时候,计算是否需要滚动 */ private boolean scrollIfNecessary(RecyclerView recyclerView, int curX, int curY) { if (!isDrag) { return false; } RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); int scrollX = 0; int scrollY = 0; if (lm.canScrollHorizontally()) { scrollX = dragListener.calcHorizontalScrollDistance(recyclerView, curX, curY); } if (lm.canScrollVertically()) { scrollY = dragListener.calcVerticalScrollDistance(recyclerView, curX, curY); } // System.out.println("scroll:::::" + scrollY + "=" + recyclerView.getScrollY() + "curY::" + curY); // System.out.println("scroll:::::" + scrollX + "=" + recyclerView.getScrollX() + "curX::" + curX); if (scrollX != 0 || scrollY != 0) { recyclerView.scrollBy(scrollX, scrollY); } return scrollX != 0 || scrollY != 0; } /** * 获取需要move的目标Position,即toPosition * * @return NONE为找不到 */ private int getTargetItemPos(View itemTargetView, float childY, int lastRecyclerPos, int currRecyclerPos) { int itemPos = getPositionByChildView(itemTargetView); if (itemPos == NONE) { return itemPos; } if ((itemPos != lastItemPos || lastRecyclerPos != currRecyclerPos)) { return itemPos; } return NONE; } /** * 两个Item是否需要move */ private boolean isItemNeedChange(View itemView, int lastItemPos, int itemPos, float itemY) { if (itemView == null || lastItemPos == NONE || itemPos == NONE || lastItemPos == itemPos) { return false; } int top = itemView.getTop(); int moveLimit = (int) (top + itemView.getHeight() * dragListener.getMoveLimit()); // System.out.println("isNeedRemove-top:" + top + "==height:" + itemView.getHeight() // + "==touchY:" + itemY + "moveLimit==" + moveLimit + "lastItemPos > itemPos===" + (lastItemPos > itemPos)); if (lastItemPos > itemPos) { // return touchY < moveLimit && top >= 0; return itemY < moveLimit; } else { return itemY > moveLimit; } } /** * touch的位置是否为当前view,防止两个item切换时的抖动问题 */ private boolean isCurrPosition(float childY, View itemView) { // System.out.println("isCurrPosition:" + (childY > itemView.getTop() && childY < itemView.getBottom())); return childY >= itemView.getTop() && childY <= itemView.getBottom(); } /** * 查找当前view在RecyclerView中的位置 没有返回NONE */ private int getPositionByChildView(View itemView) { if (itemView == null) { return NONE; } try { return ((RecyclerView.LayoutParams) itemView.getLayoutParams()).getViewAdapterPosition(); } catch (Exception e) { e.printStackTrace(); } return NONE; } /** * 是否真正切换了RecyclerView * 需要注意这里把没有切换后没有touch到Item当成不是真正切换 */ private boolean isChangeRecyclerView(int lastRecyclerPos, int currRecyclerPos) { return lastRecyclerPos != currRecyclerPos && lastRecyclerPos != NONE && currRecyclerPos != NONE; } /** * 是否为初次选中RecycleView */ private boolean isSelectedRecyclerView(int lastRecyclerPos, int currRecyclerPos) { return lastRecyclerPos == NONE && currRecyclerPos != NONE; } /** * 是否为第一次选中子ItemView */ private boolean isSelectedChildView(int lastChildPos, int currChildPos) { return lastChildPos == NONE && currChildPos != NONE; } static class EmptyDragListener extends OnItemDragListener { } }