Giới thiệu
Bài viết này hướng dẫn cách cập nhật động (thêm/xóa/sửa) dữ liệu trong ListView trên Android bằng cách sử dụng ArrayAdapter và gọi notifyDataSetChanged(). Kỹ thuật này áp dụng cho danh sách đơn giản, chat app, todo list, feed...
Các bước thực hiện
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.
Bước 2: Layout chính (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nhập mục mới..."
android:inputType="text" />
<Button
android:id="@+id/btnAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/editText"
android:layout_marginTop="8dp"
android:text="Thêm mục" />
<Button
android:id="@+id/btnRemove"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/editText"
android:layout_toRightOf="@id/btnAdd"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="Xóa mục cuối" />
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/btnAdd"
android:layout_marginTop="16dp" />
</RelativeLayout>
Bước 3: Viết code cập nhật động (src/MainActivity.java)
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private EditText editText;
private Button btnAdd, btnRemove;
private ListView listView;
private ArrayList items = new ArrayList<>();
private ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
btnAdd = findViewById(R.id.btnAdd);
btnRemove = findViewById(R.id.btnRemove);
listView = findViewById(R.id.listView);
// Khởi tạo adapter với layout hệ thống đơn giản
adapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
items
);
listView.setAdapter(adapter);
// Thêm mục
btnAdd.setOnClickListener(v -> {
String text = editText.getText().toString().trim();
if (!text.isEmpty()) {
items.add(text);
adapter.notifyDataSetChanged(); // QUAN TRỌNG: Thông báo adapter data changed
editText.setText("");
}
});
// Xóa mục cuối
btnRemove.setOnClickListener(v -> {
if (!items.isEmpty()) {
items.remove(items.size() - 1);
adapter.notifyDataSetChanged();
}
});
// Xóa mục khi click (bonus)
listView.setOnItemClickListener((parent, view, position, id) -> {
items.remove(position);
adapter.notifyDataSetChanged();
});
// Xóa mục khi long click (bonus)
listView.setOnItemLongClickListener((parent, view, position, id) -> {
items.remove(position);
adapter.notifyDataSetChanged();
return true;
});
}
}
Bước 4: Manifest (androidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="app.com.sample">
<application
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">
<intent-filter>
</intent-filter>
</activity>
</application>
</manifest>
Chạy ứng dụng
Kết nối thiết bị, nhấn Run. Nhập text, nhấn "Thêm mục" -> item xuất hiện ngay. Click item để xóa. Nhấn "Xóa mục cuối" để xóa item cuối cùng.
Các phương thức notify quan trọng
| Phương thức | Khi nào dùng | Hiệu năng |
|---|---|---|
notifyDataSetChanged() |
Dữ liệu thay đổi lớn, nhiều item, hoặc không biết chính xác gì thay đổi | Reload toàn bộ list - chậm với list lớn |
notifyItemInserted(position) |
Thêm 1 item tại vị trí cụ thể (RecyclerView) | Animation mượt, chỉ cập nhật item mới |
notifyItemRemoved(position) |
Xóa 1 item tại vị trí cụ thể (RecyclerView) | Animation mượt |
notifyItemChanged(position) |
Cập nhật 1 item (RecyclerView) | Chỉ rebind 1 viewholder |
notifyItemRangeInserted/Removed/Changed |
Thêm/xóa/sửa nhiều item liên tiếp (RecyclerView) | Tối ưu cho batch update |
⚠️ ListView vs RecyclerView
ListView đã lỗi thời. RecyclerView là thay thế hiện đại, bắt buộc dùng RecyclerView.Adapter với ViewHolder pattern.
RecyclerView Dynamic Update (Khuyên dùng)
public class MyAdapter extends RecyclerView.Adapter{ private List items = new ArrayList<>(); // Thêm item public void addItem(String item) { items.add(item); notifyItemInserted(items.size() - 1); } // Thêm nhiều item public void addItems(List newItems) { int start = items.size(); items.addAll(newItems); notifyItemRangeInserted(start, newItems.size()); } // Xóa item public void removeItem(int position) { items.remove(position); notifyItemRemoved(position); } // Cập nhật item public void updateItem(int position, String newText) { items.set(position, newText); notifyItemChanged(position); } // Swap toàn bộ data (search, filter) public void setItems(List newItems) { items.clear(); items.addAll(newItems); notifyDataSetChanged(); // Hoặc DiffUtil cho animation } }
DiffUtil - Cập nhật thông minh cho RecyclerView
// Tính toán diff giữa list cũ và mới -> chỉ update những gì thay đổi
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() { return oldList.size(); }
@Override
public int getNewListSize() { return newList.size(); }
@Override
public boolean areItemsTheSame(int oldPos, int newPos) {
return oldList.get(oldPos).getId() == newList.get(newPos).getId();
}
@Override
public boolean areContentsTheSame(int oldPos, int newPos) {
return oldList.get(oldPos).equals(newList.get(newPos));
}
}, true); // detect moves
diffResult.dispatchUpdatesTo(adapter);
Lưu ý quan trọng
- Luôn sửa đổi list data trước, rồi gọi notify - không bao giờ gọi notify trước khi sửa data.
- Trên UI Thread: notifyDataSetChanged() phải chạy trên main thread.
- Async data loading: Load data trên background, sau đó post về main thread để update adapter.
- Animation: RecyclerView tự động animate insert/remove/change khi dùng notifyItem*. ListView không có animation mặc định.
- DiffUtil chạy trên background thread cho list lớn (>100 items).