Android adding RecyclerView swipe to delete and undo
Bài đăng này đã không được cập nhật trong 7 năm
1. Cơ chế hoạt động RecyclerView swipe - ItemTouchHelper
- Với sự giúp đỡ của lớp ItemTouchHelper ta có thể thêm action swipe để xóa 1 item trong list dữ liệu. Swipe row để xóa row khỏi RecyclerView, nhưng sẽ không refresh lại dữ liệu. Khi đó RecyclerView sẽ hiển thị empty row tại row mình vừa swipe. Để fix trường hợp này ta cần xóa item khỏi dataset và notifyDataSetChanged.
- ItemTouchHelper.SimpleCallback bao gồm các phương thức onMove(), onChileDraw(), onSwiped() khi row được swipe. Hiển thị background view khi đang xóa item từ adapter có thể thực hiện bởi hàm callback trên
1.1. Xác định swipe direction
- Swipe direction được quyết định khi tạo SimpleCallback(). Trong trường hợp của ta sẽ chỉ dùng Left->Right direction. Nếu muốn thêm hướng khác ta sẽ sử dụng operator |
new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT)
1.2. Tạo Layout
- Layout sẽ bao gồm background view và forground view. Foreground view luôn hiển thị trong RecyclerView, và khi action swipe được thực hiện thì background sẽ hiển thị.
2. Tạo project
- Tạo project mới bằng AndroidStudio.
- Chi tiết project có thể down ở cuối bài viết, ở đây sẽ chỉ giới thiệu qua về phần code chính của project
a. Application tạo một class sau
import android.app.Application;
import android.text.TextUtils;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;
public class MyApplication extends Application {
public static final String TAG = MyApplication.class
.getSimpleName();
private RequestQueue mRequestQueue;
private static MyApplication mInstance;
@Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public static synchronized MyApplication getInstance() {
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
mRequestQueue = Volley.newRequestQueue(getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req, String tag) {
// set the default tag if tag is empty
req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
getRequestQueue().add(req);
}
public <T> void addToRequestQueue(Request<T> req) {
req.setTag(TAG);
getRequestQueue().add(req);
}
public void cancelPendingRequests(Object tag) {
if (mRequestQueue != null) {
mRequestQueue.cancelAll(tag);
}
}
}
sau đó update file manifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
b. update layout cho activity
activity_main
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mac.recyclerviewswipetodelete.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
content_main
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.mac.recyclerviewswipetodelete.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
c. Tạo model class Item
Item
public class Item {
int id;
String name;
String description;
double price;
String thumbnail;
public Item(){}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
}
d. Tạo layout cho từng row recyclerview
cart_list_item
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/view_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_row_background">
<ImageView
android:id="@+id/delete_icon"
android:layout_width="@dimen/ic_delete"
android:layout_height="@dimen/ic_delete"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:src="@drawable/ic_delete_white_24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="@dimen/padd_10"
android:layout_toLeftOf="@id/delete_icon"
android:text="@string/delete"
android:textColor="@android:color/white"
android:textSize="13sp" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/view_foreground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="@dimen/padd_10">
<ImageView
android:id="@+id/thumbnail"
android:layout_width="@dimen/thumbnail"
android:layout_height="@dimen/thumbnail"
android:layout_marginRight="@dimen/activity_padding_horizontal"
android:scaleType="centerCrop" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/thumbnail"
android:ellipsize="end"
android:fontFamily="sans-serif"
android:maxLines="1"
android:textColor="@color/item_name"
android:textSize="17sp" />
<TextView
android:id="@+id/description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/name"
android:layout_marginTop="5dp"
android:layout_toRightOf="@id/thumbnail"
android:textColor="@color/desciption"
android:textSize="12sp" />
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toRightOf="@id/thumbnail"
android:textColor="@color/colorAccent"
android:textStyle="bold" />
</RelativeLayout>
</FrameLayout>
e. Tạo adapter cho recyclerview
CartListAdapter
public class CartListAdapter extends RecyclerView.Adapter<CartListAdapter.MyViewHolder> {
private Context context;
private List<Item> cartList;
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView name, description, price;
public ImageView thumbnail;
public RelativeLayout viewBackground, viewForeground;
public MyViewHolder(View view) {
super(view);
name = view.findViewById(R.id.name);
description = view.findViewById(R.id.description);
price = view.findViewById(R.id.price);
thumbnail = view.findViewById(R.id.thumbnail);
viewBackground = view.findViewById(R.id.view_background);
viewForeground = view.findViewById(R.id.view_foreground);
}
}
public CartListAdapter(Context context, List<Item> cartList) {
this.context = context;
this.cartList = cartList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(context).inflate(R.layout.cart_list_item, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Item item = cartList.get(position);
holder.name.setText(item.getName());
holder.description.setText(item.getDescription());
holder.price.setText(item.getPrice()+"");
Glide.with(context)
.load(item.getThumbnail())
.into(holder.thumbnail);
}
@Override
public int getItemCount() {
return cartList.size();
}
public void removeItem(int position) {
cartList.remove(position);
notifyItemRemoved(position);
}
public void restoreItem(Item item, int position) {
cartList.add(position, item);
notifyItemInserted(position);
}
}
f. Tạo RecyclerItemTouchHelper
- lớp này sẽ thực hiện việc swipe item
RecyclerItemTouchHelper
public class RecyclerItemTouchHelper extends ItemTouchHelper.SimpleCallback {
private RecyclerItemTouchHelperListener listener;
public RecyclerItemTouchHelper(int dragDirs, int swipeDirs, RecyclerItemTouchHelperListener listener) {
super(dragDirs, swipeDirs);
this.listener = listener;
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (viewHolder != null) {
View foregroundView = ((CartListAdapter.MyViewHolder) viewHolder).viewForeground;
getDefaultUIUtil().onSelected(foregroundView);
}
}
@Override
public void onChildDrawOver(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View foregroundView = ((CartListAdapter.MyViewHolder) viewHolder).viewForeground;
getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
View foregroundView = ((CartListAdapter.MyViewHolder) viewHolder).viewForeground;
getDefaultUIUtil().clearView(foregroundView);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
View foregroundView = ((CartListAdapter.MyViewHolder) viewHolder).viewForeground;
getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY, actionState, isCurrentlyActive);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
if (listener != null) {
listener.onSwiped(viewHolder, direction, viewHolder.getAdapterPosition());
}
}
@Override
public int convertToAbsoluteDirection(int flags, int layoutDirection) {
return super.convertToAbsoluteDirection(flags, layoutDirection);
}
public interface RecyclerItemTouchHelperListener {
void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position);
}
}
g. update main activity
public class MainActivity extends AppCompatActivity implements RecyclerItemTouchHelper.RecyclerItemTouchHelperListener {
private final String URL_DATA = "https://api.androidhive.info/json/menu.json";
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView recyclerView;
private List<Item> cartList;
private CartListAdapter adapter;
private CoordinatorLayout coordinatorLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(R.string.my_cart);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout);
cartList = new ArrayList<>();
adapter = new CartListAdapter(this, cartList);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
recyclerView.setAdapter(adapter);
ItemTouchHelper.SimpleCallback itemTouchHelperCallback = new RecyclerItemTouchHelper(0, ItemTouchHelper.LEFT, this);
new ItemTouchHelper(itemTouchHelperCallback).attachToRecyclerView(recyclerView);
prepareCart();
}
private void prepareCart() {
JsonArrayRequest request = new JsonArrayRequest(URL_DATA, new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
if (response == null) {
return;
}
List<Item> items = new Gson().fromJson(response.toString(), new TypeToken<List<Item>>(){}.getType());
cartList.clear();
cartList.addAll(items);
adapter.notifyDataSetChanged();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
});
MyApplication.getInstance().addToRequestQueue(request);
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction, int position) {
if (viewHolder instanceof CartListAdapter.MyViewHolder) {
String name = cartList.get(viewHolder.getAdapterPosition()).getName();
final Item deletedItem = cartList.get(viewHolder.getAdapterPosition());
final int deletedIndex = viewHolder.getAdapterPosition();
adapter.removeItem(viewHolder.getAdapterPosition());
Snackbar snackbar = Snackbar.make(coordinatorLayout, name + " removed from cart!", Snackbar.LENGTH_LONG);
snackbar.setAction("UNDO", new View.OnClickListener() {
@Override
public void onClick(View view) {
adapter.restoreItem(deletedItem, deletedIndex);
}
});
snackbar.setActionTextColor(Color.YELLOW);
snackbar.show();
}
}
}
chạy và test app
All rights reserved