在RecyclerView中拖放。 第2部分:拖放控制器,网格和自定义动画

在RecyclerView中拖放。第2部分:拖放控制器,网格和自定义动画


第一部分中,我们研究了ItemTouchHelperItemTouchHelper.Callback的实现,该实现向RecyclerView添加了基本的拖放滑动到关闭功能 。 在本文中,我们将通过添加对以网格形式布置元素,拖放控制器,列表项的选择以及自定义滑动动画的支持,继续上一版本的工作。


元素网格


拖放控制器


创建支持拖放的列表时,它们通常实现通过触摸来拖放项目的功能。 这有助于“编辑模式”中列表的清晰性和易用性,并且也材料准则建议 。 在我们的示例中添加拖放控制器非常简单。


拖放控制器


首先,更新元素的layoutitem_main.xml )。


 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item" android:layout_width="match_parent" android:layout_height="?listPreferredItemHeight" android:clickable="true" android:focusable="true" android:foreground="?selectableItemBackground"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="16dp" android:textAppearance="?android:attr/textAppearanceMedium" /> <ImageView android:id="@+id/handle" android:layout_width="?listPreferredItemHeight" android:layout_height="match_parent" android:layout_gravity="center_vertical|right" android:scaleType="center" android:src="@drawable/ic_reorder_grey_500_24dp" /> </FrameLayout> 

可以在Material Design图标中找到用于拖动控制器的图像,并使用Android Studio中方便的图标生成器插件将其添加到项目中。


如前一篇文章中简要提到的,您可以使用ItemTouchHelper.startDrag(ViewHolder)以编程方式开始拖放操作。 因此,我们要做的就是通过添加拖动控制器来更新ViewHolder并设置一个简单的触摸处理程序,该处理程序将调用startDrag()


我们将需要一个接口来沿链传输事件:


 public interface OnStartDragListener { /** * Called when a view is requesting a start of a drag. * * @param viewHolder The holder of the view to drag. */ void onStartDrag(RecyclerView.ViewHolder viewHolder); } 

然后在ItemViewHolder为拖动控制器定义一个ImageView


 public final ImageView handleView; public ItemViewHolder(View itemView) { super(itemView); // ... handleView = (ImageView) itemView.findViewById(R.id.handle); } 

并更新RecyclerListAdapter


 private final OnStartDragListener mDragStartListener; public RecyclerListAdapter(OnStartDragListener dragStartListener) { mDragStartListener = dragStartListener; // ... } @Override public void onBindViewHolder(final ItemViewHolder holder, int position) { // ... holder.handleView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) { mDragStartListener.onStartDrag(holder); } return false; } }); } 

RecyclerListAdapter现在应该看起来像这样


剩下要做的就是将OnStartDragListener添加到片段中:


 public class RecyclerListFragment extends Fragment implements OnStartDragListener { // ... @Override public void onViewCreated(View view, Bundle icicle) { super.onViewCreated(view, icicle); RecyclerListAdapter a = new RecyclerListAdapter(this); // ... } @Override public void onStartDrag(RecyclerView.ViewHolder viewHolder) { mItemTouchHelper.startDrag(viewHolder); } } 

RecyclerListFragment现在应该看起来像这样 。 现在,当您启动应用程序时,可以通过触摸控制器开始拖放。


拖动列表项


突出显示列表项


现在,在我们的示例中,没有视觉上的指示表明正在拖动该项目。 显然,这不是应该的,但是很容易解决。 使用ItemTouchHelper可以使用标准项目突出显示效果。 在Lollipop和更高版本的Android上,背光在与元素交互的过程中“散布”在元素上; 在早期版本中,该元素只是将其颜色更改为深色。


要在我们的示例中实现此目的,只需FrameLayout元素的根FrameLayout添加背景( background属性),或在RecyclerListAdapter.ItemViewHolder的构造函数中进行设置。 它看起来像这样:


元素选择


它看起来很酷,但是您可能想要控制更多。 一种方法是允许ViewHolder处理元素的状态更改。 为此, ItemTouchHelper.Callback提供了另外两种方法:


  • 每当元素的状态更改为拖动ACTION_STATE_DRAG )或滑动ACTION_STATE_SWIPE )时,都会调用onSelectedChanged(ViewHolder, int) )。 这是将view组件的状态更改为active的理想位置。
  • 拖放view组件完成时,以及滑动完成时clearView(RecyclerView, ViewHolder)调用clearView(RecyclerView, ViewHolder)ACTION_STATE_IDLE )。 在这里,通常会恢复view组件的初始状态。

现在,让我们将它们放在一起。


首先,创建一个ViewHolders将实现的接口:


 /** * Notifies a View Holder of relevant callbacks from * {@link ItemTouchHelper.Callback}. */ public interface ItemTouchHelperViewHolder { /** * Called when the {@link ItemTouchHelper} first registers an * item as being moved or swiped. * Implementations should update the item view to indicate * it's active state. */ void onItemSelected(); /** * Called when the {@link ItemTouchHelper} has completed the * move or swipe, and the active item state should be cleared. */ void onItemClear(); } 

然后,在SimpleItemTouchHelperCallback实现适当的方法:


 @Override public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) { // We only want the active item if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) { if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemSelected(); } } super.onSelectedChanged(viewHolder, actionState); } @Override public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); if (viewHolder instanceof ItemTouchHelperViewHolder) { ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder; itemViewHolder.onItemClear(); } } 

现在剩下的就是RecyclerListAdapter.ItemViewHolder实现ItemTouchHelperViewHolder


 public class ItemViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder { // ... @Override public void onItemSelected() { itemView.setBackgroundColor(Color.LTGRAY); } @Override public void onItemClear() { itemView.setBackgroundColor(0); } } 

在此示例中,我们仅在元素处于活动状态时添加灰色背景,然后将其删除。 如果您的ItemTouchHelper和适配器紧密相连,则可以轻松地放弃此设置,而直接在ItemTouchHelper.Callback切换view组件的状态。


篮网


如果现在尝试使用GridLayoutManager ,则会看到它无法正常工作。 原因和解决方案很简单:我们必须告诉ItemTouchHelper我们希望支持左右拖动项目。 之前在SimpleItemTouchHelperCallback我们已经指定了:


 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; return makeMovementFlags(dragFlags, swipeFlags); } 

支持网格的唯一更改是添加适当的标志:


 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; 

但是,对于网格元素,“ 轻扫以消除”并不是很自然的行为,因此swipeFlags最有可能被swipeFlags


 @Override public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT; int swipeFlags = 0; return makeMovementFlags(dragFlags, swipeFlags); } 

要查看GridLayoutManager的工作示例,请参阅RecyclerGridFragment 。 这是启动时的样子:


网格元素


自定义滑动动画


ItemTouchHelper.Callback提供了一种非常方便的方法,可以在拖动或扫动时完全控制动画。 由于ItemTouchHelperRecyclerView.ItemDecoration ,因此我们可以以类似方式干预呈现view组件的过程。 在下一部分中,我们将更详细地研究这个问题,但是现在,让我们看一个覆盖默认滑动动画以显示线性消失的简单示例。


 @Override public void onChildDraw(Canvas c, RecyclerView recyclerView, ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { float width = (float) viewHolder.itemView.getWidth(); float alpha = 1.0f - Math.abs(dX) / width; viewHolder.itemView.setAlpha(alpha); viewHolder.itemView.setTranslationX(dX); } else { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } 

dXdY是相对于所选view组件的当前偏移,其中:


  • -1.0f是从右到左的完整滑动(从ItemTouchHelper.ENDItemTouchHelper.START
  • 从左到右(从ItemTouchHelper.STARTItemTouchHelper.END )完全滑动1.0f。

重要的是要为actionState的任何actionState调用super来触发默认动画。


在下一部分中,我们将考虑一个示例,在该示例中,我们将在拖放时控制元素的绘制。


结论


实际上,设置ItemTouchHelper很有趣。 为了不增加本文的数量,我将其分为几部分。


源代码


请参阅Android-ItemTouchHelper-Demo GitHub存储库上的本系列文章的完整代码。 本文介绍了从ef8f149d164fba的提交。


←在RecyclerView中拖动和滑动。 第1部分:ItemTouchHelper

Source: https://habr.com/ru/post/zh-CN428419/


All Articles