Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cập nhật động ListView (Dynamic Update) trên Android

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).