在 Android TV 中如何实现 RecyclerView item 间的位置互换?

这里主要利用 RecyclerViewItemAnimator 进行实现交互效果,以及利用 AdapternotifyItemMoved 方法实现数据更新。

因为是 Android TV,主要的交互方式是遥控器,所以无法利用 ItemTouchHelper 进行协助实现位置交互的功能,那主要从以下几步进行:

  • 重写 RecyclerViewfocusSearch 方法,focus 变化时触发位置互换;
  • Adapter 中调用 notifyItemMoved 实现数据更新;

focusSearch 方法修改

public View focusSearch(View focused, int direction) {
    Adapter adapter = getAdapter();

    View itemView = findContainingItemView(focused);
    if (itemView != null && isSwapMode()) {
        return swapItemsIfNeeded(itemView, direction);
    }
    return super.focusSearch(focused, direction);
}

private View swapItemsIfNeeded(View focused, int direction) {
    int position = getChildAdapterPosition(focused);
    ItemAnimator animator = getItemAnimator();
    if ((animator == null || !animator.isRunning())
            && canMoveInDirection(position, direction)) {
        int spanCount = getSpanCount();
        switch (direction) {
            case FOCUS_LEFT:
                moveItem(position, position - 1);
                break;
            case FOCUS_UP:
                moveItem(position, position - spanCount);
                break;
            case FOCUS_RIGHT:
                moveItem(position, position + 1);
                break;
            case FOCUS_DOWN:
                moveItem(position, position + spanCount);
                break;
            default:
                break;
        }
    }
    return focused;
}

private boolean canMoveInDirection(int position, int direction) {
    int spanCount = getSpanCount();
    LayoutManager m = getLayoutManager();
    assert m != null;

    if (direction == FOCUS_LEFT) {
        return position % spanCount > 0;
    } else if (direction == FOCUS_UP) {
        return position - spanCount > 0;
    } else if (direction == FOCUS_RIGHT) {
        return !(position % spanCount >= (spanCount - 1) ||
                         position >= m.getItemCount() - 1);
    } else if (direction == FOCUS_DOWN) {
        return position + spanCount <= m.getItemCount() - 1;
    }
    return false;
}

private int getSpanCount() {
    int spanCount = 1;
    LayoutManager m = getLayoutManager();
    if (m instanceof GridLayoutManager) {
        spanCount = ((GridLayoutManager) m).getSpanCount();
    }
    return spanCount;
}

private void moveItem(int fromPosition, int toPosition) {
    Adapter adapter = getAdapter();
    if (adapter instanceof XXXAdapter) {
        ((XXXAdapter) adapter).moveItem(fromPosition, toPosition);
    }
}

其中 XXXAdapterAdapter 实现类,主要用于数据更新

XXXAdapter 实现数据更新

public void moveItem(int from, int to) {
   Info info = data.remove(from);
   data.add(to, info);

   // 该方法会触发到 SimpleItemAnimator 的 animateMove 方法
   // 利用 animateMove 实现平滑的移动效果
   // RecyclerView.ItemAnimator 的实现不在这里详细阐述,有兴趣可以去看 DefaultItemAnimator 的实现
   notifyItemMoved(from, to);

}

通过调用 notifyItemMoved 以及和 RecyclerView.ItemAnimator 的结合实现,达到平滑位置交换的效果。

参考

stackoverflow: https://stackoverflow.com/questions/54236100/how-do-i-move-grid-items-on-android-tv