場景:
近期做的TODO APP需要在主頁添加一個功能,就是可以左滑刪除,右滑完成。看了一下當前其他人做的例如仿探探式的效果,核心功能基本一樣,但是和我預想的還是有少量區別,於是干脆自己重頭學一遍如何自定義layout並且實現我所期望的功能。順便寫個傻瓜式教程,總結一下前人的經驗。
需要:
Android Studio, RecyclerView
方法:
1. ItemTouchHelper
首先來看一下這個類的官方定義:
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
It works with a RecyclerView and a Callback class, which configures what type of interactions are enabled and also receives events when user performs these actions.
Depending on which functionality you support, you should override onMove(RecyclerView, ViewHolder, ViewHolder)
and / or onSwiped(ViewHolder, int)
.
This class is designed to work with any LayoutManager but for certain situations, it can be optimized for your custom LayoutManager by extending methods in the ItemTouchHelper.Callback
class or implementing ItemTouchHelper.ViewDropHandler
interface in your LayoutManager.
By default, ItemTouchHelper moves the items' translateX/Y properties to reposition them. You can customize these behaviors by overriding onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)
or onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)
.
Most of the time you only need to override onChildDraw
.
可以看出,這是一個可以支持RecyclerView實現用於滑動刪除,拖拽效果的類。
他和recyclerview配合工作以確定什么樣的交互方式可以使用,並且創建回調來對這些事件來進行接收
我們可以復寫onMove和onSwiped來定義它的功能
這個類通常需要配合LayoutManager來使用
ItemHelper會將item重新移動,可以通過復寫onChildDraw和onChildDrawOver來重新定義其行為
ok,我們直接用一下試試看
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); MyAdapter adapter = new MyAdapter(list); recyclerView.setAdapter(adapter); ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() { @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { return 0; } @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { return false; } @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { } }); helper.attachToRecyclerView(recyclerView);
運行之后,並沒有任何效果,顯然,這里我們需要定義之前說的回調來定義事件的接收,直接來看一下這個ItemTouchHelper.Callback的定義:
This class is the contract between ItemTouchHelper and your application. It lets you control which touch behaviors are enabled per each ViewHolder and also receive callbacks when user performs these actions.
To control which actions user can take on each view, you should override getMovementFlags(RecyclerView, ViewHolder)
and return appropriate set of direction flags. (LEFT
, RIGHT
, START
, END
, UP
, DOWN
). You can usemakeMovementFlags(int, int)
to easily construct it. Alternatively, you can use ItemTouchHelper.SimpleCallback
.
If user drags an item, ItemTouchHelper will call onMove(recyclerView, dragged, target)
. Upon receiving this callback, you should move the item from the old position (dragged.getAdapterPosition()
) to new position (target.getAdapterPosition()
) in your adapter and also call notifyItemMoved(int, int)
. To control where a View can be dropped, you can override canDropOver(RecyclerView, ViewHolder, ViewHolder)
. When a dragging View overlaps multiple other views, Callback chooses the closest View with which dragged View might have changed positions. Although this approach works for many use cases, if you have a custom LayoutManager, you can overridechooseDropTarget(ViewHolder, java.util.List, int, int)
to select a custom drop target.
When a View is swiped, ItemTouchHelper animates it until it goes out of bounds, then calls onSwiped(ViewHolder, int)
. At this point, you should update your adapter (e.g. remove the item) and call related Adapter#notify event.
這個包含三個主要函數:
getMovementFlags(): 用來定義哪些方向可以用,例如上下左右,用來處理拖拽,START和END用來處理側滑
onMove() 在這里需要定義item是怎么移動的,從哪移動到哪,並且可以修改被移動所遮擋的其他view的行為,這里主要實現的是拖拽
onSwipe() 同理,這里實現滑動
ok,我們來具體實現上面各個方法:
getMovementFlags()
int makeMovementFlags (int dragFlags, int swipeFlags)
Convenience method to create movement flags.
For instance, if you want to let your items be drag & dropped vertically and swiped left to be dismissed, you can call this method with: makeMovementFlags(UP | DOWN, LEFT);
這里我們使用它推薦的makeMovementFlags(int dragFlag,int swipeFlag)來構建返回結果,這里兩個參數是由二進制表示的,轉化成二進制后各位01表示是否有這個方向, 因此我們對這些方向做 或 ( | ) 操作來構成參數。
onMove()
boolean onMove (RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target)
Called when ItemTouchHelper wants to move the dragged item from its old position to the new position.
If this method returns true, ItemTouchHelper assumes viewHolder
has been moved to the adapter position of target
ViewHolder (ViewHolder#getAdapterPosition()
).
If you don't support drag & drop, this method will never be called.
viewHolder就是你正在拖拽的view,target就是你移動時被覆蓋的view,想要實現拖拽效果,我們必須要更新從拖拽view到覆蓋view之間所有item的位置,具體邏輯請看示例
onSwipe()
void onSwiped (RecyclerView.ViewHolder viewHolder, int direction)
Called when a ViewHolder is swiped by the user.
If you are returning relative directions (START
, END
) from the getMovementFlags(RecyclerView, ViewHolder)
method, this method will also use relative directions. Otherwise, it will use absolute directions.
If you don't support swiping, this method will never be called.
ItemTouchHelper will keep a reference to the View until it is detached from RecyclerView. As soon as it is detached, ItemTouchHelper will call clearView(RecyclerView, ViewHolder)
.
這里我們直接找到當前的item,然后刪掉它,接着notify一下就行了,當然我們可以定義方向來判斷到底是左滑還是右滑
完整代碼如下:
這里由於需要傳入adapter的信息,因此我們重新創建個類extend ItemTouchHelper.Callback

package com.wjk32.myapplication; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.TextView; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class MainActivity extends AppCompatActivity { public static final String TAG="123"; public static final String TAG2="MainActivity"; String s="MainAc"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, TAG2+" onCreate"); setContentView(R.layout.activity_main); List<String> list = new ArrayList<>(); for(int i=0;i<100;i++) { list.add(""+i); } RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false)); final MyAdapter adapter = new MyAdapter(list); recyclerView.setAdapter(adapter); ItemTouchHelper helper = new ItemTouchHelper(new MyItemTouchCallback(adapter)); helper.attachToRecyclerView(recyclerView); } public class MyItemTouchCallback extends ItemTouchHelper.Callback { private final MyAdapter adapter; public MyItemTouchCallback(MyAdapter adapter) { this.adapter = adapter; } @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { return makeMovementFlags(ItemTouchHelper.DOWN|ItemTouchHelper.RIGHT|ItemTouchHelper.UP|ItemTouchHelper.LEFT, ItemTouchHelper.START|ItemTouchHelper.END); } @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { int startPosition = viewHolder.getAdapterPosition(); int endPosition = target.getAdapterPosition(); //the item to swap int index=startPosition; //drag direction int dir=startPosition-endPosition>0?-1:1; while(index<endPosition){ Collections.swap(adapter.getmItemLists(), index,index+dir ); index+=dir; } recyclerView.getAdapter().notifyItemMoved(startPosition, endPosition); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { int position = viewHolder.getAdapterPosition(); if (direction == ItemTouchHelper.END||direction == ItemTouchHelper.START) { adapter.getmItemLists().remove(position); adapter.notifyItemRemoved(position); } } } public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> { private List<String> mItemLists; public class MyViewHolder extends RecyclerView.ViewHolder { public TextView textView; public MyViewHolder(TextView v) { super(v); textView = v; } } public List<String> getmItemLists() { return mItemLists; } public MyAdapter(List<String> myDataset) { mItemLists = myDataset; } @Override public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { TextView v = (TextView) LayoutInflater.from(parent.getContext()) .inflate(R.layout.item, parent, false); MyViewHolder vh = new MyViewHolder(v); return vh; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.textView.setText(mItemLists.get(position)); } @Override public int getItemCount() { return mItemLists.size(); } } }
好了,到現在我們已經實現了基本的一些功能。
接下來我們將進行一些深入的探索。
參考
1. https://yuqirong.me/2017/02/03/RecyclerView%E5%AE%9E%E7%8E%B0%E6%8B%96%E6%8B%BD%E6%8E%92%E5%BA%8F%E5%92%8C%E4%BE%A7%E6%BB%91%E5%88%A0%E9%99%A4/