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.