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

Phát hiện cuộn lên/xuống trong ListView Android

Giới thiệu

Bài viết này hướng dẫn cách phát hiện hướng cuộn (scroll up/down) trong ListView trên Android bằng cách implement AbsListView.OnScrollListener. Kỹ thuật này hữu ích cho: ẩn/hiện toolbar, load more (pagination), refresh, analytics...

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: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="8dp"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Bước 3: Viết code phát hiện cuộn (src/MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private String[] numbers = {
        "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
        "11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
        "21", "22", "23", "24", "25"
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = findViewById(R.id.listView);
        
        ArrayAdapter adapter = new ArrayAdapter<>(
            this, 
            android.R.layout.simple_list_item_1, 
            numbers
        );
        listView.setAdapter(adapter);

        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            private int lastFirstVisibleItem = 0;

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                // scrollState: SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING
                // Có thể dùng để detect khi user ngừng cuộn (IDLE) để load more, hide header...
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, 
                               int visibleItemCount, int totalItemCount) {
                
                // Phát hiện hướng cuộn
                if (lastFirstVisibleItem < firstVisibleItem) {
                    // Cuộn XUỐNG (scroll down) - item đầu tiên tăng index
                    onScrollDown();
                } else if (lastFirstVisibleItem > firstVisibleItem) {
                    // Cuộn LÊN (scroll up) - item đầu tiên giảm index
                    onScrollUp();
                }
                
                lastFirstVisibleItem = firstVisibleItem;
                
                // Bonus: Phát hiện cuộn đến cuối danh sách (load more)
                if (firstVisibleItem + visibleItemCount >= totalItemCount && totalItemCount > 0) {
                    onReachEnd();
                }
            }

            private void onScrollDown() {
                // Toast.makeText(MainActivity.this, "Đang cuộn xuống", Toast.LENGTH_SHORT).show();
                // Ví dụ: ẩn FloatingActionButton, ẩn Toolbar
            }

            private void onScrollUp() {
                // Toast.makeText(MainActivity.this, "Đang cuộn lên", Toast.LENGTH_SHORT).show();
                // Ví dụ: hiện Toolbar, hiện FAB
            }

            private void onReachEnd() {
                // Toast.makeText(MainActivity.this, "Đã đến cuối danh sách", Toast.LENGTH_SHORT).show();
                // loadMoreData();
            }
        });
    }
}

Bước 4: Cấu hình 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. Cuộn danh sách lên/xuống để thấy Toast thông báo hướng cuộn (nếu uncomment code Toast).

Lưu ý quan trọng - ListView đã lỗi thời

ListView đã được thay thế bởi RecyclerView từ Android Lollipop (API 21). RecyclerView mạnh mẽ, linh hoạt hơn nhiều. Dưới đây là cách tương tự với RecyclerView:

RecyclerView.OnScrollListener (Khuyên dùng)

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        
        // dy > 0: cuộn xuống, dy < 0: cuộn lên
        if (dy > 0) {
            onScrollDown();
        } else if (dy < 0) {
            onScrollUp();
        }
        
        // Kiểm tra đến cuối danh sách
        LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int lastVisiblePosition = layoutManager.findLastCompletelyVisibleItemPosition();
        int totalItemCount = layoutManager.getItemCount();
        
        if (lastVisiblePosition >= totalItemCount - 1) {
            onReachEnd();
        }
    }
    
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
    }
});

Thư viện hỗ trợ sẵn

  • SmartRefreshLayout: Pull-to-refresh, load more tích hợp sẵn.
  • Android-ObservableScrollView: Sync scroll events giữa nhiều view.
  • RecyclerViewPaginator: Tự động load more.

Ứng dụng thực tế

  • Ẩn/hiện Toolbar, BottomNavigationView, FAB khi cuộn.
  • Infinite scrolling (load more khi cuộn gần cuối).
  • Parallax effect cho header image.
  • Analytics: track xem user đọc đến đâu.
  • Tự động play/pause video khi item vào/ra khỏi màn hình.