Hướng dẫn này sẽ giới thiệu cách triển khai chức năng kéo và thả item trong Android RecyclerView.
Thêm chức năng kéo thả trong Android RecyclerView
Chức năng kéo và thả có thể được tích hợp vào RecyclerView thông qua lớp tiện ích ItemTouchHelper
. Dưới đây là các phương thức quan trọng trong giao diện ItemTouchHelper.Callback
cần được hiện thực:
isLongPressDragEnabled
– trả vềtrue
để bật thao tác nhấn giữ trên từng dòng trong RecyclerView nhằm kích hoạt chức năng kéo và thả.isItemViewSwipeEnabled
– cho phép bật hoặc tắt tính năng vuốt. Trong hướng dẫn này, chúng ta sẽ vô hiệu hóa tùy chọn này.getMovementFlags
– cho phép chỉ định các cờ (flags) xác định hướng kéo và vuốt. Vì tính năng vuốt đã bị tắt, chúng ta sẽ truyền0
cho phần vuốt.onMove
– xử lý logic khi thực hiện thao tác kéo và thả giữa các phần tử.onSwipe
– xử lý thao tác vuốt. Tuy nhiên, trong hướng dẫn hiện tại, phương thức này sẽ để trống.onSelectedChanged
– được gọi dựa trên trạng thái hiện tại của RecyclerView và việc phần tử có đang được nhấn giữ hoặc vuốt hay không. Tại đây, ta có thể tuỳ biến giao diện cho dòng dữ liệu, chẳng hạn như thay đổi màu nền khi đang được kéo.clearView
– được gọi khi người dùng kết thúc tương tác với một dòng trong RecyclerView.
Sau khi nắm các khái niệm cơ bản, chúng ta sẽ bắt đầu xây dựng ứng dụng Android với tính năng kéo và thả trong RecyclerView.
Cấu trúc dự án
Mã nguồn
Mã nguồn của tệp activity_main.xml
, trong đó chỉ chứa một RecyclerView, được trình bày dưới đây:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="<https://schemas.android.com/apk/res/android>"
xmlns:app="<https://schemas.android.com/apk/res-auto>"
xmlns:tools="<https://schemas.android.com/tools>"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</LinearLayout>
Mã nguồn của tệp MainActivity.java
được trình bày như sau:
package com.journaldev.androidrecyclerviewdraganddrop;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
RecyclerView recyclerView;
RecyclerViewAdapter mAdapter;
ArrayList<String> stringArrayList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerView);
populateRecyclerView();
}
private void populateRecyclerView() {
stringArrayList.add("Item 1");
stringArrayList.add("Item 2");
stringArrayList.add("Item 3");
stringArrayList.add("Item 4");
stringArrayList.add("Item 5");
stringArrayList.add("Item 6");
stringArrayList.add("Item 7");
stringArrayList.add("Item 8");
stringArrayList.add("Item 9");
stringArrayList.add("Item 10");
mAdapter = new RecyclerViewAdapter(stringArrayList);
ItemTouchHelper.Callback callback =
new ItemMoveCallback(mAdapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
touchHelper.attachToRecyclerView(recyclerView);
recyclerView.setAdapter(mAdapter);
}
}
Trong đoạn mã trên, chúng ta đã khởi tạo RecyclerViewAdapter.java
với một ArrayList
chứa các chuỗi. Một đối tượng của lớp ItemMoveCallback.java
được gắn vào RecyclerView để kích hoạt chức năng kéo và thả. Dưới đây là mã nguồn của lớp ItemMoveCallback.java
:
package com.journaldev.androidrecyclerviewdraganddrop;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
public class ItemMoveCallback extends ItemTouchHelper.Callback {
private final ItemTouchHelperContract mAdapter;
public ItemMoveCallback(ItemTouchHelperContract adapter) {
mAdapter = adapter;
}
@Override
public boolean isLongPressDragEnabled() {
return true;
}
@Override
public boolean isItemViewSwipeEnabled() {
return false;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
return makeMovementFlags(dragFlags, 0);
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onRowMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
if (viewHolder instanceof RecyclerViewAdapter.MyViewHolder) {
RecyclerViewAdapter.MyViewHolder myViewHolder =
(RecyclerViewAdapter.MyViewHolder) viewHolder;
mAdapter.onRowSelected(myViewHolder);
}
}
super.onSelectedChanged(viewHolder, actionState);
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (viewHolder instanceof RecyclerViewAdapter.MyViewHolder) {
RecyclerViewAdapter.MyViewHolder myViewHolder =
(RecyclerViewAdapter.MyViewHolder) viewHolder;
mAdapter.onRowClear(myViewHolder);
}
}
public interface ItemTouchHelperContract {
void onRowMoved(int fromPosition, int toPosition);
void onRowSelected(RecyclerViewAdapter.MyViewHolder myViewHolder);
void onRowClear(RecyclerViewAdapter.MyViewHolder myViewHolder);
}
}
Tại đây, chúng ta đã định nghĩa một interface có tên ItemTouchHelperContract
. Mỗi phương thức trong interface này sẽ được gọi từ các phương thức đã được triển khai trong interface ItemTouchHelper.Callback
. Mã nguồn của lớp RecyclerViewAdapter.java
được trình bày như sau:
package com.journaldev.androidrecyclerviewdraganddrop;
import android.graphics.Color;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Collections;
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.MyViewHolder> implements ItemMoveCallback.ItemTouchHelperContract {
private ArrayList<String> data;
public class MyViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
View rowView;
public MyViewHolder(View itemView) {
super(itemView);
rowView = itemView;
mTitle = itemView.findViewById(R.id.txtTitle);
}
}
public RecyclerViewAdapter(ArrayList<String> data) {
this.data = data;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_row, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.mTitle.setText(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
@Override
public void onRowMoved(int fromPosition, int toPosition) {
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(data, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(data, i, i - 1);
}
}
notifyItemMoved(fromPosition, toPosition);
}
@Override
public void onRowSelected(MyViewHolder myViewHolder) {
myViewHolder.rowView.setBackgroundColor(Color.GRAY);
}
@Override
public void onRowClear(MyViewHolder myViewHolder) {
myViewHolder.rowView.setBackgroundColor(Color.WHITE);
}
}
Phương thức onRowMoved
được định nghĩa trong giao diện Contract
trước đó sẽ được gọi khi thao tác kéo thả hoàn tất. Tại đây, chúng ta thực hiện hoán đổi vị trí hai dòng trong ArrayList
và gọi notifyItemMoved
để cập nhật lại adapter.
Kết quả đầu ra của ứng dụng:
Hiện tại, chức năng kéo và thả được thực hiện bằng cách nhấn giữ ở bất kỳ vị trí nào trong mỗi dòng của RecyclerView. Tiếp theo, chúng ta sẽ tìm hiểu cách thực hiện thao tác tương tự nhưng chỉ khi nhấn vào một thành phần cụ thể trong dòng dữ liệu.
Kéo và thả sử dụng Handle
Để sử dụng một thành phần cụ thể (handle view) trong dòng RecyclerView làm điểm kích hoạt kéo và thả, chúng ta cần thực hiện các bước sau:
- Đặt
isLongPressDragEnabled
thànhfalse
để vô hiệu hoá thao tác kéo và thả mặc định. - Tạo một giao diện như sau:
public interface StartDragListener {
void requestDrag(RecyclerView.ViewHolder viewHolder);
}
- Triển khai giao diện này trong
MainActivity
và truyền nó vào Adapter.
@Override
public void requestDrag(RecyclerView.ViewHolder viewHolder) {
touchHelper.startDrag(viewHolder);
}
mAdapter = new RecyclerViewAdapter(stringArrayList, this);
Bên trong tệp RecyclerViewAdapter.java
, thực hiện như sau:
holder.imageView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mStartDragListener.requestDrag(holder);
}
return false;
}
});
Bạn có thể tìm thấy mã nguồn được cập nhật trong liên kết tải xuống ở cuối hướng dẫn này. Kết quả đầu ra của ứng dụng sau khi cập nhật mã được trình bày dưới đây:
Đến đây, bài hướng dẫn của chúng ta cũng đã đến lúc phải khép lại. Toàn bộ mã nguồn của dự án được trình bày bên dưới:
AndroidRecyclerViewDragAndDrop