一、概述
ItemTouchHelper
在RecyclerView
的整个体系中,负责监听Item
的手势操作,我们通过给它设置一个继承于ItemTouchHelper.Callback
的子类,在其中处理Item
的UI
变化,就可以完成侧滑删除、拖动排序等操作,下面,我们分以下几部介绍:
API
解析- 实战
- 采用默认动画
- 自定义侧滑删除动画
二、API
分析
对于Item
的手势操作分为两种:侧滑和拖动,如果需要支持这两种,那么需要给ItemTouchHelper
传入一个ItemTouchHelper.Callback
的子类,并把ItemTouchHelper
和RecyclerView
关联起来,下面,我们先来介绍一下ItemTouchHelper.Callback
个回调方法的含义:
控制相关
public boolean isLongPressDragEnabled()
是否可以通过长按来触发拖动操作,默认返回true
,如果返回false
,那么可以通过startDrag(ViewHolder)
方法来触发某个特定Item
的拖动的机制。public boolean isItemViewSwipeEnabled()
是否可以对每个Item
进行侧滑。public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder)
返回对于某个ViewHolder
可以移动的方向,可选的值有UP/DOWN/LEFT/RIGHT/START/END
。对于纵向排列的线性布局而言,如果要支持上下拖动排序,那么就要标志位中就要包含UP&DOWN
,而如果需要支持左滑删除,那么标志位中就要包含LEFT
。
结果相关
public abstract boolean onMove(RecyclerView recyclerView, ViewHolder viewHolder, ViewHolder target)
当某个被拖动的Item
被从旧位置拖动到了新位置后回调,如果返回true
,那么ItemTouchHelper
就认为viewHolder
已经被移动到了target
在Adapter
中的位置。public abstract void onSwiped(ViewHolder viewHolder, int direction)
当某个Item
被滑动到消失时回调,direction
表示滑动的方向。
状态相关
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)
当Item
的状态发生改变时,回调该方法,actionState
的取值有ACTION_STATE_IDLE/ACTION_STATE_SWIPE/ACTION_STATE_DRAG
。public void clearView(RecyclerView recyclerView, ViewHolder viewHolder)
标志着用户对于某个Item
的操作并且Item
的动画结束,此时我们应该恢复它的状态,以保证它被重新使用的时候能正确地展现。public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
Canvas
:绘制RecyclerView
的Canvas
dx, dy
:偏移。actionState
:拖拽还是侧滑,对应ACTION_STATE_DRAG
和ACTION_STATE_SWIPE
。isCurrentlyActive
为true
表示这个Item
正在被用户所控制,false
则表示它仅仅是在回到原本状态的动画过程当中。
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)
和上面类似,只不过它是绘制在Item
之上。
三、实战
3.1 使用系统默认效果
如果我们希望使用系统默认的效果,那么只需要做以下几步:
- 继承于
ItemTouchHelper.Callback
编写自己的回调类,并在拖动和侧滑操作完成之后更新数据:public class SimpleItemTouchHelper extends ItemTouchHelper.Callback { private ItemTouchAdapter mAdapter; public SimpleItemTouchHelper(ItemTouchAdapter adapter) { mAdapter = adapter; } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { Log.d("SimpleItemTouchHelper", "onSwiped, onMove, source=" + viewHolder.getAdapterPosition() + ",target=" + target.getAdapterPosition()); mAdapter.onItemDragged(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { Log.d("SimpleItemTouchHelper", "onSwiped, direction=" + direction); mAdapter.onItemSwiped(viewHolder.getAdapterPosition()); } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { return makeMovementFlags(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT); } }
- 编写数据操作的代码:
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.NormalViewHolder> implements ItemTouchAdapter { //...... @Override public void onItemDragged(int from, int to) { Collections.swap(mTitles, from, to); notifyItemMoved(from, to); } @Override public void onItemSwiped(int position) { mTitles.remove(position); notifyItemRemoved(position); } }
- 把
ItemTouchHelper.Callback
和RecyclerView
关联起来,看注释中的1,2,3
步:private void init() { List<String> titles = new ArrayList<>(); for (int i = 0; i < 20; i++) { titles.add(String.valueOf(i)); } LinearLayoutManager layoutManager = new LinearLayoutManager(this); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content); recyclerView.setLayoutManager(layoutManager); NormalAdapter adapter = new NormalAdapter(titles); //1.自定义的ItemTouchHeloer.Callback SimpleItemTouchHelper simpleItemTouchHelper = new SimpleItemTouchHelper(adapter); //2.利用这个Callback构造ItemTouchHelper ItemTouchHelper itemTouchHelper = new ItemTouchHelper(simpleItemTouchHelper); //3.把ItemTouchHelper和RecyclerView关联起来. itemTouchHelper.attachToRecyclerView(recyclerView); recyclerView.setAdapter(adapter); }
下面就是最终的效果:
3.2 自定义侧滑删除动画
当我们需要自定侧滑删除动画时,那么需要重写onChildDraw
或者onChildDrawOver
方法,在其中监听滑动距离的变化,并根据它来实时改变viewHolder
中的UI
,首先看效果:
- 首先,我们需要重写
Item
的布局,它包含两层,顶层是普通状态的标题文案,而底层则是蓝色底的删除提示:<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="66dp"> <!-- 删除提示 --> <LinearLayout android:id="@+id/ll_delete" android:orientation="vertical" android:gravity="center" android:layout_gravity="end" android:background="@android:color/holo_blue_dark" android:paddingLeft="10dp" android:paddingRight="10dp" android:layout_width="wrap_content" android:layout_height="match_parent"> <ImageView android:src="@android:drawable/ic_input_delete" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:text="delete" android:textColor="@android:color/white" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> <!-- 普通文案 --> <TextView android:id="@+id/tv_title" android:gravity="center" android:background="@android:color/white" android:layout_width="match_parent" android:layout_height="match_parent"/> </FrameLayout>
接着,我们需要重写
ItemTouchHelper.Callback
:public class AdvancedItemTouchHelper extends ItemTouchHelper.Callback { private ItemTouchAdapter mAdapter; public AdvancedItemTouchHelper(ItemTouchAdapter itemTouchAdapter) { mAdapter = itemTouchAdapter; } @Override public boolean isLongPressDragEnabled() { return false; } @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { return makeMovementFlags(0, ItemTouchHelper.LEFT); } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { mAdapter.onItemSwiped(viewHolder.getAdapterPosition()); } @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { return false; } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); ((NormalAdapter.NormalViewHolder) viewHolder).mTv.setTranslationX(0); } @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { NormalAdapter.NormalViewHolder mViewHolder = (NormalAdapter.NormalViewHolder) viewHolder; int deleteWidth = mViewHolder.mDeleteLayout.getWidth(); float fraction = deleteWidth / (float) mViewHolder.itemView.getWidth(); mViewHolder.mTv.setTranslationX(dX * fraction); } }
这里面有几点需要注意:
- 为了让
Item
支持左滑删除,我们需要在getMovementFlags
中返回ItemTouchHelper.LEFT
标志位。 - 在
onChildDraw
当中,通过传入的dX
动态改变了普通文案的translationX
,使得底层的删除提示能够漏出。 - 在侧滑操作完成之后,通过
Adapter
来删除数据。 - 在
clearView
中,需要把mTv
重置为初始的状态。
最后,我们按照前面的方法,把它和RecyclerView
关联起来:
private void init() {
List<String> titles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
titles.add("Item " + String.valueOf(i));
}
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
recyclerView.setLayoutManager(layoutManager);
NormalAdapter adapter = new NormalAdapter(titles);
AdvancedItemTouchHelper advancedItemTouchHelper = new AdvancedItemTouchHelper(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(advancedItemTouchHelper);
itemTouchHelper.attachToRecyclerView(recyclerView);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
}
四、小结
自定义RecyclerView
的手势动画,关键是要理解ItemTouchHelper.Callback
中各回调函数的含义,再通过回调函数中传入的数值来动态改变viewHolder
中保存的itemView
以及其子View
的展现形式,就可以做出各种绚丽的效果。
时间: 2024-09-29 16:00:08